From 23d316e857b35e81d990114a918f215e5e3a556e Mon Sep 17 00:00:00 2001 From: Bernard Knueven Date: Tue, 18 Jun 2024 21:52:52 -0600 Subject: [PATCH 01/16] finding scaling_factors in PyomoNLP --- .../contrib/pynumero/interfaces/pyomo_nlp.py | 43 +++++++++---------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py b/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py index e12d0cf568b..a793419efc0 100644 --- a/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py +++ b/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py @@ -20,6 +20,7 @@ from pyomo.common.tempfiles import TempfileManager from pyomo.opt import WriterFactory import pyomo.core.base as pyo +from pyomo.core.base.suffix import SuffixFinder from pyomo.common.collections import ComponentMap from pyomo.common.env import CtypesEnviron from pyomo.solvers.amplfunc_merge import amplfunc_merge @@ -298,34 +299,32 @@ def get_inequality_constraint_indices(self, constraints): # overloaded from NLP def get_obj_scaling(self): obj = self.get_pyomo_objective() - scaling_suffix = self._pyomo_model.component('scaling_factor') - if scaling_suffix and scaling_suffix.ctype is pyo.Suffix: - if obj in scaling_suffix: - return scaling_suffix[obj] - return 1.0 - return None + val = SuffixFinder('scaling_factor').find(obj) + return val # overloaded from NLP def get_primals_scaling(self): - scaling_suffix = self._pyomo_model.component('scaling_factor') - if scaling_suffix and scaling_suffix.ctype is pyo.Suffix: - primals_scaling = np.ones(self.n_primals()) - for i, v in enumerate(self.get_pyomo_variables()): - if v in scaling_suffix: - primals_scaling[i] = scaling_suffix[v] - return primals_scaling - return None + scaling_suffix_finder = SuffixFinder('scaling_factor') + primals_scaling = np.ones(self.n_primals()) + ret = None + for i, v in enumerate(self.get_pyomo_variables()): + val = scaling_suffix_finder.find(v) + if val is not None: + primals_scaling[i] = val + ret = primals_scaling + return ret # overloaded from NLP def get_constraints_scaling(self): - scaling_suffix = self._pyomo_model.component('scaling_factor') - if scaling_suffix and scaling_suffix.ctype is pyo.Suffix: - constraints_scaling = np.ones(self.n_constraints()) - for i, c in enumerate(self.get_pyomo_constraints()): - if c in scaling_suffix: - constraints_scaling[i] = scaling_suffix[c] - return constraints_scaling - return None + scaling_suffix_finder = SuffixFinder('scaling_factor') + constraints_scaling = np.ones(self.n_constraints()) + ret = None + for i, c in enumerate(self.get_pyomo_constraints()): + val = scaling_suffix_finder.find(c) + if val is not None: + constraints_scaling[i] = val + ret = constraints_scaling + return ret def extract_subvector_grad_objective(self, pyomo_variables): """Compute the gradient of the objective and return the entries From a7791e18965e356f952483d09c419df420b01866 Mon Sep 17 00:00:00 2001 From: Bernard Knueven Date: Tue, 18 Jun 2024 22:13:35 -0600 Subject: [PATCH 02/16] always return a scaling factor --- .../contrib/pynumero/interfaces/pyomo_nlp.py | 22 ++++++------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py b/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py index a793419efc0..8759329389d 100644 --- a/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py +++ b/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py @@ -299,32 +299,24 @@ def get_inequality_constraint_indices(self, constraints): # overloaded from NLP def get_obj_scaling(self): obj = self.get_pyomo_objective() - val = SuffixFinder('scaling_factor').find(obj) + val = SuffixFinder('scaling_factor', 1.0).find(obj) return val # overloaded from NLP def get_primals_scaling(self): - scaling_suffix_finder = SuffixFinder('scaling_factor') + scaling_suffix_finder = SuffixFinder('scaling_factor', 1.0) primals_scaling = np.ones(self.n_primals()) - ret = None for i, v in enumerate(self.get_pyomo_variables()): - val = scaling_suffix_finder.find(v) - if val is not None: - primals_scaling[i] = val - ret = primals_scaling - return ret + primals_scaling[i] = scaling_suffix_finder.find(v) + return primals_scaling # overloaded from NLP def get_constraints_scaling(self): - scaling_suffix_finder = SuffixFinder('scaling_factor') + scaling_suffix_finder = SuffixFinder('scaling_factor', 1.0) constraints_scaling = np.ones(self.n_constraints()) - ret = None for i, c in enumerate(self.get_pyomo_constraints()): - val = scaling_suffix_finder.find(c) - if val is not None: - constraints_scaling[i] = val - ret = constraints_scaling - return ret + constraints_scaling[i] = scaling_suffix_finder.find(c) + return constraints_scaling def extract_subvector_grad_objective(self, pyomo_variables): """Compute the gradient of the objective and return the entries From e7bfad9be7b7e374560faa886f4cdca925c3cf72 Mon Sep 17 00:00:00 2001 From: Bernard Knueven Date: Thu, 20 Jun 2024 11:59:36 -0600 Subject: [PATCH 03/16] fix tests --- .../algorithms/solvers/tests/test_cyipopt_interfaces.py | 6 +++--- .../interfaces/tests/test_external_grey_box_model.py | 8 ++++---- .../pynumero/interfaces/tests/test_pyomo_grey_box_nlp.py | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pyomo/contrib/pynumero/algorithms/solvers/tests/test_cyipopt_interfaces.py b/pyomo/contrib/pynumero/algorithms/solvers/tests/test_cyipopt_interfaces.py index 88d4df1e17d..9d2c961a69d 100644 --- a/pyomo/contrib/pynumero/algorithms/solvers/tests/test_cyipopt_interfaces.py +++ b/pyomo/contrib/pynumero/algorithms/solvers/tests/test_cyipopt_interfaces.py @@ -156,9 +156,9 @@ def test_model1_CyIpoptNLP_scaling(self): cynlp = CyIpoptNLP(PyomoNLP(m)) obj_scaling, x_scaling, g_scaling = cynlp.scaling_factors() - self.assertTrue(obj_scaling is None) - self.assertTrue(x_scaling is None) - self.assertTrue(g_scaling is None) + self.assertTrue(obj_scaling == 1.0) + self.assertTrue((x_scaling == 1.0).all()) + self.assertTrue((g_scaling == 1.0).all()) def _check_model1(self, nlp, cynlp): # test x_init diff --git a/pyomo/contrib/pynumero/interfaces/tests/test_external_grey_box_model.py b/pyomo/contrib/pynumero/interfaces/tests/test_external_grey_box_model.py index 0fc342c4e40..188aba30eeb 100644 --- a/pyomo/contrib/pynumero/interfaces/tests/test_external_grey_box_model.py +++ b/pyomo/contrib/pynumero/interfaces/tests/test_external_grey_box_model.py @@ -1827,7 +1827,7 @@ def create_model_two_equalities_two_outputs(self, external_model): m.egb.outputs['Pout'].setub(70) return m - def test_scaling_all_missing(self): + def test_scaling_all_one(self): m = self.create_model_two_equalities_two_outputs( ex_models.PressureDropTwoEqualitiesTwoOutputs() ) @@ -1836,9 +1836,9 @@ def test_scaling_all_missing(self): fs = pyomo_nlp.get_obj_scaling() xs = pyomo_nlp.get_primals_scaling() cs = pyomo_nlp.get_constraints_scaling() - self.assertIsNone(fs) - self.assertIsNone(xs) - self.assertIsNone(cs) + self.assertEqual(fs, 1.0) + self.assertTrue((xs == 1.0).all()) + self.assertTrue((cs == 1.0).all()) def test_scaling_pyomo_model_only(self): m = self.create_model_two_equalities_two_outputs( 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..1dcdc2a6237 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 @@ -2248,7 +2248,7 @@ def create_model_two_equalities_two_outputs(self, external_model): m.egb.outputs['Pout'].setub(70) return m - def test_scaling_all_missing(self): + def test_scaling_all_one(self): m = self.create_model_two_equalities_two_outputs( ex_models.PressureDropTwoEqualitiesTwoOutputs() ) @@ -2257,9 +2257,9 @@ def test_scaling_all_missing(self): fs = pyomo_nlp.get_obj_scaling() xs = pyomo_nlp.get_primals_scaling() cs = pyomo_nlp.get_constraints_scaling() - self.assertIsNone(fs) - self.assertIsNone(xs) - self.assertIsNone(cs) + self.assertEqual(fs, 1.0) + self.assertTrue((xs == 1.0).all()) + self.assertTrue((cs == 1.0).all()) def test_scaling_pyomo_model_only(self): m = self.create_model_two_equalities_two_outputs( From 8215ef8901362af059b82496d2f6ea2ad37e6256 Mon Sep 17 00:00:00 2001 From: Bernard Knueven Date: Thu, 20 Jun 2024 15:29:40 -0600 Subject: [PATCH 04/16] adding SuffixFinder to PyomoGreyBoxNLP --- pyomo/contrib/pynumero/interfaces/pyomo_nlp.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py b/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py index 8759329389d..f81299bb372 100644 --- a/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py +++ b/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py @@ -598,12 +598,12 @@ def __init__(self, pyomo_model): need_scaling = True self._primals_scaling = np.ones(self.n_primals()) - scaling_suffix = self._pyomo_nlp._pyomo_model.component('scaling_factor') - if scaling_suffix and scaling_suffix.ctype is pyo.Suffix: - need_scaling = True - for i, v in enumerate(self.get_pyomo_variables()): - if v in scaling_suffix: - self._primals_scaling[i] = scaling_suffix[v] + scaling_suffix_finder = SuffixFinder('scaling_factor') + for i, v in enumerate(self.get_pyomo_variables()): + v_scaling = scaling_suffix_finder.find(v) + if v_scaling is not None: + need_scaling = True + self._primals_scaling[i] = v_scaling self._constraints_scaling = [] pyomo_nlp_scaling = self._pyomo_nlp.get_constraints_scaling() From c2e00f205d07402c302b866b5c51dbca9779ce55 Mon Sep 17 00:00:00 2001 From: Bernard Knueven Date: Thu, 20 Jun 2024 20:33:45 -0600 Subject: [PATCH 05/16] remove hack from implicity functions solver --- .../pynumero/algorithms/solvers/implicit_functions.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pyomo/contrib/pynumero/algorithms/solvers/implicit_functions.py b/pyomo/contrib/pynumero/algorithms/solvers/implicit_functions.py index e40580c1161..f478d316e2b 100644 --- a/pyomo/contrib/pynumero/algorithms/solvers/implicit_functions.py +++ b/pyomo/contrib/pynumero/algorithms/solvers/implicit_functions.py @@ -403,11 +403,6 @@ def __init__( # Need a dummy objective to create an NLP for block, inputs in self._solver_subsystem_list: block._obj = Objective(expr=0.0) - # I need scaling_factor so Pyomo NLPs I create from these blocks - # don't break when ProjectedNLP calls get_primals_scaling - block.scaling_factor = Suffix(direction=Suffix.EXPORT) - # HACK: scaling_factor just needs to be nonempty - block.scaling_factor[block._obj] = 1.0 # Original PyomoNLP for each subset in the partition # Since we are creating these NLPs with "constants" fixed, these From c7b1bca4b5f1a623cbf7df0c1ddb0f74834ef487 Mon Sep 17 00:00:00 2001 From: Bernard Knueven Date: Thu, 20 Jun 2024 20:42:04 -0600 Subject: [PATCH 06/16] adding SuffixFinder to PyomoNLPWithGreyBoxBlocks --- .../pynumero/interfaces/pyomo_grey_box_nlp.py | 13 +++++++------ pyomo/contrib/pynumero/interfaces/pyomo_nlp.py | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py b/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py index e6ed40e9974..8f122eff08d 100644 --- a/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py +++ b/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py @@ -31,6 +31,7 @@ ) from pyomo.contrib.pynumero.interfaces.external_grey_box import ExternalGreyBoxBlock from pyomo.contrib.pynumero.interfaces.nlp_projections import ProjectedNLP +from pyomo.core.base.suffix import SuffixFinder # Todo: make some of the numpy arrays not writable from __init__ @@ -227,12 +228,12 @@ def __init__(self, pyomo_model): need_scaling = True self._primals_scaling = np.ones(self.n_primals()) - scaling_suffix = pyomo_model.component('scaling_factor') - if scaling_suffix and scaling_suffix.ctype is pyo.Suffix: - need_scaling = True - for i, v in enumerate(self._pyomo_model_var_datas): - if v in scaling_suffix: - self._primals_scaling[i] = scaling_suffix[v] + scaling_suffix_finder = SuffixFinder('scaling_factor') + for i, v in enumerate(self._pyomo_model_var_datas): + v_scaling = scaling_suffix_finder.find(v) + if v_scaling is not None: + need_scaling = True + self._primals_scaling[i] = v_scaling self._constraints_scaling = BlockVector(len(nlps)) for i, nlp in enumerate(nlps): diff --git a/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py b/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py index f81299bb372..eebf7099b4f 100644 --- a/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py +++ b/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py @@ -20,13 +20,13 @@ from pyomo.common.tempfiles import TempfileManager from pyomo.opt import WriterFactory import pyomo.core.base as pyo -from pyomo.core.base.suffix import SuffixFinder from pyomo.common.collections import ComponentMap from pyomo.common.env import CtypesEnviron from pyomo.solvers.amplfunc_merge import amplfunc_merge from ..sparse.block_matrix import BlockMatrix from pyomo.contrib.pynumero.interfaces.ampl_nlp import AslNLP from pyomo.contrib.pynumero.interfaces.nlp import NLP +from pyomo.core.base.suffix import SuffixFinder from .external_grey_box import ExternalGreyBoxBlock From 0b4d16b6d8bc334949481ff69f8e7370d132a1b6 Mon Sep 17 00:00:00 2001 From: Bernard Knueven Date: Thu, 20 Jun 2024 20:51:10 -0600 Subject: [PATCH 07/16] modify tests so the fail on main --- .../tests/test_external_grey_box_model.py | 45 +++++++------- .../tests/test_pyomo_grey_box_nlp.py | 60 ++++++++++--------- 2 files changed, 56 insertions(+), 49 deletions(-) diff --git a/pyomo/contrib/pynumero/interfaces/tests/test_external_grey_box_model.py b/pyomo/contrib/pynumero/interfaces/tests/test_external_grey_box_model.py index 188aba30eeb..f0a0dc545af 100644 --- a/pyomo/contrib/pynumero/interfaces/tests/test_external_grey_box_model.py +++ b/pyomo/contrib/pynumero/interfaces/tests/test_external_grey_box_model.py @@ -1846,14 +1846,15 @@ def test_scaling_pyomo_model_only(self): ) m.obj = pyo.Objective(expr=(m.egb.outputs['Pout'] - 20) ** 2) m.scaling_factor = pyo.Suffix(direction=pyo.Suffix.EXPORT) + m.egb.scaling_factor = pyo.Suffix(direction=pyo.Suffix.EXPORT) # m.scaling_factor[m.obj] = 0.1 # scale the objective - m.scaling_factor[m.egb.inputs['Pin']] = 1.1 # scale the variable - m.scaling_factor[m.egb.inputs['c']] = 1.2 # scale the variable - m.scaling_factor[m.egb.inputs['F']] = 1.3 # scale the variable - # m.scaling_factor[m.egb.inputs['P1']] = 1.4 # scale the variable - m.scaling_factor[m.egb.inputs['P3']] = 1.5 # scale the variable - m.scaling_factor[m.egb.outputs['P2']] = 1.6 # scale the variable - m.scaling_factor[m.egb.outputs['Pout']] = 1.7 # scale the variable + m.egb.scaling_factor[m.egb.inputs['Pin']] = 1.1 # scale the variable + m.egb.scaling_factor[m.egb.inputs['c']] = 1.2 # scale the variable + m.egb.scaling_factor[m.egb.inputs['F']] = 1.3 # scale the variable + # m.egb.scaling_factor[m.egb.inputs['P1']] = 1.4 # scale the variable + m.egb.scaling_factor[m.egb.inputs['P3']] = 1.5 # scale the variable + m.egb.scaling_factor[m.egb.outputs['P2']] = 1.6 # scale the variable + m.egb.scaling_factor[m.egb.outputs['Pout']] = 1.7 # scale the variable # m.scaling_factor[m.hin] = 1.8 m.scaling_factor[m.hout] = 1.9 # m.scaling_factor[m.incon] = 2.1 @@ -1971,14 +1972,15 @@ def test_scaling_pyomo_model_and_greybox(self): ) m.obj = pyo.Objective(expr=(m.egb.outputs['Pout'] - 20) ** 2) m.scaling_factor = pyo.Suffix(direction=pyo.Suffix.EXPORT) + m.egb.scaling_factor = pyo.Suffix(direction=pyo.Suffix.EXPORT) # m.scaling_factor[m.obj] = 0.1 # scale the objective - m.scaling_factor[m.egb.inputs['Pin']] = 1.1 # scale the variable - m.scaling_factor[m.egb.inputs['c']] = 1.2 # scale the variable - m.scaling_factor[m.egb.inputs['F']] = 1.3 # scale the variable - # m.scaling_factor[m.egb.inputs['P1']] = 1.4 # scale the variable - m.scaling_factor[m.egb.inputs['P3']] = 1.5 # scale the variable - m.scaling_factor[m.egb.outputs['P2']] = 1.6 # scale the variable - m.scaling_factor[m.egb.outputs['Pout']] = 1.7 # scale the variable + m.egb.scaling_factor[m.egb.inputs['Pin']] = 1.1 # scale the variable + m.egb.scaling_factor[m.egb.inputs['c']] = 1.2 # scale the variable + m.egb.scaling_factor[m.egb.inputs['F']] = 1.3 # scale the variable + # m.egb.scaling_factor[m.egb.inputs['P1']] = 1.4 # scale the variable + m.egb.scaling_factor[m.egb.inputs['P3']] = 1.5 # scale the variable + m.egb.scaling_factor[m.egb.outputs['P2']] = 1.6 # scale the variable + m.egb.scaling_factor[m.egb.outputs['Pout']] = 1.7 # scale the variable # m.scaling_factor[m.hin] = 1.8 m.scaling_factor[m.hout] = 1.9 # m.scaling_factor[m.incon] = 2.1 @@ -2063,14 +2065,15 @@ def test_external_greybox_solve_scaling(self): ) m.scaling_factor = pyo.Suffix(direction=pyo.Suffix.EXPORT) + m.egb.scaling_factor = pyo.Suffix(direction=pyo.Suffix.EXPORT) m.scaling_factor[m.obj] = 0.1 # scale the objective - m.scaling_factor[m.egb.inputs['Pin']] = 1.1 # scale the variable - m.scaling_factor[m.egb.inputs['c']] = 1.2 # scale the variable - m.scaling_factor[m.egb.inputs['F']] = 1.3 # scale the variable - # m.scaling_factor[m.egb.inputs['P1']] = 1.4 # scale the variable - m.scaling_factor[m.egb.inputs['P3']] = 1.5 # scale the variable - m.scaling_factor[m.egb.outputs['P2']] = 1.6 # scale the variable - m.scaling_factor[m.egb.outputs['Pout']] = 1.7 # scale the variable + m.egb.scaling_factor[m.egb.inputs['Pin']] = 1.1 # scale the variable + m.egb.scaling_factor[m.egb.inputs['c']] = 1.2 # scale the variable + m.egb.scaling_factor[m.egb.inputs['F']] = 1.3 # scale the variable + # m.egb.scaling_factor[m.egb.inputs['P1']] = 1.4 # scale the variable + m.egb.scaling_factor[m.egb.inputs['P3']] = 1.5 # scale the variable + m.egb.scaling_factor[m.egb.outputs['P2']] = 1.6 # scale the variable + m.egb.scaling_factor[m.egb.outputs['Pout']] = 1.7 # scale the variable m.scaling_factor[m.mu] = 1.9 m.scaling_factor[m.pincon] = 2.2 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 1dcdc2a6237..e4842652278 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 @@ -956,14 +956,15 @@ def test_scaling_pyomo_model_only(self): ) m.obj = pyo.Objective(expr=(m.egb.outputs['Pout'] - 20) ** 2) m.scaling_factor = pyo.Suffix(direction=pyo.Suffix.EXPORT) + m.egb.scaling_factor = pyo.Suffix(direction=pyo.Suffix.EXPORT) # m.scaling_factor[m.obj] = 0.1 # scale the objective - m.scaling_factor[m.egb.inputs['Pin']] = 1.1 # scale the variable - m.scaling_factor[m.egb.inputs['c']] = 1.2 # scale the variable - m.scaling_factor[m.egb.inputs['F']] = 1.3 # scale the variable - # m.scaling_factor[m.egb.inputs['P1']] = 1.4 # scale the variable - m.scaling_factor[m.egb.inputs['P3']] = 1.5 # scale the variable - m.scaling_factor[m.egb.outputs['P2']] = 1.6 # scale the variable - m.scaling_factor[m.egb.outputs['Pout']] = 1.7 # scale the variable + m.egb.scaling_factor[m.egb.inputs['Pin']] = 1.1 # scale the variable + m.egb.scaling_factor[m.egb.inputs['c']] = 1.2 # scale the variable + m.egb.scaling_factor[m.egb.inputs['F']] = 1.3 # scale the variable + # m.egb.scaling_factor[m.egb.inputs['P1']] = 1.4 # scale the variable + m.egb.scaling_factor[m.egb.inputs['P3']] = 1.5 # scale the variable + m.egb.scaling_factor[m.egb.outputs['P2']] = 1.6 # scale the variable + m.egb.scaling_factor[m.egb.outputs['Pout']] = 1.7 # scale the variable # m.scaling_factor[m.hin] = 1.8 m.scaling_factor[m.hout] = 1.9 # m.scaling_factor[m.incon] = 2.1 @@ -2267,14 +2268,15 @@ def test_scaling_pyomo_model_only(self): ) m.obj = pyo.Objective(expr=(m.egb.outputs['Pout'] - 20) ** 2) m.scaling_factor = pyo.Suffix(direction=pyo.Suffix.EXPORT) + m.egb.scaling_factor = pyo.Suffix(direction=pyo.Suffix.EXPORT) # m.scaling_factor[m.obj] = 0.1 # scale the objective - m.scaling_factor[m.egb.inputs['Pin']] = 1.1 # scale the variable - m.scaling_factor[m.egb.inputs['c']] = 1.2 # scale the variable - m.scaling_factor[m.egb.inputs['F']] = 1.3 # scale the variable - # m.scaling_factor[m.egb.inputs['P1']] = 1.4 # scale the variable - m.scaling_factor[m.egb.inputs['P3']] = 1.5 # scale the variable - m.scaling_factor[m.egb.outputs['P2']] = 1.6 # scale the variable - m.scaling_factor[m.egb.outputs['Pout']] = 1.7 # scale the variable + m.egb.scaling_factor[m.egb.inputs['Pin']] = 1.1 # scale the variable + m.egb.scaling_factor[m.egb.inputs['c']] = 1.2 # scale the variable + m.egb.scaling_factor[m.egb.inputs['F']] = 1.3 # scale the variable + # m.egb.scaling_factor[m.egb.inputs['P1']] = 1.4 # scale the variable + m.egb.scaling_factor[m.egb.inputs['P3']] = 1.5 # scale the variable + m.egb.scaling_factor[m.egb.outputs['P2']] = 1.6 # scale the variable + m.egb.scaling_factor[m.egb.outputs['Pout']] = 1.7 # scale the variable # m.scaling_factor[m.hin] = 1.8 m.scaling_factor[m.hout] = 1.9 # m.scaling_factor[m.incon] = 2.1 @@ -2392,14 +2394,15 @@ def test_scaling_pyomo_model_and_greybox(self): ) m.obj = pyo.Objective(expr=(m.egb.outputs['Pout'] - 20) ** 2) m.scaling_factor = pyo.Suffix(direction=pyo.Suffix.EXPORT) + m.egb.scaling_factor = pyo.Suffix(direction=pyo.Suffix.EXPORT) # m.scaling_factor[m.obj] = 0.1 # scale the objective - m.scaling_factor[m.egb.inputs['Pin']] = 1.1 # scale the variable - m.scaling_factor[m.egb.inputs['c']] = 1.2 # scale the variable - m.scaling_factor[m.egb.inputs['F']] = 1.3 # scale the variable - # m.scaling_factor[m.egb.inputs['P1']] = 1.4 # scale the variable - m.scaling_factor[m.egb.inputs['P3']] = 1.5 # scale the variable - m.scaling_factor[m.egb.outputs['P2']] = 1.6 # scale the variable - m.scaling_factor[m.egb.outputs['Pout']] = 1.7 # scale the variable + m.egb.scaling_factor[m.egb.inputs['Pin']] = 1.1 # scale the variable + m.egb.scaling_factor[m.egb.inputs['c']] = 1.2 # scale the variable + m.egb.scaling_factor[m.egb.inputs['F']] = 1.3 # scale the variable + # m.egb.scaling_factor[m.egb.inputs['P1']] = 1.4 # scale the variable + m.egb.scaling_factor[m.egb.inputs['P3']] = 1.5 # scale the variable + m.egb.scaling_factor[m.egb.outputs['P2']] = 1.6 # scale the variable + m.egb.scaling_factor[m.egb.outputs['Pout']] = 1.7 # scale the variable # m.scaling_factor[m.hin] = 1.8 m.scaling_factor[m.hout] = 1.9 # m.scaling_factor[m.incon] = 2.1 @@ -2484,14 +2487,15 @@ def test_external_greybox_solve_scaling(self): ) m.scaling_factor = pyo.Suffix(direction=pyo.Suffix.EXPORT) + m.egb.scaling_factor = pyo.Suffix(direction=pyo.Suffix.EXPORT) m.scaling_factor[m.obj] = 0.1 # scale the objective - m.scaling_factor[m.egb.inputs['Pin']] = 1.1 # scale the variable - m.scaling_factor[m.egb.inputs['c']] = 1.2 # scale the variable - m.scaling_factor[m.egb.inputs['F']] = 1.3 # scale the variable - # m.scaling_factor[m.egb.inputs['P1']] = 1.4 # scale the variable - m.scaling_factor[m.egb.inputs['P3']] = 1.5 # scale the variable - m.scaling_factor[m.egb.outputs['P2']] = 1.6 # scale the variable - m.scaling_factor[m.egb.outputs['Pout']] = 1.7 # scale the variable + m.egb.scaling_factor[m.egb.inputs['Pin']] = 1.1 # scale the variable + m.egb.scaling_factor[m.egb.inputs['c']] = 1.2 # scale the variable + m.egb.scaling_factor[m.egb.inputs['F']] = 1.3 # scale the variable + # m.egb.scaling_factor[m.egb.inputs['P1']] = 1.4 # scale the variable + m.egb.scaling_factor[m.egb.inputs['P3']] = 1.5 # scale the variable + m.egb.scaling_factor[m.egb.outputs['P2']] = 1.6 # scale the variable + m.egb.scaling_factor[m.egb.outputs['Pout']] = 1.7 # scale the variable m.scaling_factor[m.mu] = 1.9 m.scaling_factor[m.pincon] = 2.2 From 1733e35cb25c0763435b5ea3e6c74052b18801a1 Mon Sep 17 00:00:00 2001 From: Bernard Knueven Date: Thu, 27 Jun 2024 12:35:56 -0600 Subject: [PATCH 08/16] Revert "remove hack from implicity functions solver" This reverts commit c2e00f205d07402c302b866b5c51dbca9779ce55. --- .../pynumero/algorithms/solvers/implicit_functions.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pyomo/contrib/pynumero/algorithms/solvers/implicit_functions.py b/pyomo/contrib/pynumero/algorithms/solvers/implicit_functions.py index f478d316e2b..e40580c1161 100644 --- a/pyomo/contrib/pynumero/algorithms/solvers/implicit_functions.py +++ b/pyomo/contrib/pynumero/algorithms/solvers/implicit_functions.py @@ -403,6 +403,11 @@ def __init__( # Need a dummy objective to create an NLP for block, inputs in self._solver_subsystem_list: block._obj = Objective(expr=0.0) + # I need scaling_factor so Pyomo NLPs I create from these blocks + # don't break when ProjectedNLP calls get_primals_scaling + block.scaling_factor = Suffix(direction=Suffix.EXPORT) + # HACK: scaling_factor just needs to be nonempty + block.scaling_factor[block._obj] = 1.0 # Original PyomoNLP for each subset in the partition # Since we are creating these NLPs with "constants" fixed, these From 50df842bf32c42ced9647bf88690f71c1363797c Mon Sep 17 00:00:00 2001 From: Bernard Knueven Date: Thu, 27 Jun 2024 12:36:06 -0600 Subject: [PATCH 09/16] Revert "fix tests" This reverts commit e7bfad9be7b7e374560faa886f4cdca925c3cf72. --- .../algorithms/solvers/tests/test_cyipopt_interfaces.py | 6 +++--- .../interfaces/tests/test_external_grey_box_model.py | 8 ++++---- .../pynumero/interfaces/tests/test_pyomo_grey_box_nlp.py | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pyomo/contrib/pynumero/algorithms/solvers/tests/test_cyipopt_interfaces.py b/pyomo/contrib/pynumero/algorithms/solvers/tests/test_cyipopt_interfaces.py index 9d2c961a69d..88d4df1e17d 100644 --- a/pyomo/contrib/pynumero/algorithms/solvers/tests/test_cyipopt_interfaces.py +++ b/pyomo/contrib/pynumero/algorithms/solvers/tests/test_cyipopt_interfaces.py @@ -156,9 +156,9 @@ def test_model1_CyIpoptNLP_scaling(self): cynlp = CyIpoptNLP(PyomoNLP(m)) obj_scaling, x_scaling, g_scaling = cynlp.scaling_factors() - self.assertTrue(obj_scaling == 1.0) - self.assertTrue((x_scaling == 1.0).all()) - self.assertTrue((g_scaling == 1.0).all()) + self.assertTrue(obj_scaling is None) + self.assertTrue(x_scaling is None) + self.assertTrue(g_scaling is None) def _check_model1(self, nlp, cynlp): # test x_init diff --git a/pyomo/contrib/pynumero/interfaces/tests/test_external_grey_box_model.py b/pyomo/contrib/pynumero/interfaces/tests/test_external_grey_box_model.py index f0a0dc545af..e592b8ff691 100644 --- a/pyomo/contrib/pynumero/interfaces/tests/test_external_grey_box_model.py +++ b/pyomo/contrib/pynumero/interfaces/tests/test_external_grey_box_model.py @@ -1827,7 +1827,7 @@ def create_model_two_equalities_two_outputs(self, external_model): m.egb.outputs['Pout'].setub(70) return m - def test_scaling_all_one(self): + def test_scaling_all_missing(self): m = self.create_model_two_equalities_two_outputs( ex_models.PressureDropTwoEqualitiesTwoOutputs() ) @@ -1836,9 +1836,9 @@ def test_scaling_all_one(self): fs = pyomo_nlp.get_obj_scaling() xs = pyomo_nlp.get_primals_scaling() cs = pyomo_nlp.get_constraints_scaling() - self.assertEqual(fs, 1.0) - self.assertTrue((xs == 1.0).all()) - self.assertTrue((cs == 1.0).all()) + self.assertIsNone(fs) + self.assertIsNone(xs) + self.assertIsNone(cs) def test_scaling_pyomo_model_only(self): m = self.create_model_two_equalities_two_outputs( 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 e4842652278..39cfdf16146 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 @@ -2249,7 +2249,7 @@ def create_model_two_equalities_two_outputs(self, external_model): m.egb.outputs['Pout'].setub(70) return m - def test_scaling_all_one(self): + def test_scaling_all_missing(self): m = self.create_model_two_equalities_two_outputs( ex_models.PressureDropTwoEqualitiesTwoOutputs() ) @@ -2258,9 +2258,9 @@ def test_scaling_all_one(self): fs = pyomo_nlp.get_obj_scaling() xs = pyomo_nlp.get_primals_scaling() cs = pyomo_nlp.get_constraints_scaling() - self.assertEqual(fs, 1.0) - self.assertTrue((xs == 1.0).all()) - self.assertTrue((cs == 1.0).all()) + self.assertIsNone(fs) + self.assertIsNone(xs) + self.assertIsNone(cs) def test_scaling_pyomo_model_only(self): m = self.create_model_two_equalities_two_outputs( From 44da06824e479404e9850bde6ff16bd70490e68b Mon Sep 17 00:00:00 2001 From: Bernard Knueven Date: Thu, 27 Jun 2024 12:36:13 -0600 Subject: [PATCH 10/16] Revert "always return a scaling factor" This reverts commit a7791e18965e356f952483d09c419df420b01866. --- .../contrib/pynumero/interfaces/pyomo_nlp.py | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py b/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py index eebf7099b4f..1416bf0f8b8 100644 --- a/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py +++ b/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py @@ -299,24 +299,32 @@ def get_inequality_constraint_indices(self, constraints): # overloaded from NLP def get_obj_scaling(self): obj = self.get_pyomo_objective() - val = SuffixFinder('scaling_factor', 1.0).find(obj) + val = SuffixFinder('scaling_factor').find(obj) return val # overloaded from NLP def get_primals_scaling(self): - scaling_suffix_finder = SuffixFinder('scaling_factor', 1.0) + scaling_suffix_finder = SuffixFinder('scaling_factor') primals_scaling = np.ones(self.n_primals()) + ret = None for i, v in enumerate(self.get_pyomo_variables()): - primals_scaling[i] = scaling_suffix_finder.find(v) - return primals_scaling + val = scaling_suffix_finder.find(v) + if val is not None: + primals_scaling[i] = val + ret = primals_scaling + return ret # overloaded from NLP def get_constraints_scaling(self): - scaling_suffix_finder = SuffixFinder('scaling_factor', 1.0) + scaling_suffix_finder = SuffixFinder('scaling_factor') constraints_scaling = np.ones(self.n_constraints()) + ret = None for i, c in enumerate(self.get_pyomo_constraints()): - constraints_scaling[i] = scaling_suffix_finder.find(c) - return constraints_scaling + val = scaling_suffix_finder.find(c) + if val is not None: + constraints_scaling[i] = val + ret = constraints_scaling + return ret def extract_subvector_grad_objective(self, pyomo_variables): """Compute the gradient of the objective and return the entries From 9ca392825ad90696d06440845f74ff33392c4f4a Mon Sep 17 00:00:00 2001 From: Bernard Knueven Date: Thu, 27 Jun 2024 12:35:35 -0600 Subject: [PATCH 11/16] maintain backwards compatibility --- .../pynumero/interfaces/pyomo_grey_box_nlp.py | 4 +++ .../contrib/pynumero/interfaces/pyomo_nlp.py | 25 ++++++++++++++++--- 2 files changed, 26 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 8f122eff08d..3cc23260f56 100644 --- a/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py +++ b/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py @@ -234,6 +234,10 @@ def __init__(self, pyomo_model): if v_scaling is not None: need_scaling = True self._primals_scaling[i] = v_scaling + # maintain backwards compatibility + scaling_suffix = self._pyomo_model.component('scaling_factor') + if scaling_suffix and scaling_suffix.ctype is pyo.Suffix: + need_scaling = True self._constraints_scaling = BlockVector(len(nlps)) for i, nlp in enumerate(nlps): diff --git a/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py b/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py index 1416bf0f8b8..4b955b0176a 100644 --- a/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py +++ b/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py @@ -300,7 +300,12 @@ def get_inequality_constraint_indices(self, constraints): def get_obj_scaling(self): obj = self.get_pyomo_objective() val = SuffixFinder('scaling_factor').find(obj) - return val + # maintain backwards compatibility + scaling_suffix = self._pyomo_model.component('scaling_factor') + if scaling_suffix and scaling_suffix.ctype is pyo.Suffix: + return 1.0 if val is None else val + else: + return val # overloaded from NLP def get_primals_scaling(self): @@ -312,7 +317,12 @@ def get_primals_scaling(self): if val is not None: primals_scaling[i] = val ret = primals_scaling - return ret + # maintain backwards compatibility + scaling_suffix = self._pyomo_model.component('scaling_factor') + if scaling_suffix and scaling_suffix.ctype is pyo.Suffix: + return primals_scaling + else: + return ret # overloaded from NLP def get_constraints_scaling(self): @@ -324,7 +334,12 @@ def get_constraints_scaling(self): if val is not None: constraints_scaling[i] = val ret = constraints_scaling - return ret + # maintain backwards compatibility + scaling_suffix = self._pyomo_model.component('scaling_factor') + if scaling_suffix and scaling_suffix.ctype is pyo.Suffix: + return constraints_scaling + else: + return ret def extract_subvector_grad_objective(self, pyomo_variables): """Compute the gradient of the objective and return the entries @@ -612,6 +627,10 @@ def __init__(self, pyomo_model): if v_scaling is not None: need_scaling = True self._primals_scaling[i] = v_scaling + # maintain backwards compatibility + scaling_suffix = self._pyomo_model.component('scaling_factor') + if scaling_suffix and scaling_suffix.ctype is pyo.Suffix: + need_scaling = True self._constraints_scaling = [] pyomo_nlp_scaling = self._pyomo_nlp.get_constraints_scaling() From 29e0395e97afcdd347fe33c10ce133298eb051a6 Mon Sep 17 00:00:00 2001 From: Bernard Knueven Date: Thu, 27 Jun 2024 12:54:37 -0600 Subject: [PATCH 12/16] Revert "modify tests so the fail on main" This reverts commit 0b4d16b6d8bc334949481ff69f8e7370d132a1b6. --- .../tests/test_external_grey_box_model.py | 45 +++++++------- .../tests/test_pyomo_grey_box_nlp.py | 60 +++++++++---------- 2 files changed, 49 insertions(+), 56 deletions(-) diff --git a/pyomo/contrib/pynumero/interfaces/tests/test_external_grey_box_model.py b/pyomo/contrib/pynumero/interfaces/tests/test_external_grey_box_model.py index e592b8ff691..0fc342c4e40 100644 --- a/pyomo/contrib/pynumero/interfaces/tests/test_external_grey_box_model.py +++ b/pyomo/contrib/pynumero/interfaces/tests/test_external_grey_box_model.py @@ -1846,15 +1846,14 @@ def test_scaling_pyomo_model_only(self): ) m.obj = pyo.Objective(expr=(m.egb.outputs['Pout'] - 20) ** 2) m.scaling_factor = pyo.Suffix(direction=pyo.Suffix.EXPORT) - m.egb.scaling_factor = pyo.Suffix(direction=pyo.Suffix.EXPORT) # m.scaling_factor[m.obj] = 0.1 # scale the objective - m.egb.scaling_factor[m.egb.inputs['Pin']] = 1.1 # scale the variable - m.egb.scaling_factor[m.egb.inputs['c']] = 1.2 # scale the variable - m.egb.scaling_factor[m.egb.inputs['F']] = 1.3 # scale the variable - # m.egb.scaling_factor[m.egb.inputs['P1']] = 1.4 # scale the variable - m.egb.scaling_factor[m.egb.inputs['P3']] = 1.5 # scale the variable - m.egb.scaling_factor[m.egb.outputs['P2']] = 1.6 # scale the variable - m.egb.scaling_factor[m.egb.outputs['Pout']] = 1.7 # scale the variable + m.scaling_factor[m.egb.inputs['Pin']] = 1.1 # scale the variable + m.scaling_factor[m.egb.inputs['c']] = 1.2 # scale the variable + m.scaling_factor[m.egb.inputs['F']] = 1.3 # scale the variable + # m.scaling_factor[m.egb.inputs['P1']] = 1.4 # scale the variable + m.scaling_factor[m.egb.inputs['P3']] = 1.5 # scale the variable + m.scaling_factor[m.egb.outputs['P2']] = 1.6 # scale the variable + m.scaling_factor[m.egb.outputs['Pout']] = 1.7 # scale the variable # m.scaling_factor[m.hin] = 1.8 m.scaling_factor[m.hout] = 1.9 # m.scaling_factor[m.incon] = 2.1 @@ -1972,15 +1971,14 @@ def test_scaling_pyomo_model_and_greybox(self): ) m.obj = pyo.Objective(expr=(m.egb.outputs['Pout'] - 20) ** 2) m.scaling_factor = pyo.Suffix(direction=pyo.Suffix.EXPORT) - m.egb.scaling_factor = pyo.Suffix(direction=pyo.Suffix.EXPORT) # m.scaling_factor[m.obj] = 0.1 # scale the objective - m.egb.scaling_factor[m.egb.inputs['Pin']] = 1.1 # scale the variable - m.egb.scaling_factor[m.egb.inputs['c']] = 1.2 # scale the variable - m.egb.scaling_factor[m.egb.inputs['F']] = 1.3 # scale the variable - # m.egb.scaling_factor[m.egb.inputs['P1']] = 1.4 # scale the variable - m.egb.scaling_factor[m.egb.inputs['P3']] = 1.5 # scale the variable - m.egb.scaling_factor[m.egb.outputs['P2']] = 1.6 # scale the variable - m.egb.scaling_factor[m.egb.outputs['Pout']] = 1.7 # scale the variable + m.scaling_factor[m.egb.inputs['Pin']] = 1.1 # scale the variable + m.scaling_factor[m.egb.inputs['c']] = 1.2 # scale the variable + m.scaling_factor[m.egb.inputs['F']] = 1.3 # scale the variable + # m.scaling_factor[m.egb.inputs['P1']] = 1.4 # scale the variable + m.scaling_factor[m.egb.inputs['P3']] = 1.5 # scale the variable + m.scaling_factor[m.egb.outputs['P2']] = 1.6 # scale the variable + m.scaling_factor[m.egb.outputs['Pout']] = 1.7 # scale the variable # m.scaling_factor[m.hin] = 1.8 m.scaling_factor[m.hout] = 1.9 # m.scaling_factor[m.incon] = 2.1 @@ -2065,15 +2063,14 @@ def test_external_greybox_solve_scaling(self): ) m.scaling_factor = pyo.Suffix(direction=pyo.Suffix.EXPORT) - m.egb.scaling_factor = pyo.Suffix(direction=pyo.Suffix.EXPORT) m.scaling_factor[m.obj] = 0.1 # scale the objective - m.egb.scaling_factor[m.egb.inputs['Pin']] = 1.1 # scale the variable - m.egb.scaling_factor[m.egb.inputs['c']] = 1.2 # scale the variable - m.egb.scaling_factor[m.egb.inputs['F']] = 1.3 # scale the variable - # m.egb.scaling_factor[m.egb.inputs['P1']] = 1.4 # scale the variable - m.egb.scaling_factor[m.egb.inputs['P3']] = 1.5 # scale the variable - m.egb.scaling_factor[m.egb.outputs['P2']] = 1.6 # scale the variable - m.egb.scaling_factor[m.egb.outputs['Pout']] = 1.7 # scale the variable + m.scaling_factor[m.egb.inputs['Pin']] = 1.1 # scale the variable + m.scaling_factor[m.egb.inputs['c']] = 1.2 # scale the variable + m.scaling_factor[m.egb.inputs['F']] = 1.3 # scale the variable + # m.scaling_factor[m.egb.inputs['P1']] = 1.4 # scale the variable + m.scaling_factor[m.egb.inputs['P3']] = 1.5 # scale the variable + m.scaling_factor[m.egb.outputs['P2']] = 1.6 # scale the variable + m.scaling_factor[m.egb.outputs['Pout']] = 1.7 # scale the variable m.scaling_factor[m.mu] = 1.9 m.scaling_factor[m.pincon] = 2.2 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 39cfdf16146..ecadf40e5cf 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 @@ -956,15 +956,14 @@ def test_scaling_pyomo_model_only(self): ) m.obj = pyo.Objective(expr=(m.egb.outputs['Pout'] - 20) ** 2) m.scaling_factor = pyo.Suffix(direction=pyo.Suffix.EXPORT) - m.egb.scaling_factor = pyo.Suffix(direction=pyo.Suffix.EXPORT) # m.scaling_factor[m.obj] = 0.1 # scale the objective - m.egb.scaling_factor[m.egb.inputs['Pin']] = 1.1 # scale the variable - m.egb.scaling_factor[m.egb.inputs['c']] = 1.2 # scale the variable - m.egb.scaling_factor[m.egb.inputs['F']] = 1.3 # scale the variable - # m.egb.scaling_factor[m.egb.inputs['P1']] = 1.4 # scale the variable - m.egb.scaling_factor[m.egb.inputs['P3']] = 1.5 # scale the variable - m.egb.scaling_factor[m.egb.outputs['P2']] = 1.6 # scale the variable - m.egb.scaling_factor[m.egb.outputs['Pout']] = 1.7 # scale the variable + m.scaling_factor[m.egb.inputs['Pin']] = 1.1 # scale the variable + m.scaling_factor[m.egb.inputs['c']] = 1.2 # scale the variable + m.scaling_factor[m.egb.inputs['F']] = 1.3 # scale the variable + # m.scaling_factor[m.egb.inputs['P1']] = 1.4 # scale the variable + m.scaling_factor[m.egb.inputs['P3']] = 1.5 # scale the variable + m.scaling_factor[m.egb.outputs['P2']] = 1.6 # scale the variable + m.scaling_factor[m.egb.outputs['Pout']] = 1.7 # scale the variable # m.scaling_factor[m.hin] = 1.8 m.scaling_factor[m.hout] = 1.9 # m.scaling_factor[m.incon] = 2.1 @@ -2268,15 +2267,14 @@ def test_scaling_pyomo_model_only(self): ) m.obj = pyo.Objective(expr=(m.egb.outputs['Pout'] - 20) ** 2) m.scaling_factor = pyo.Suffix(direction=pyo.Suffix.EXPORT) - m.egb.scaling_factor = pyo.Suffix(direction=pyo.Suffix.EXPORT) # m.scaling_factor[m.obj] = 0.1 # scale the objective - m.egb.scaling_factor[m.egb.inputs['Pin']] = 1.1 # scale the variable - m.egb.scaling_factor[m.egb.inputs['c']] = 1.2 # scale the variable - m.egb.scaling_factor[m.egb.inputs['F']] = 1.3 # scale the variable - # m.egb.scaling_factor[m.egb.inputs['P1']] = 1.4 # scale the variable - m.egb.scaling_factor[m.egb.inputs['P3']] = 1.5 # scale the variable - m.egb.scaling_factor[m.egb.outputs['P2']] = 1.6 # scale the variable - m.egb.scaling_factor[m.egb.outputs['Pout']] = 1.7 # scale the variable + m.scaling_factor[m.egb.inputs['Pin']] = 1.1 # scale the variable + m.scaling_factor[m.egb.inputs['c']] = 1.2 # scale the variable + m.scaling_factor[m.egb.inputs['F']] = 1.3 # scale the variable + # m.scaling_factor[m.egb.inputs['P1']] = 1.4 # scale the variable + m.scaling_factor[m.egb.inputs['P3']] = 1.5 # scale the variable + m.scaling_factor[m.egb.outputs['P2']] = 1.6 # scale the variable + m.scaling_factor[m.egb.outputs['Pout']] = 1.7 # scale the variable # m.scaling_factor[m.hin] = 1.8 m.scaling_factor[m.hout] = 1.9 # m.scaling_factor[m.incon] = 2.1 @@ -2394,15 +2392,14 @@ def test_scaling_pyomo_model_and_greybox(self): ) m.obj = pyo.Objective(expr=(m.egb.outputs['Pout'] - 20) ** 2) m.scaling_factor = pyo.Suffix(direction=pyo.Suffix.EXPORT) - m.egb.scaling_factor = pyo.Suffix(direction=pyo.Suffix.EXPORT) # m.scaling_factor[m.obj] = 0.1 # scale the objective - m.egb.scaling_factor[m.egb.inputs['Pin']] = 1.1 # scale the variable - m.egb.scaling_factor[m.egb.inputs['c']] = 1.2 # scale the variable - m.egb.scaling_factor[m.egb.inputs['F']] = 1.3 # scale the variable - # m.egb.scaling_factor[m.egb.inputs['P1']] = 1.4 # scale the variable - m.egb.scaling_factor[m.egb.inputs['P3']] = 1.5 # scale the variable - m.egb.scaling_factor[m.egb.outputs['P2']] = 1.6 # scale the variable - m.egb.scaling_factor[m.egb.outputs['Pout']] = 1.7 # scale the variable + m.scaling_factor[m.egb.inputs['Pin']] = 1.1 # scale the variable + m.scaling_factor[m.egb.inputs['c']] = 1.2 # scale the variable + m.scaling_factor[m.egb.inputs['F']] = 1.3 # scale the variable + # m.scaling_factor[m.egb.inputs['P1']] = 1.4 # scale the variable + m.scaling_factor[m.egb.inputs['P3']] = 1.5 # scale the variable + m.scaling_factor[m.egb.outputs['P2']] = 1.6 # scale the variable + m.scaling_factor[m.egb.outputs['Pout']] = 1.7 # scale the variable # m.scaling_factor[m.hin] = 1.8 m.scaling_factor[m.hout] = 1.9 # m.scaling_factor[m.incon] = 2.1 @@ -2487,15 +2484,14 @@ def test_external_greybox_solve_scaling(self): ) m.scaling_factor = pyo.Suffix(direction=pyo.Suffix.EXPORT) - m.egb.scaling_factor = pyo.Suffix(direction=pyo.Suffix.EXPORT) m.scaling_factor[m.obj] = 0.1 # scale the objective - m.egb.scaling_factor[m.egb.inputs['Pin']] = 1.1 # scale the variable - m.egb.scaling_factor[m.egb.inputs['c']] = 1.2 # scale the variable - m.egb.scaling_factor[m.egb.inputs['F']] = 1.3 # scale the variable - # m.egb.scaling_factor[m.egb.inputs['P1']] = 1.4 # scale the variable - m.egb.scaling_factor[m.egb.inputs['P3']] = 1.5 # scale the variable - m.egb.scaling_factor[m.egb.outputs['P2']] = 1.6 # scale the variable - m.egb.scaling_factor[m.egb.outputs['Pout']] = 1.7 # scale the variable + m.scaling_factor[m.egb.inputs['Pin']] = 1.1 # scale the variable + m.scaling_factor[m.egb.inputs['c']] = 1.2 # scale the variable + m.scaling_factor[m.egb.inputs['F']] = 1.3 # scale the variable + # m.scaling_factor[m.egb.inputs['P1']] = 1.4 # scale the variable + m.scaling_factor[m.egb.inputs['P3']] = 1.5 # scale the variable + m.scaling_factor[m.egb.outputs['P2']] = 1.6 # scale the variable + m.scaling_factor[m.egb.outputs['Pout']] = 1.7 # scale the variable m.scaling_factor[m.mu] = 1.9 m.scaling_factor[m.pincon] = 2.2 From d4e4e0adafdf0769571c46a5a9f1cc9a9cde4946 Mon Sep 17 00:00:00 2001 From: Bernard Knueven Date: Thu, 27 Jun 2024 13:02:24 -0600 Subject: [PATCH 13/16] add small test --- .../pynumero/interfaces/tests/test_nlp.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/pyomo/contrib/pynumero/interfaces/tests/test_nlp.py b/pyomo/contrib/pynumero/interfaces/tests/test_nlp.py index 4f735e06de7..b456ce1cb51 100644 --- a/pyomo/contrib/pynumero/interfaces/tests/test_nlp.py +++ b/pyomo/contrib/pynumero/interfaces/tests/test_nlp.py @@ -699,6 +699,25 @@ def test_indices_methods(self): dense_hess = hess.todense() self.assertTrue(np.array_equal(dense_hess, expected_hess)) + def test_subblock_scaling(self): + m = pyo.ConcreteModel() + m.b = b = pyo.Block() + b.x = pyo.Var(bounds=(5e-17, 5e-16), initialize=1e-16) + b.scaling_factor = pyo.Suffix(direction=pyo.Suffix.EXPORT) + b.scaling_factor[b.x] = 1e16 + + b.c = pyo.Constraint(rule=b.x == 1e-16) + b.scaling_factor[b.c] = 1e16 + + b.o = pyo.Objective(expr=b.x) + b.scaling_factor[b.o] = 1e16 + + nlp = PyomoNLP(m) + + assert nlp.get_obj_scaling() == 1e16 + assert nlp.get_primals_scaling()[0] == 1e16 + assert nlp.get_constraints_scaling()[0] == 1e16 + def test_no_objective(self): m = pyo.ConcreteModel() m.x = pyo.Var() From 031500b8123fea0822241f4286ff40eea5cdac09 Mon Sep 17 00:00:00 2001 From: Bernard Knueven Date: Thu, 15 Aug 2024 15:43:48 -0600 Subject: [PATCH 14/16] use context argument for SuffixFinder --- .../pynumero/interfaces/pyomo_grey_box_nlp.py | 4 +++- pyomo/contrib/pynumero/interfaces/pyomo_nlp.py | 14 ++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py b/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py index 3cc23260f56..8b320c091e4 100644 --- a/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py +++ b/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py @@ -228,7 +228,9 @@ def __init__(self, pyomo_model): need_scaling = True self._primals_scaling = np.ones(self.n_primals()) - scaling_suffix_finder = SuffixFinder('scaling_factor') + scaling_suffix_finder = SuffixFinder( + 'scaling_factor', context=self._pyomo_model + ) for i, v in enumerate(self._pyomo_model_var_datas): v_scaling = scaling_suffix_finder.find(v) if v_scaling is not None: diff --git a/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py b/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py index 4b955b0176a..8790e29cf37 100644 --- a/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py +++ b/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py @@ -299,7 +299,7 @@ def get_inequality_constraint_indices(self, constraints): # overloaded from NLP def get_obj_scaling(self): obj = self.get_pyomo_objective() - val = SuffixFinder('scaling_factor').find(obj) + val = SuffixFinder('scaling_factor', context=self._pyomo_model).find(obj) # maintain backwards compatibility scaling_suffix = self._pyomo_model.component('scaling_factor') if scaling_suffix and scaling_suffix.ctype is pyo.Suffix: @@ -309,7 +309,9 @@ def get_obj_scaling(self): # overloaded from NLP def get_primals_scaling(self): - scaling_suffix_finder = SuffixFinder('scaling_factor') + scaling_suffix_finder = SuffixFinder( + 'scaling_factor', context=self._pyomo_model + ) primals_scaling = np.ones(self.n_primals()) ret = None for i, v in enumerate(self.get_pyomo_variables()): @@ -326,7 +328,9 @@ def get_primals_scaling(self): # overloaded from NLP def get_constraints_scaling(self): - scaling_suffix_finder = SuffixFinder('scaling_factor') + scaling_suffix_finder = SuffixFinder( + 'scaling_factor', context=self._pyomo_model + ) constraints_scaling = np.ones(self.n_constraints()) ret = None for i, c in enumerate(self.get_pyomo_constraints()): @@ -621,7 +625,9 @@ def __init__(self, pyomo_model): need_scaling = True self._primals_scaling = np.ones(self.n_primals()) - scaling_suffix_finder = SuffixFinder('scaling_factor') + scaling_suffix_finder = SuffixFinder( + 'scaling_factor', context=self._pyomo_model + ) for i, v in enumerate(self.get_pyomo_variables()): v_scaling = scaling_suffix_finder.find(v) if v_scaling is not None: From ff678174ec76cf0ec2c2275b9640b6e0fc15d048 Mon Sep 17 00:00:00 2001 From: Bernard Knueven Date: Fri, 16 Aug 2024 12:43:01 -0600 Subject: [PATCH 15/16] implement John's suggestion --- .../pynumero/interfaces/pyomo_grey_box_nlp.py | 20 ++--- .../contrib/pynumero/interfaces/pyomo_nlp.py | 85 ++++++++----------- 2 files changed, 43 insertions(+), 62 deletions(-) diff --git a/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py b/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py index 8b320c091e4..66cf99ea862 100644 --- a/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py +++ b/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py @@ -227,19 +227,15 @@ def __init__(self, pyomo_model): else: need_scaling = True - self._primals_scaling = np.ones(self.n_primals()) - scaling_suffix_finder = SuffixFinder( - 'scaling_factor', context=self._pyomo_model + scaling_finder = SuffixFinder( + 'scaling_factor', default=1.0, context=self._pyomo_model ) - for i, v in enumerate(self._pyomo_model_var_datas): - v_scaling = scaling_suffix_finder.find(v) - if v_scaling is not None: - need_scaling = True - self._primals_scaling[i] = v_scaling - # maintain backwards compatibility - scaling_suffix = self._pyomo_model.component('scaling_factor') - if scaling_suffix and scaling_suffix.ctype is pyo.Suffix: - need_scaling = True + self._primals_scaling = np.fromiter( + (scaling_finder.find(v) for v in self._pyomo_model_var_datas), + count=self.n_primals(), + dtype=float, + ) + need_scaling = bool(scaling_finder.all_suffixes) self._constraints_scaling = BlockVector(len(nlps)) for i, nlp in enumerate(nlps): diff --git a/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py b/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py index 8790e29cf37..725435619ad 100644 --- a/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py +++ b/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py @@ -298,52 +298,41 @@ def get_inequality_constraint_indices(self, constraints): # overloaded from NLP def get_obj_scaling(self): - obj = self.get_pyomo_objective() - val = SuffixFinder('scaling_factor', context=self._pyomo_model).find(obj) - # maintain backwards compatibility - scaling_suffix = self._pyomo_model.component('scaling_factor') - if scaling_suffix and scaling_suffix.ctype is pyo.Suffix: - return 1.0 if val is None else val - else: - return val + scaling_finder = SuffixFinder( + 'scaling_factor', default=1.0, context=self._pyomo_model + ) + val = scaling_finder.find(self.get_pyomo_objective()) + if not scaling_finder.all_suffixes: + return None + return val # overloaded from NLP def get_primals_scaling(self): - scaling_suffix_finder = SuffixFinder( - 'scaling_factor', context=self._pyomo_model + scaling_finder = SuffixFinder( + 'scaling_factor', default=1.0, context=self._pyomo_model ) - primals_scaling = np.ones(self.n_primals()) - ret = None - for i, v in enumerate(self.get_pyomo_variables()): - val = scaling_suffix_finder.find(v) - if val is not None: - primals_scaling[i] = val - ret = primals_scaling - # maintain backwards compatibility - scaling_suffix = self._pyomo_model.component('scaling_factor') - if scaling_suffix and scaling_suffix.ctype is pyo.Suffix: - return primals_scaling - else: - return ret + primals_scaling = np.fromiter( + (scaling_finder.find(v) for v in self.get_pyomo_variables()), + count=self.n_primals(), + dtype=float, + ) + if not scaling_finder.all_suffixes: + return None + return primals_scaling # overloaded from NLP def get_constraints_scaling(self): - scaling_suffix_finder = SuffixFinder( - 'scaling_factor', context=self._pyomo_model + scaling_finder = SuffixFinder( + 'scaling_factor', default=1.0, context=self._pyomo_model ) - constraints_scaling = np.ones(self.n_constraints()) - ret = None - for i, c in enumerate(self.get_pyomo_constraints()): - val = scaling_suffix_finder.find(c) - if val is not None: - constraints_scaling[i] = val - ret = constraints_scaling - # maintain backwards compatibility - scaling_suffix = self._pyomo_model.component('scaling_factor') - if scaling_suffix and scaling_suffix.ctype is pyo.Suffix: - return constraints_scaling - else: - return ret + constraints_scaling = np.fromiter( + (scaling_finder.find(v) for v in self.get_pyomo_constraints()), + count=self.n_constraints(), + dtype=float, + ) + if not scaling_finder.all_suffixes: + return None + return constraints_scaling def extract_subvector_grad_objective(self, pyomo_variables): """Compute the gradient of the objective and return the entries @@ -624,19 +613,15 @@ def __init__(self, pyomo_model): else: need_scaling = True - self._primals_scaling = np.ones(self.n_primals()) - scaling_suffix_finder = SuffixFinder( - 'scaling_factor', context=self._pyomo_model + scaling_finder = SuffixFinder( + 'scaling_factor', default=1.0, context=self._pyomo_model ) - for i, v in enumerate(self.get_pyomo_variables()): - v_scaling = scaling_suffix_finder.find(v) - if v_scaling is not None: - need_scaling = True - self._primals_scaling[i] = v_scaling - # maintain backwards compatibility - scaling_suffix = self._pyomo_model.component('scaling_factor') - if scaling_suffix and scaling_suffix.ctype is pyo.Suffix: - need_scaling = True + self._primals_scaling = np.fromiter( + (scaling_finder.find(v) for v in self.get_pyomo_variables()), + count=self.n_primals(), + dtype=float, + ) + need_scaling = bool(scaling_finder.all_suffixes) self._constraints_scaling = [] pyomo_nlp_scaling = self._pyomo_nlp.get_constraints_scaling() From 7e79e194486b98d4e2694355d8233f55e01e38d8 Mon Sep 17 00:00:00 2001 From: Bernard Knueven Date: Fri, 16 Aug 2024 14:37:40 -0600 Subject: [PATCH 16/16] adding Robby's test code for no scaling --- .../pynumero/interfaces/tests/test_nlp.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/pyomo/contrib/pynumero/interfaces/tests/test_nlp.py b/pyomo/contrib/pynumero/interfaces/tests/test_nlp.py index b456ce1cb51..a291ef1151a 100644 --- a/pyomo/contrib/pynumero/interfaces/tests/test_nlp.py +++ b/pyomo/contrib/pynumero/interfaces/tests/test_nlp.py @@ -718,6 +718,23 @@ def test_subblock_scaling(self): assert nlp.get_primals_scaling()[0] == 1e16 assert nlp.get_constraints_scaling()[0] == 1e16 + def test_subblock_no_scaling(self): + m = pyo.ConcreteModel() + m.b = pyo.Block() + m.b.x = pyo.Var([1, 2], initialize={1: 100, 2: 20}) + + # Components so we don't have an empty NLP + m.b.eq = pyo.Constraint(expr=m.b.x[1] * m.b.x[2] == 2000) + m.b.obj = pyo.Objective(expr=m.b.x[1] ** 2 + m.b.x[2] ** 2) + + m.scaling_factor = pyo.Suffix(direction=pyo.Suffix.EXPORT) + m.scaling_factor[m.b.x[1]] = 1e-2 + m.scaling_factor[m.b.x[2]] = 1e-1 + + nlp = PyomoNLP(m.b) + scaling = nlp.get_primals_scaling() + assert scaling is None + def test_no_objective(self): m = pyo.ConcreteModel() m.x = pyo.Var()