-
Notifications
You must be signed in to change notification settings - Fork 577
Implicit constraint #3870
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Implicit constraint #3870
Changes from all commits
0b1fe30
92265d7
cc4b693
f67e0c4
0a8f340
952d2ff
85253e9
e9e7007
e2628d6
8970a57
1cb696c
e77845b
8f3cce2
e694391
01818ca
66eab7f
2a3fd7a
f3bb8fd
1fa3ce6
55b6434
01df76b
48a4a5d
09654f5
fa781b2
974ce8a
9936edf
0d4184a
4cac8da
0d426fe
c93e330
9cc2bcb
b831f45
5e7ef58
5eb7d47
e19e53f
e78da85
e15dfc5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,185 @@ | ||
| # ___________________________________________________________________________ | ||
| # | ||
| # Pyomo: Python Optimization Modeling Objects | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please check / update the copyright statement (this is last year's). |
||
| # Copyright (c) 2008-2025 | ||
| # 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. | ||
| # ___________________________________________________________________________ | ||
| # | ||
| # Additional contributions Copyright (c) 2026 OLI Systems, Inc. | ||
| # ___________________________________________________________________________ | ||
|
|
||
| import pyomo.common.unittest as unittest | ||
| import pyomo.environ as pyo | ||
| from pyomo.common.collections import ComponentSet | ||
|
|
||
| from pyomo.contrib.incidence_analysis import IncidenceGraphInterface | ||
| from pyomo.contrib.pynumero.interfaces.external_grey_box import ExternalGreyBoxBlock | ||
| import pyomo.contrib.pynumero.interfaces.tests.external_grey_box_models as ex_models | ||
| from pyomo.contrib.pynumero.interfaces.external_grey_box_constraint import ( | ||
| ExternalGreyBoxConstraint, | ||
| ) | ||
|
|
||
|
|
||
| class TestExternalGreyBoxIncidence(unittest.TestCase): | ||
| def test_pressure_drop_single_output(self): | ||
| m = pyo.ConcreteModel() | ||
| m.egb = ExternalGreyBoxBlock() | ||
| m.egb.set_external_model(ex_models.PressureDropSingleOutput()) | ||
|
|
||
| igraph = IncidenceGraphInterface(m, include_inequality=False) | ||
| var_dm_partition, con_dm_partition = igraph.dulmage_mendelsohn() | ||
|
|
||
| uc_var = var_dm_partition.unmatched + var_dm_partition.underconstrained | ||
| uc_con = con_dm_partition.underconstrained | ||
| oc_var = var_dm_partition.overconstrained | ||
| oc_con = con_dm_partition.overconstrained + con_dm_partition.unmatched | ||
|
Comment on lines
+36
to
+39
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No action necessary, but this is reminding me that it would be nice to have an easier way to access the UC and OC sets without needing to know to add the unmatched components. |
||
|
|
||
| egb_var = ComponentSet(m.egb.component_data_objects(pyo.Var)) | ||
| self.assertEqual(ComponentSet(uc_var), egb_var) | ||
|
|
||
| uc_cons_set = ComponentSet([m.egb.output_constraints["Pout"]]) | ||
| self.assertEqual(ComponentSet(uc_con), uc_cons_set) | ||
|
|
||
| self.assertEqual(ComponentSet(oc_var), ComponentSet([])) | ||
| self.assertEqual(ComponentSet(oc_con), ComponentSet([])) | ||
|
|
||
| max_matching = igraph.maximum_matching() | ||
| self.assertIn(max_matching[m.egb.output_constraints["Pout"]], egb_var) | ||
|
|
||
| cc_vars, cc_cons = igraph.get_connected_components() | ||
| self.assertEqual(ComponentSet(cc_vars[0]), egb_var) | ||
| self.assertEqual(cc_cons[0][0].name, "egb.output_constraints[Pout]") | ||
|
|
||
| def test_pressure_drop_single_output_block_triangularization(self): | ||
| m = pyo.ConcreteModel() | ||
| m.egb = ExternalGreyBoxBlock() | ||
| m.egb.set_external_model(ex_models.PressureDropSingleOutput()) | ||
|
|
||
| # Add constraints to make model square, then rebuild graph to test block triangularization | ||
| m.con1 = pyo.Constraint(expr=m.egb.inputs["Pin"] == 1) | ||
| m.con2 = pyo.Constraint(expr=m.egb.inputs["c"] == 1) | ||
| m.con3 = pyo.Constraint(expr=m.egb.inputs["F"] == 1) | ||
| igraph = IncidenceGraphInterface(m, include_inequality=False) | ||
| bt_vars, bt_cons = igraph.block_triangularize() | ||
|
|
||
| # Expect 4 decomposable sub-sets, one for each linking constraint and one for the grey box | ||
| self.assertEqual(len(bt_vars), 4) | ||
| self.assertEqual(len(bt_cons), 4) | ||
|
|
||
| var_set_0 = [m.egb.inputs["Pin"]] | ||
| var_set_1 = [m.egb.inputs["c"]] | ||
| var_set_2 = [m.egb.inputs["F"]] | ||
| var_set_3 = [m.egb.outputs["Pout"]] | ||
| expected_bt_vars = [var_set_0, var_set_1, var_set_2, var_set_3] | ||
|
|
||
| con_set_0 = [m.con1] | ||
| con_set_1 = [m.con2] | ||
| con_set_2 = [m.con3] | ||
| con_set_3 = [m.egb.output_constraints["Pout"]] | ||
| expected_bt_cons = [con_set_0, con_set_1, con_set_2, con_set_3] | ||
|
|
||
| self.assertEqual(bt_vars, expected_bt_vars) | ||
| self.assertEqual(bt_cons, expected_bt_cons) | ||
|
|
||
| self.assertIs(bt_vars[0][0], m.egb.inputs["Pin"]) | ||
| self.assertIs(bt_vars[1][0], m.egb.inputs["c"]) | ||
| self.assertIs(bt_vars[2][0], m.egb.inputs["F"]) | ||
| self.assertIs(bt_vars[3][0], m.egb.outputs["Pout"]) | ||
|
|
||
| self.assertIs(bt_cons[0][0], m.con1) | ||
| self.assertIs(bt_cons[1][0], m.con2) | ||
| self.assertIs(bt_cons[2][0], m.con3) | ||
| self.assertIs(bt_cons[3][0], m.egb.output_constraints["Pout"]) | ||
|
|
||
| self.assertEqual( | ||
| ComponentSet(igraph.get_adjacent_to(m.egb.output_constraints["Pout"])), | ||
| ComponentSet(m.egb.component_data_objects(pyo.Var)), | ||
| ) | ||
|
|
||
|
andrewlee94 marked this conversation as resolved.
|
||
| def test_pressure_drop_two_equalities_two_outputs(self): | ||
| m = pyo.ConcreteModel() | ||
| m.egb = ExternalGreyBoxBlock() | ||
| m.egb.set_external_model(ex_models.PressureDropTwoEqualitiesTwoOutputs()) | ||
|
|
||
| igraph = IncidenceGraphInterface(m, include_inequality=False) | ||
| var_dm_partition, con_dm_partition = igraph.dulmage_mendelsohn() | ||
|
|
||
| uc_var = var_dm_partition.unmatched + var_dm_partition.underconstrained | ||
| uc_con = con_dm_partition.underconstrained | ||
| oc_var = var_dm_partition.overconstrained | ||
| oc_con = con_dm_partition.overconstrained + con_dm_partition.unmatched | ||
|
|
||
| uc_var_set = ComponentSet( | ||
| [ | ||
| m.egb.inputs["F"], | ||
| m.egb.inputs["P1"], | ||
| m.egb.inputs["P3"], | ||
| m.egb.inputs["Pin"], | ||
| m.egb.inputs["c"], | ||
| m.egb.outputs["P2"], | ||
| m.egb.outputs["Pout"], | ||
| ] | ||
| ) | ||
| self.assertEqual(ComponentSet(uc_var), uc_var_set) | ||
|
|
||
| uc_con_set = ComponentSet( | ||
| [ | ||
| m.egb.output_constraints["Pout"], | ||
| m.egb.output_constraints["P2"], | ||
| m.egb.eq_constraints["pdrop1"], | ||
| m.egb.eq_constraints["pdrop3"], | ||
| ] | ||
| ) | ||
| self.assertEqual(ComponentSet(uc_con), uc_con_set) | ||
|
|
||
| self.assertEqual(ComponentSet(oc_var), ComponentSet([])) | ||
| self.assertEqual(ComponentSet(oc_con), ComponentSet([])) | ||
|
|
||
| max_matching = igraph.maximum_matching() | ||
| egb_var = ComponentSet(m.egb.component_data_objects(pyo.Var)) | ||
| egb_cons = ComponentSet(m.egb.component_data_objects(ExternalGreyBoxConstraint)) | ||
| self.assertIn(max_matching[m.egb.output_constraints["Pout"]], egb_var) | ||
|
|
||
| cc_vars, cc_cons = igraph.get_connected_components() | ||
| self.assertEqual(ComponentSet(cc_vars[0]), egb_var) | ||
| self.assertEqual(ComponentSet(cc_cons[0]), egb_cons) | ||
|
|
||
| def test_pressure_drop_two_equalities_two_outputs_block_triangularization(self): | ||
| m = pyo.ConcreteModel() | ||
| m.egb = ExternalGreyBoxBlock() | ||
| m.egb.set_external_model(ex_models.PressureDropTwoEqualitiesTwoOutputs()) | ||
|
|
||
| # Add constraints to make model square, then rebuild graph to test block triangularization | ||
| m.con1 = pyo.Constraint(expr=m.egb.inputs["F"] == 1) | ||
| m.con2 = pyo.Constraint(expr=m.egb.inputs["Pin"] == 1) | ||
| m.con3 = pyo.Constraint(expr=m.egb.inputs["c"] == 1) | ||
| igraph = IncidenceGraphInterface(m, include_inequality=False) | ||
| bt_vars, bt_cons = igraph.block_triangularize() | ||
|
|
||
| matching = { | ||
| m.con1: m.egb.inputs["F"], | ||
| m.con2: m.egb.inputs["Pin"], | ||
| m.con3: m.egb.inputs["c"], | ||
| m.egb.eq_constraints["pdrop1"]: m.egb.inputs["P1"], | ||
| m.egb.eq_constraints["pdrop3"]: m.egb.inputs["P3"], | ||
| m.egb.output_constraints["P2"]: m.egb.outputs["P2"], | ||
| m.egb.output_constraints["Pout"]: m.egb.outputs["Pout"], | ||
| } | ||
|
|
||
| seen = ComponentSet() | ||
| for vars, cons in zip(bt_vars, bt_cons): | ||
| self.assertEqual(len(vars), 1) | ||
| self.assertIs(vars[0], matching[cons[0]]) | ||
| seen.update(vars) | ||
| # We know that P1 has to come before P2 and P3 in the block triangular form | ||
| if vars[0] is m.egb.outputs["P2"] or vars[0] is m.egb.inputs["P3"]: | ||
| self.assertIn(m.egb.inputs["P1"], seen) | ||
|
|
||
| # We know that these constraints have to be in the first three blocks | ||
| self.assertEqual( | ||
| set(bt_cons[i][0] for i in range(3)), set([m.con1, m.con2, m.con3]) | ||
| ) | ||
Uh oh!
There was an error while loading. Please reload this page.