From 3398780eca5cbf2fd670ee377ca4c7a05746bfb3 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Thu, 21 Dec 2023 16:47:17 -0700 Subject: [PATCH 1/4] adding some comments --- pyomo/contrib/pynumero/interfaces/external_grey_box.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyomo/contrib/pynumero/interfaces/external_grey_box.py b/pyomo/contrib/pynumero/interfaces/external_grey_box.py index 642fd3bf310..080cfdc48f9 100644 --- a/pyomo/contrib/pynumero/interfaces/external_grey_box.py +++ b/pyomo/contrib/pynumero/interfaces/external_grey_box.py @@ -45,6 +45,10 @@ we have a specialized interface built on PyNumero that provides an interface to the CyIpopt solver. +constraints: c(x) = 0 +outputs: y = c(x) + + To use this interface: * Create a class that is derived from ExternalGreyBoxModel and implement the necessary methods. This derived class must provide From dd6248d0f3314d6c3d18e3c9ee0057c18b284043 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Sun, 8 Sep 2024 11:26:04 -0600 Subject: [PATCH 2/4] add support for objectives in external grey box --- .../external_with_objective.py | 151 ++++++++++++++++++ .../pynumero/interfaces/external_grey_box.py | 27 ++++ .../pynumero/interfaces/pyomo_grey_box_nlp.py | 135 +++++++++++----- .../tests/test_pyomo_grey_box_nlp.py | 74 +++------ 4 files changed, 293 insertions(+), 94 deletions(-) create mode 100644 pyomo/contrib/pynumero/examples/external_grey_box/external_with_objective.py diff --git a/pyomo/contrib/pynumero/examples/external_grey_box/external_with_objective.py b/pyomo/contrib/pynumero/examples/external_grey_box/external_with_objective.py new file mode 100644 index 00000000000..57835dcc049 --- /dev/null +++ b/pyomo/contrib/pynumero/examples/external_grey_box/external_with_objective.py @@ -0,0 +1,151 @@ +import math +import numpy as np +from scipy.sparse import coo_matrix +import pyomo.environ as pe +from pyomo.contrib.pynumero.interfaces.external_grey_box import ( + ExternalGreyBoxModel, + ExternalGreyBoxBlock, +) + + +class Unconstrained(ExternalGreyBoxModel): + """ + min (x+2)**2 + (y-2)**2 + """ + + def input_names(self): + return ['x', 'y'] + + def set_input_values(self, input_values): + self._input_values = list(input_values) + + def has_objective(self): + return True + + def evaluate_objective(self): + x = self._input_values[0] + y = self._input_values[1] + return (x + 2) ** 2 + (y - 2) ** 2 + + def evaluate_grad_objective(self): + x = self._input_values[0] + y = self._input_values[1] + return np.asarray([2 * (x + 2), 2 * (y - 2)], dtype=float) + + +class Constrained(ExternalGreyBoxModel): + """ + min x**2 + y**2 + s.t. 0 == y - exp(x) + """ + + def input_names(self): + return ['x', 'y'] + + def set_input_values(self, input_values): + self._input_values = list(input_values) + + def has_objective(self): + return True + + def evaluate_objective(self): + x = self._input_values[0] + y = self._input_values[1] + return x**2 + y**2 + + def evaluate_grad_objective(self): + x = self._input_values[0] + y = self._input_values[1] + return np.asarray([2 * x, 2 * y], dtype=float) + + def equality_constraint_names(self): + return ['c1'] + + def evaluate_equality_constraints(self): + x = self._input_values[0] + y = self._input_values[1] + return np.asarray([y - math.exp(x)], dtype=float) + + def evaluate_jacobian_equality_constraints(self): + x = self._input_values[0] + row = [0, 0] + col = [0, 1] + data = [-math.exp(x), 1] + jac = coo_matrix((data, (row, col)), shape=(1, 2)) + return jac + + +class ConstrainedWithHessian(Constrained): + def evaluate_hessian_objective(self): + row = [0, 1] + col = [0, 1] + data = [2, 2] + hess = coo_matrix((data, (row, col)), shape=(2, 2)) + return hess + + def set_equality_constraint_multipliers(self, eq_con_multiplier_values): + self._dual = eq_con_multiplier_values[0] + + def evaluate_hessian_equality_constraints(self): + x = self._input_values[0] + row = [0] + col = [0] + data = [-math.exp(x) * self._dual] + hess = coo_matrix((data, (row, col)), shape=(2, 2)) + return hess + + +def solve_unconstrained(): + m = pe.ConcreteModel() + m.z = pe.Var() + m.grey_box = ExternalGreyBoxBlock(external_model=Unconstrained()) + m.c = pe.Constraint(expr=m.z == m.grey_box.inputs['x'] + 1) + + opt = pe.SolverFactory('cyipopt') + opt.config.options['hessian_approximation'] = 'limited-memory' + res = opt.solve(m, tee=True) + pe.assert_optimal_termination(res) + x = m.grey_box.inputs['x'].value + y = m.grey_box.inputs['y'].value + assert math.isclose(x, -2) + assert math.isclose(y, 2) + return m + + +def solve_constrained(): + m = pe.ConcreteModel() + m.z = pe.Var() + m.grey_box = ExternalGreyBoxBlock(external_model=Constrained()) + m.c2 = pe.Constraint(expr=m.z == m.grey_box.inputs['x'] + 1) + + opt = pe.SolverFactory('cyipopt') + opt.config.options['hessian_approximation'] = 'limited-memory' + res = opt.solve(m, tee=True) + pe.assert_optimal_termination(res) + x = m.grey_box.inputs['x'].value + y = m.grey_box.inputs['y'].value + assert math.isclose(x, -0.4263027509962655) + assert math.isclose(y, 0.6529186403960969) + return m + + +def solve_constrained_with_hessian(): + m = pe.ConcreteModel() + m.z = pe.Var() + m.grey_box = ExternalGreyBoxBlock(external_model=ConstrainedWithHessian()) + m.c2 = pe.Constraint(expr=m.z == m.grey_box.inputs['x'] + 1) + + opt = pe.SolverFactory('cyipopt') + res = opt.solve(m, tee=True) + pe.assert_optimal_termination(res) + x = m.grey_box.inputs['x'].value + y = m.grey_box.inputs['y'].value + assert math.isclose(x, -0.4263027509962655) + assert math.isclose(y, 0.6529186403960969) + return m + + +if __name__ == '__main__': + m = solve_constrained_with_hessian() + print(f"x: {m.grey_box.inputs['x'].value}") + print(f"y: {m.grey_box.inputs['y'].value}") diff --git a/pyomo/contrib/pynumero/interfaces/external_grey_box.py b/pyomo/contrib/pynumero/interfaces/external_grey_box.py index 2931166638d..72327e15d2d 100644 --- a/pyomo/contrib/pynumero/interfaces/external_grey_box.py +++ b/pyomo/contrib/pynumero/interfaces/external_grey_box.py @@ -11,6 +11,7 @@ import abc import logging +import numpy as np from scipy.sparse import coo_matrix from pyomo.common.dependencies import numpy as np @@ -130,6 +131,9 @@ def evaluate_hessian_outputs(self): the input variables. This method must return H_o^k = sum_i (y_o^k)_i * grad^2_{uu} w_o(u^k) + def evaluate_hessian_objective(self): + Compute the hessian of the objective + Examples that show Hessian support are also found in: pyomo/contrib/pynumero/examples/external_grey_box/react-example/ @@ -319,6 +323,29 @@ def evaluate_jacobian_outputs(self): # def evaluate_hessian_outputs(self): # + # Support for objectives + def has_objective(self): + return False + + def evaluate_objective(self) -> float: + """ + Compute the objective from the values set in + input_values + """ + raise NotImplementedError( + 'evaluate_objective called but not ' 'implemented in the derived class.' + ) + + def evaluate_grad_objective(self, out=None): + """ + Compute the gradient of the objective from the + values set in input_values + """ + raise NotImplementedError( + 'evaluate_grad_objective called but not ' + 'implemented in the derived class.' + ) + class ExternalGreyBoxBlockData(BlockData): def set_external_model(self, external_grey_box_model, inputs=None, outputs=None): diff --git a/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py b/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py index 66cf99ea862..3ec7856a612 100644 --- a/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py +++ b/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py @@ -341,8 +341,8 @@ def get_duals(self): # overloaded from NLP def set_obj_factor(self, obj_factor): - # objective is owned by the pyomo model - self._pyomo_nlp.set_obj_factor(obj_factor) + for nlp in self._nlps: + nlp.set_obj_factor(obj_factor) # overloaded from NLP def get_obj_factor(self): @@ -364,11 +364,22 @@ def get_constraints_scaling(self): # overloaded from NLP def evaluate_objective(self): # objective is owned by the pyomo model - return self._pyomo_nlp.evaluate_objective() + obj = 0 + for nlp in self._nlps: + obj += nlp.evaluate_objective() + return obj # overloaded from NLP def evaluate_grad_objective(self, out=None): - return self._pyomo_nlp.evaluate_grad_objective(out=out) + ret = np.zeros(self.n_primals(), dtype=float) + for nlp in self._nlps: + ret += nlp.evaluate_grad_objective() + + if out is not None: + ret.copyto(out) + return out + + return ret # overloaded from NLP def evaluate_constraints(self, out=None): @@ -476,29 +487,21 @@ class _ExternalGreyBoxAsNLP(NLP): """ This class takes an ExternalGreyBoxModel and makes it look like an NLP so it can be used with other interfaces. Currently, - the ExternalGreyBoxModel supports constraints only (no objective), - so some of the methods are not appropriate and raise exceptions + the ExternalGreyBoxModel supports objectives and equality + constraints only, so some of the methods are not appropriate + and raise exceptions """ def __init__(self, external_grey_box_block): self._block = external_grey_box_block self._ex_model = external_grey_box_block.get_external_model() + self._obj_factor = 1.0 n_inputs = len(self._block.inputs) assert n_inputs == self._ex_model.n_inputs() n_eq_constraints = self._ex_model.n_equality_constraints() n_outputs = len(self._block.outputs) assert n_outputs == self._ex_model.n_outputs() - if ( - self._ex_model.n_outputs() == 0 - and self._ex_model.n_equality_constraints() == 0 - ): - raise ValueError( - 'ExternalGreyBoxModel has no equality constraints ' - 'or outputs. To use _ExternalGreyBoxAsNLP, it must' - ' have at least one or both.' - ) - # create the list of primals and constraint names # primals will be ordered inputs, followed by outputs self._primals_names = [ @@ -584,12 +587,18 @@ def __init__(self, external_grey_box_block): self._ex_model, 'evaluate_hessian_outputs' ): self._has_hessian_support = False + if self._ex_model.has_objective() and not hasattr( + self._ex_model, 'evaluate_hessian_objective' + ): + self._has_hessian_support = False self._nnz_jacobian = None self._nnz_hessian_lag = None self._cached_constraint_residuals = None self._cached_jacobian = None self._cached_hessian = None + self._cached_objective = None + self._cached_grad_objective = None def n_primals(self): return len(self._primals_names) @@ -643,6 +652,8 @@ def _cache_invalidate_primals(self): self._cached_constraint_residuals = None self._cached_jacobian = None self._cached_hessian = None + self._cached_objective = None + self._cached_grad_objective = None def set_primals(self, primals): self._cache_invalidate_primals() @@ -673,13 +684,16 @@ def get_duals(self): return np.copy(self._dual_values) def set_obj_factor(self, obj_factor): - raise NotImplementedError('_ExternalGreyBoxAsNLP does not support objectives') + self._cached_hessian = None + self._obj_factor = obj_factor def get_obj_factor(self): - raise NotImplementedError('_ExternalGreyBoxAsNLP does not support objectives') + return self._obj_factor def get_obj_scaling(self): - raise NotImplementedError('_ExternalGreyBoxAsNLP does not support objectives') + raise NotImplementedError( + '_ExternalGreyBoxAsNLP does not support objective scaling' + ) def get_primals_scaling(self): raise NotImplementedError( @@ -706,13 +720,31 @@ def get_constraints_scaling(self): return scaling return None + def _evaluate_objective_if_necessary_and_cache(self): + if self._ex_model.has_objective(): + if self._cached_objective is None: + self._cached_objective = self._ex_model.evaluate_objective() + else: + self._cached_objective = 0 + def evaluate_objective(self): - # todo: Should we return 0 here? - raise NotImplementedError('_ExternalGreyBoxNLP does not support objectives') + self._evaluate_objective_if_necessary_and_cache() + return self._cached_objective + + def _evaluate_grad_objective_if_necessary_and_cache(self): + if self._ex_model.has_objective(): + if self._cached_grad_objective is None: + self._cached_grad_objective = self._ex_model.evaluate_grad_objective() + else: + self._cached_grad_objective = np.zeros(self.n_primals(), dtype=float) def evaluate_grad_objective(self, out=None): - # todo: Should we return 0 here? - raise NotImplementedError('_ExternalGreyBoxNLP does not support objectives') + self._evaluate_grad_objective_if_necessary_and_cache() + if out is not None: + assert len(out) == self.n_primals() + np.copyto(out, self._cached_grad_objective) + return out + return np.copy(self._cached_grad_objective) def _evaluate_constraints_if_necessary_and_cache(self): if self._cached_constraint_residuals is None: @@ -774,10 +806,37 @@ def _evaluate_hessian_if_necessary_and_cache(self): hess.set_col_size(0, self._ex_model.n_inputs()) hess.set_col_size(1, self._ex_model.n_outputs()) + n = self._ex_model.n_inputs() + expected_shape = (n, n) + + # get the hessian of the objective + if self._ex_model.has_objective(): + obj_hess = ( + self._ex_model.evaluate_hessian_objective() * self.get_obj_factor() + ) + if obj_hess.shape != expected_shape: + raise ValueError( + 'ExternalGreyBoxModel objective hessian shape' + 'shoule be (n_inputs, n_inputs)' + ) + # let's check that it is lower triangular + if np.any(obj_hess.row < obj_hess.col): + raise ValueError( + 'ExternalGreyBoxModel must return lower' + 'triangular portion of the Hessian only' + ) + obj_hess = make_lower_triangular_full(obj_hess) + else: + obj_hess = coo_matrix(([], ([], [])), shape=expected_shape) + # get the hessian w.r.t. the equality constraints - eq_hess = None if self._ex_model.n_equality_constraints() > 0: eq_hess = self._ex_model.evaluate_hessian_equality_constraints() + if eq_hess.shape != expected_shape: + raise ValueError( + 'ExternalGreyBoxModel equality constraint hessian shape' + 'shoule be (n_inputs, n_inputs)' + ) # let's check that it is lower triangular if np.any(eq_hess.row < eq_hess.col): raise ValueError( @@ -786,10 +845,16 @@ def _evaluate_hessian_if_necessary_and_cache(self): ) eq_hess = make_lower_triangular_full(eq_hess) + else: + eq_hess = coo_matrix(([], ([], [])), shape=expected_shape) - output_hess = None if self._ex_model.n_outputs() > 0: output_hess = self._ex_model.evaluate_hessian_outputs() + if output_hess.shape != expected_shape: + raise ValueError( + 'ExternalGreyBoxModel output hessian shape' + 'shoule be (n_inputs, n_inputs)' + ) # let's check that it is lower triangular if np.any(output_hess.row < output_hess.col): raise ValueError( @@ -798,21 +863,13 @@ def _evaluate_hessian_if_necessary_and_cache(self): ) output_hess = make_lower_triangular_full(output_hess) + else: + output_hess = coo_matrix(([], ([], [])), shape=expected_shape) - input_hess = None - if eq_hess is not None and output_hess is not None: - # we may want to make this more efficient - row = np.concatenate((eq_hess.row, output_hess.row)) - col = np.concatenate((eq_hess.col, output_hess.col)) - data = np.concatenate((eq_hess.data, output_hess.data)) - - assert eq_hess.shape == output_hess.shape - input_hess = coo_matrix((data, (row, col)), shape=eq_hess.shape) - elif eq_hess is not None: - input_hess = eq_hess - elif output_hess is not None: - input_hess = output_hess - assert input_hess is not None # need equality or outputs or both + row = np.concatenate((obj_hess.row, eq_hess.row, output_hess.row)) + col = np.concatenate((obj_hess.col, eq_hess.col, output_hess.col)) + data = np.concatenate((obj_hess.data, eq_hess.data, output_hess.data)) + input_hess = coo_matrix((data, (row, col)), shape=expected_shape) hess.set_block(0, 0, input_hess) self._cached_hessian = hess.tocoo() diff --git a/pyomo/contrib/pynumero/interfaces/tests/test_pyomo_grey_box_nlp.py b/pyomo/contrib/pynumero/interfaces/tests/test_pyomo_grey_box_nlp.py index ecadf40e5cf..64717e52783 100644 --- a/pyomo/contrib/pynumero/interfaces/tests/test_pyomo_grey_box_nlp.py +++ b/pyomo/contrib/pynumero/interfaces/tests/test_pyomo_grey_box_nlp.py @@ -41,6 +41,11 @@ check_sparse_matrix_specific_order, ) import pyomo.contrib.pynumero.interfaces.tests.external_grey_box_models as ex_models +from pyomo.contrib.pynumero.examples.external_grey_box.external_with_objective import ( + solve_unconstrained, + solve_constrained, + solve_constrained_with_hessian, +) class TestExternalGreyBoxAsNLP(unittest.TestCase): @@ -135,17 +140,6 @@ def _test_pressure_drop_single_output(self, ex_model, hessian_support): y = egb_nlp.get_duals() self.assertTrue(np.array_equal(y, np.asarray([21], dtype=np.float64))) - with self.assertRaises(NotImplementedError): - fac = egb_nlp.get_obj_factor() - with self.assertRaises(NotImplementedError): - egb_nlp.set_obj_factor(42) - with self.assertRaises(NotImplementedError): - egb_nlp.set_obj_factor(1) - with self.assertRaises(NotImplementedError): - f = egb_nlp.evaluate_objective() - with self.assertRaises(NotImplementedError): - gradf = egb_nlp.evaluate_grad_objective() - c = egb_nlp.evaluate_constraints() comparison_c = np.asarray([-22], dtype=np.float64) check_vectors_specific_order(self, c, c_order, comparison_c, comparison_c_order) @@ -294,17 +288,6 @@ def _test_pressure_drop_single_equality(self, ex_model, hessian_support): y = egb_nlp.get_duals() self.assertTrue(np.array_equal(y, np.asarray([21], dtype=np.float64))) - with self.assertRaises(NotImplementedError): - fac = egb_nlp.get_obj_factor() - with self.assertRaises(NotImplementedError): - egb_nlp.set_obj_factor(42) - with self.assertRaises(NotImplementedError): - egb_nlp.set_obj_factor(1) - with self.assertRaises(NotImplementedError): - f = egb_nlp.evaluate_objective() - with self.assertRaises(NotImplementedError): - gradf = egb_nlp.evaluate_grad_objective() - c = egb_nlp.evaluate_constraints() comparison_c = np.asarray([22], dtype=np.float64) check_vectors_specific_order(self, c, c_order, comparison_c, comparison_c_order) @@ -459,17 +442,6 @@ def _test_pressure_drop_two_outputs(self, ex_model, hessian_support): y = egb_nlp.get_duals() self.assertTrue(np.array_equal(y, np.asarray([21, 5], dtype=np.float64))) - with self.assertRaises(NotImplementedError): - fac = egb_nlp.get_obj_factor() - with self.assertRaises(NotImplementedError): - egb_nlp.set_obj_factor(42) - with self.assertRaises(NotImplementedError): - egb_nlp.set_obj_factor(1) - with self.assertRaises(NotImplementedError): - f = egb_nlp.evaluate_objective() - with self.assertRaises(NotImplementedError): - gradf = egb_nlp.evaluate_grad_objective() - c = egb_nlp.evaluate_constraints() comparison_c = np.asarray([-16, -22], dtype=np.float64) check_vectors_specific_order(self, c, c_order, comparison_c, comparison_c_order) @@ -630,17 +602,6 @@ def _test_pressure_drop_two_equalities(self, ex_model, hessian_support): y = egb_nlp.get_duals() self.assertTrue(np.array_equal(y, np.asarray([21, 5], dtype=np.float64))) - with self.assertRaises(NotImplementedError): - fac = egb_nlp.get_obj_factor() - with self.assertRaises(NotImplementedError): - egb_nlp.set_obj_factor(42) - with self.assertRaises(NotImplementedError): - egb_nlp.set_obj_factor(1) - with self.assertRaises(NotImplementedError): - f = egb_nlp.evaluate_objective() - with self.assertRaises(NotImplementedError): - gradf = egb_nlp.evaluate_grad_objective() - c = egb_nlp.evaluate_constraints() comparison_c = np.asarray([16, 6], dtype=np.float64) check_vectors_specific_order(self, c, c_order, comparison_c, comparison_c_order) @@ -813,17 +774,6 @@ def _test_pressure_drop_two_equalities_two_outputs(self, ex_model, hessian_suppo y = egb_nlp.get_duals() self.assertTrue(np.array_equal(y, np.asarray([21, 5, 6, 7], dtype=np.float64))) - with self.assertRaises(NotImplementedError): - fac = egb_nlp.get_obj_factor() - with self.assertRaises(NotImplementedError): - egb_nlp.set_obj_factor(42) - with self.assertRaises(NotImplementedError): - egb_nlp.set_obj_factor(1) - with self.assertRaises(NotImplementedError): - f = egb_nlp.evaluate_objective() - with self.assertRaises(NotImplementedError): - gradf = egb_nlp.evaluate_grad_objective() - c = egb_nlp.evaluate_constraints() comparison_c = np.asarray([-2, 26, -13, -22], dtype=np.float64) check_vectors_specific_order(self, c, c_order, comparison_c, comparison_c_order) @@ -2640,5 +2590,19 @@ def test_duals_after_solve(self): self.assertAlmostEqual(m.dual[m.egb]['egb.u2_con'], 62.5, places=3) +class TestGreyBoxObjectives(unittest.TestCase): + @unittest.skipIf(not cyipopt_available, "CyIpopt needed to run tests with solve") + def test_unconstrained(self): + solve_unconstrained() + + @unittest.skipIf(not cyipopt_available, "CyIpopt needed to run tests with solve") + def test_constrained(self): + solve_constrained() + + @unittest.skipIf(not cyipopt_available, "CyIpopt needed to run tests with solve") + def test_constrained_with_hessian(self): + solve_constrained_with_hessian() + + if __name__ == '__main__': unittest.main() From c08a188ab87c331c984e6e26a29a6715b9bd1305 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Sun, 8 Sep 2024 12:50:11 -0600 Subject: [PATCH 3/4] fix typo --- pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py b/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py index 3ec7856a612..82cc7be4cfc 100644 --- a/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py +++ b/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py @@ -817,7 +817,7 @@ def _evaluate_hessian_if_necessary_and_cache(self): if obj_hess.shape != expected_shape: raise ValueError( 'ExternalGreyBoxModel objective hessian shape' - 'shoule be (n_inputs, n_inputs)' + 'should be (n_inputs, n_inputs)' ) # let's check that it is lower triangular if np.any(obj_hess.row < obj_hess.col): @@ -835,7 +835,7 @@ def _evaluate_hessian_if_necessary_and_cache(self): if eq_hess.shape != expected_shape: raise ValueError( 'ExternalGreyBoxModel equality constraint hessian shape' - 'shoule be (n_inputs, n_inputs)' + 'should be (n_inputs, n_inputs)' ) # let's check that it is lower triangular if np.any(eq_hess.row < eq_hess.col): @@ -853,7 +853,7 @@ def _evaluate_hessian_if_necessary_and_cache(self): if output_hess.shape != expected_shape: raise ValueError( 'ExternalGreyBoxModel output hessian shape' - 'shoule be (n_inputs, n_inputs)' + 'should be (n_inputs, n_inputs)' ) # let's check that it is lower triangular if np.any(output_hess.row < output_hess.col): From 2f8bbd6546dfc3bc6f3b458048b7ef7a42763582 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Sun, 8 Sep 2024 13:35:35 -0600 Subject: [PATCH 4/4] fix typo --- examples/doc/samples/case_studies/diet/DietProblem.tex | 2 +- examples/doc/samples/case_studies/diet/README.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/doc/samples/case_studies/diet/DietProblem.tex b/examples/doc/samples/case_studies/diet/DietProblem.tex index d933e097d88..e2ae7ba4c62 100644 --- a/examples/doc/samples/case_studies/diet/DietProblem.tex +++ b/examples/doc/samples/case_studies/diet/DietProblem.tex @@ -54,7 +54,7 @@ \subsection*{Build the model} The comma indicates that this parameter is over two different sets, and thus is in two dimensions. When we create the data file, we will be able to fill in how much of each nutrient each food contains. -At this point we have defined our sets and parameters. However, we have yet to cosnider the amount of food to be bought and eaten. This is the variable weâre trying to solve for, and thus we create an object of the variable class. Since this is just recording how much food to purchase, we create a one dimensional variable over food: +At this point we have defined our sets and parameters. However, we have yet to consider the amount of food to be bought and eaten. This is the variable weâre trying to solve for, and thus we create an object of the variable class. Since this is just recording how much food to purchase, we create a one dimensional variable over food: \begin{verbatim}model.amount=Var(model.foods, within = NonNegativeReals) \end{verbatim} diff --git a/examples/doc/samples/case_studies/diet/README.txt b/examples/doc/samples/case_studies/diet/README.txt index c30e963dc27..c382b4d653c 100644 --- a/examples/doc/samples/case_studies/diet/README.txt +++ b/examples/doc/samples/case_studies/diet/README.txt @@ -68,7 +68,7 @@ model.nutrient_value=Param(model.nutrients, model.foods) The comma indicates that this parameter is over two different sets, and thus is in two dimensions. When we create the data file, we will be able to fill in how much of each nutrient each food contains. -At this point we have defined our sets and parameters. However, we have yet to cosnider the amount of food to be bought and eaten. This is the variable we're trying to solve for, and thus we create an object of the variable class. Since this is just recording how much food to purchase, we create a one dimensional variable over food: +At this point we have defined our sets and parameters. However, we have yet to consider the amount of food to be bought and eaten. This is the variable we're trying to solve for, and thus we create an object of the variable class. Since this is just recording how much food to purchase, we create a one dimensional variable over food: {{{ #!python