Skip to content

Commit

Permalink
Merge pull request Pyomo#2901 from Robbybp/pynumero-eval-error
Browse files Browse the repository at this point in the history
Add `PyNumeroEvaluationError`
  • Loading branch information
michaelbynum authored Jul 17, 2023
2 parents 1288032 + ef93dca commit be93e57
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 5 deletions.
16 changes: 11 additions & 5 deletions pyomo/contrib/pynumero/asl.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

from pyomo.common.fileutils import find_library
from pyomo.common.dependencies import numpy as np
from pyomo.contrib.pynumero.exceptions import PyNumeroEvaluationError
import ctypes
import logging
import os
Expand Down Expand Up @@ -364,7 +365,8 @@ def eval_f(self, x):
res = self.ASLib.EXTERNAL_AmplInterface_eval_f(
self._obj, x, self._nx, ctypes.byref(sol)
)
assert res, "Error in AMPL evaluation"
if not res:
raise PyNumeroEvaluationError("Error in AMPL evaluation")
return sol.value

def eval_deriv_f(self, x, df):
Expand All @@ -373,7 +375,8 @@ def eval_deriv_f(self, x, df):
x.dtype == np.double
), "Error: array type. Function eval_deriv_f expects an array of type double"
res = self.ASLib.EXTERNAL_AmplInterface_eval_deriv_f(self._obj, x, df, len(x))
assert res, "Error in AMPL evaluation"
if not res:
raise PyNumeroEvaluationError("Error in AMPL evaluation")

def struct_jac_g(self, irow, jcol):
irow_p = irow.astype(np.intc, casting='safe', copy=False)
Expand Down Expand Up @@ -409,7 +412,8 @@ def eval_jac_g(self, x, jac_g_values):
res = self.ASLib.EXTERNAL_AmplInterface_eval_jac_g(
self._obj, xeval, self._nx, jac_eval, self._nnz_jac_g
)
assert res, "Error in AMPL evaluation"
if not res:
raise PyNumeroEvaluationError("Error in AMPL evaluation")

def eval_g(self, x, g):
assert x.size == self._nx, "Error: Dimension mismatch."
Expand All @@ -423,7 +427,8 @@ def eval_g(self, x, g):
res = self.ASLib.EXTERNAL_AmplInterface_eval_g(
self._obj, x, self._nx, g, self._ny
)
assert res, "Error in AMPL evaluation"
if not res:
raise PyNumeroEvaluationError("Error in AMPL evaluation")

def eval_hes_lag(self, x, lam, hes_lag, obj_factor=1.0):
assert x.size == self._nx, "Error: Dimension mismatch."
Expand Down Expand Up @@ -453,7 +458,8 @@ def eval_hes_lag(self, x, lam, hes_lag, obj_factor=1.0):
res = self.ASLib.EXTERNAL_AmplInterface_eval_hes_lag(
self._obj, x, self._nx, lam, self._ny, hes_lag, self._nnz_hess
)
assert res, "Error in AMPL evaluation"
if not res:
raise PyNumeroEvaluationError("Error in AMPL evaluation")

def finalize_solution(self, ampl_solve_status_num, msg, x, lam):
b_msg = msg.encode('utf-8')
Expand Down
20 changes: 20 additions & 0 deletions pyomo/contrib/pynumero/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# ___________________________________________________________________________
#
# Pyomo: Python Optimization Modeling Objects
# Copyright (c) 2008-2022
# National Technology and Engineering Solutions of Sandia, LLC
# Under the terms of Contract DE-NA0003525 with National Technology and
# Engineering Solutions of Sandia, LLC, the U.S. Government retains certain
# rights in this software.
# This software is distributed under the 3-clause BSD License.
# ___________________________________________________________________________


class PyNumeroEvaluationError(ArithmeticError):
"""An exception to be raised by PyNumero evaluation backends in the event
of a failed function evaluation. This should be caught by solver interfaces
and translated to the solver-specific evaluation error API.
"""

pass
52 changes: 52 additions & 0 deletions pyomo/contrib/pynumero/interfaces/tests/test_nlp.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
raise unittest.SkipTest("Pynumero needs scipy and numpy to run NLP tests")

from pyomo.contrib.pynumero.asl import AmplInterface
from pyomo.contrib.pynumero.exceptions import PyNumeroEvaluationError

if not AmplInterface.available():
raise unittest.SkipTest("Pynumero needs the ASL extension to run NLP tests")
Expand Down Expand Up @@ -816,6 +817,57 @@ def test_util_maps(self):
self.assertTrue(np.array_equal(expected_full_primals_lb, full_primals_lb))


class TestExceptions(unittest.TestCase):
def _make_bad_model(self):
m = pyo.ConcreteModel()
m.I = pyo.Set(initialize=[1, 2, 3])
m.x = pyo.Var(m.I, initialize=1)

m.obj = pyo.Objective(expr=m.x[1] + m.x[2] / m.x[3])
m.eq1 = pyo.Constraint(expr=m.x[1] == pyo.sqrt(m.x[2]))
return m

def test_eval_error_in_constraint(self):
m = self._make_bad_model()
m.x[2] = -1
nlp = PyomoNLP(m)
msg = "Error in AMPL evaluation"
with self.assertRaisesRegex(PyNumeroEvaluationError, msg):
residuals = nlp.evaluate_constraints()

def test_eval_error_in_constraint_jacobian(self):
m = self._make_bad_model()
m.x[2] = -1
nlp = PyomoNLP(m)
msg = "Error in AMPL evaluation"
with self.assertRaisesRegex(PyNumeroEvaluationError, msg):
jacobian = nlp.evaluate_jacobian()

def test_eval_error_in_objective(self):
m = self._make_bad_model()
m.x[3] = 0
nlp = PyomoNLP(m)
msg = "Error in AMPL evaluation"
with self.assertRaisesRegex(PyNumeroEvaluationError, msg):
objval = nlp.evaluate_objective()

def test_eval_error_in_objective_gradient(self):
m = self._make_bad_model()
m.x[3] = 0
nlp = PyomoNLP(m)
msg = "Error in AMPL evaluation"
with self.assertRaisesRegex(PyNumeroEvaluationError, msg):
gradient = nlp.evaluate_grad_objective()

def test_eval_error_in_lagrangian_hessian(self):
m = self._make_bad_model()
m.x[3] = 0
nlp = PyomoNLP(m)
msg = "Error in AMPL evaluation"
with self.assertRaisesRegex(PyNumeroEvaluationError, msg):
hessian = nlp.evaluate_hessian_lag()


if __name__ == '__main__':
TestAslNLP.setUpClass()
t = TestAslNLP()
Expand Down

0 comments on commit be93e57

Please sign in to comment.