From 7ee756c88466df677867026b850d0f5f6f61ae9a Mon Sep 17 00:00:00 2001 From: Carl Laird Date: Fri, 12 Jun 2020 13:42:56 -0500 Subject: [PATCH 1/3] adding support for continuous set --- pyomo/util/check_units.py | 65 ++++++++++++++++++++-------- pyomo/util/tests/test_check_units.py | 64 +++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 19 deletions(-) diff --git a/pyomo/util/check_units.py b/pyomo/util/check_units.py index 96f206873f7..42f6f615040 100644 --- a/pyomo/util/check_units.py +++ b/pyomo/util/check_units.py @@ -17,10 +17,9 @@ from pyomo.core.base import (Objective, Constraint, Var, Param, Suffix, Set, RangeSet, Block, ExternalFunction, Expression) +from pyomo.dae import ContinuousSet +from pyomo.mpec import Complementarity from pyomo.gdp import Disjunct, Disjunction - -from pyomo.gdp import Disjunct -from pyomo.gdp import Disjunction from pyomo.core.expr.template_expr import IndexTemplate from pyomo.core.expr.numvalue import native_types @@ -78,18 +77,17 @@ def _assert_units_consistent_constraint_data(condata): ConstraintData object are not consistent or are not equivalent with each other. """ - if condata.equality: - if condata.lower == 0.0: - # Pyomo can rearrange expressions, resulting in a value - # of 0 for the RHS that does not have units associated - # Therefore, if the RHS is 0, we allow it to be unitless - # and check the consistency of the body only - assert condata.upper == 0.0 - _assert_units_consistent_expression(condata.body) - else: - assert_units_equivalent(condata.lower, condata.body) - else: - assert_units_equivalent(condata.lower, condata.body, condata.upper) + # Pyomo can rearrange expressions, resulting in a value + # of 0 for upper or lower that does not have units associated + # Therefore, if the lower and/or upper is 0, we allow it to be unitless + # and check the consistency of the body only + args = list() + if condata.lower != 0.0 and condata.lower is not None: + args.append(condata.lower) + args.append(condata.body) + if condata.upper != 0.0 and condata.upper is not None: + args.append(condata.upper) + assert_units_equivalent(*args) def _assert_units_consistent_property_expr(obj): """ @@ -106,6 +104,18 @@ def _assert_units_consistent_expression(expr): """ pyomo_unit, pint_unit = units._get_units_tuple(expr) +def _assert_units_complementarity(cdata): + """ + Raise an exception if any units in either of the complementarity + expressions are inconsistent, and also check the standard block + methods. + """ + if cdata._args[0] is not None: + pyomo_unit, pint_unit = units._get_units_tuple(cdata._args[0]) + if cdata._args[1] is not None: + pyomo_unit, pint_unit = units._get_units_tuple(cdata._args[1]) + _assert_units_consistent_block(cdata) + def _assert_units_consistent_block(obj): """ This method gets all the components from the block @@ -127,7 +137,11 @@ def _assert_units_consistent_block(obj): Disjunct:_assert_units_consistent_block, Disjunction: None, Block: _assert_units_consistent_block, - ExternalFunction: None + ExternalFunction: None, + ContinuousSet: None, # ToDo: change this when continuous sets have units assigned + # complementarities that are not in normal form are not working yet + # see comment in test_check_units + # Complementarity: _assert_units_complementarity } def assert_units_consistent(obj): @@ -151,7 +165,11 @@ def assert_units_consistent(obj): if objtype in native_types: return elif obj.is_expression_type() or objtype is IndexTemplate: - _assert_units_consistent_expression(obj) + try: + _assert_units_consistent_expression(obj) + except UnitsError: + print('Units problem with expression {}'.format(obj)) + raise return # if object is not in our component handler, raise an exception @@ -166,6 +184,15 @@ def assert_units_consistent(obj): if obj.is_indexed(): # check all the component data objects for cdata in obj.values(): - handler(cdata) + try: + handler(cdata) + except UnitsError: + print('Error in units when checking {}'.format(cdata)) + raise else: - handler(obj) + try: + handler(obj) + except UnitsError: + print('Error in units when checking {}'.format(obj)) + raise + diff --git a/pyomo/util/tests/test_check_units.py b/pyomo/util/tests/test_check_units.py index 2e465d692c5..b2f7fa65d2f 100644 --- a/pyomo/util/tests/test_check_units.py +++ b/pyomo/util/tests/test_check_units.py @@ -13,13 +13,32 @@ import pyutilib.th as unittest from pyomo.environ import * +from pyomo.dae import ContinuousSet +from pyomo.mpec import Complementarity, complements +from pyomo.gdp import Disjunct, Disjunction from pyomo.core.base.units_container import ( pint_available, UnitsError, ) from pyomo.util.check_units import assert_units_consistent, assert_units_equivalent, check_units_equivalent +def python_callback_function(arg1, arg2): + return 42.0 + @unittest.skipIf(not pint_available, 'Testing units requires pint') class TestUnitsChecking(unittest.TestCase): + def _create_model_and_vars(self): + u = units + m = ConcreteModel() + m.dx = Var(units=u.m, initialize=0.10188943773836046) + m.dy = Var(units=u.m, initialize=0.0) + m.vx = Var(units=u.m/u.s, initialize=0.7071067769802851) + m.vy = Var(units=u.m/u.s, initialize=0.7071067769802851) + m.t = Var(units=u.s, bounds=(1e-5,10.0), initialize=0.0024015570927624456) + m.theta = Var(bounds=(0, 0.49*3.14), initialize=0.7853981693583533, units=u.radians) + m.a = Param(initialize=-32.2, units=u.ft/u.s**2) + m.x_unitless = Var() + return m + def test_assert_units_consistent_equivalent(self): u = units m = ConcreteModel() @@ -134,5 +153,50 @@ def broken(m,i): assert_units_consistent(m.vel_con[2]) # check constraint data assert_units_consistent(m.unitless_con[2]) # check unitless constraint data + def test_assert_units_consistent_all_components(self): + """ + Objective: _assert_units_consistent_property_expr, + Constraint: _assert_units_consistent_constraint_data, + Var: _assert_units_consistent_expression, + Expression: _assert_units_consistent_property_expr, + Suffix: None, + Param: _assert_units_consistent_expression, + Set: None, + RangeSet: None, + Disjunct:_assert_units_consistent_block, + Disjunction: None, + Block: _assert_units_consistent_block, + ExternalFunction: None, + ContinuousSet: None, # ToDo: change this when continuous sets have units assigned + Complementarity: _assert_units_complementarity + """ + # test all scalar components consistent + u = units + m = self._create_model_and_vars() + m.obj = Objective(expr=m.dx/m.t - m.vx) + m.con = Constraint(expr=m.dx/m.t == m.vx) + # vars already added + m.exp = Expression(expr=m.dx/m.t - m.vx) + m.suff = Suffix(direction=Suffix.LOCAL) + # params already added + # sets already added + m.rs = RangeSet(5) + m.disj1 = Disjunct() + m.disj1.constraint = Constraint(expr=m.dx/m.t <= m.vx) + m.disj2 = Disjunct() + m.disj2.constraint = Constraint(expr=m.dx/m.t <= m.vx) + m.disjn = Disjunction(expr=[m.disj1, m.disj2]) + # block tested as part of model + m.extfn = ExternalFunction(python_callback_function, units=u.m/u.s, arg_units=[u.m, u.s]) + m.conext = Constraint(expr=m.extfn(m.dx, m.t) - m.vx==0) + m.cset = ContinuousSet(bounds=(0,1)) + + # complementarities do not work yet + # The expression system removes the u.m since it is multiplied by zero. + # We need to change the units_container to allow 0 when comparing units + # m.compl = Complementarity(expr=complements(m.dx/m.t >= m.vx, m.dx == 0*u.m)) + + assert_units_consistent(m) + if __name__ == "__main__": unittest.main() From 4d4337d4a3ede10efc8c5a071e3fcae9768503b8 Mon Sep 17 00:00:00 2001 From: Carl Laird Date: Fri, 12 Jun 2020 15:06:50 -0500 Subject: [PATCH 2/3] add value() to a numerical check --- pyomo/util/check_units.py | 15 +++++++++++---- pyomo/util/tests/test_check_units.py | 16 ---------------- 2 files changed, 11 insertions(+), 20 deletions(-) diff --git a/pyomo/util/check_units.py b/pyomo/util/check_units.py index 42f6f615040..e88fb2a44e8 100644 --- a/pyomo/util/check_units.py +++ b/pyomo/util/check_units.py @@ -16,7 +16,8 @@ from pyomo.core.base.units_container import units, UnitsError, UnitExtractionVisitor from pyomo.core.base import (Objective, Constraint, Var, Param, Suffix, Set, RangeSet, Block, - ExternalFunction, Expression) + ExternalFunction, Expression, + value) from pyomo.dae import ContinuousSet from pyomo.mpec import Complementarity from pyomo.gdp import Disjunct, Disjunction @@ -82,12 +83,18 @@ def _assert_units_consistent_constraint_data(condata): # Therefore, if the lower and/or upper is 0, we allow it to be unitless # and check the consistency of the body only args = list() - if condata.lower != 0.0 and condata.lower is not None: + if condata.lower is not None and value(condata.lower) != 0.0: args.append(condata.lower) + args.append(condata.body) - if condata.upper != 0.0 and condata.upper is not None: + + if condata.upper is not None and value(condata.upper) != 0.0: args.append(condata.upper) - assert_units_equivalent(*args) + + if len(args) == 1: + assert_units_consistent(*args) + else: + assert_units_equivalent(*args) def _assert_units_consistent_property_expr(obj): """ diff --git a/pyomo/util/tests/test_check_units.py b/pyomo/util/tests/test_check_units.py index b2f7fa65d2f..d9f90c22396 100644 --- a/pyomo/util/tests/test_check_units.py +++ b/pyomo/util/tests/test_check_units.py @@ -154,22 +154,6 @@ def broken(m,i): assert_units_consistent(m.unitless_con[2]) # check unitless constraint data def test_assert_units_consistent_all_components(self): - """ - Objective: _assert_units_consistent_property_expr, - Constraint: _assert_units_consistent_constraint_data, - Var: _assert_units_consistent_expression, - Expression: _assert_units_consistent_property_expr, - Suffix: None, - Param: _assert_units_consistent_expression, - Set: None, - RangeSet: None, - Disjunct:_assert_units_consistent_block, - Disjunction: None, - Block: _assert_units_consistent_block, - ExternalFunction: None, - ContinuousSet: None, # ToDo: change this when continuous sets have units assigned - Complementarity: _assert_units_complementarity - """ # test all scalar components consistent u = units m = self._create_model_and_vars() From ba3ce7a659d57c28a7542e95101c055615a92fbe Mon Sep 17 00:00:00 2001 From: Carl Laird Date: Mon, 15 Jun 2020 13:10:32 -0500 Subject: [PATCH 3/3] commenting out unreachable code that will be necessary later --- pyomo/util/check_units.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/pyomo/util/check_units.py b/pyomo/util/check_units.py index e88fb2a44e8..9a96424b4e8 100644 --- a/pyomo/util/check_units.py +++ b/pyomo/util/check_units.py @@ -111,17 +111,21 @@ def _assert_units_consistent_expression(expr): """ pyomo_unit, pint_unit = units._get_units_tuple(expr) -def _assert_units_complementarity(cdata): - """ - Raise an exception if any units in either of the complementarity - expressions are inconsistent, and also check the standard block - methods. - """ - if cdata._args[0] is not None: - pyomo_unit, pint_unit = units._get_units_tuple(cdata._args[0]) - if cdata._args[1] is not None: - pyomo_unit, pint_unit = units._get_units_tuple(cdata._args[1]) - _assert_units_consistent_block(cdata) +# Complementarities that are not in standard form do not +# current work with the checking code. The Units container +# should be modified to allow sum and relationals with zero +# terms (e.g., unitless). Then this code can be enabled. +#def _assert_units_complementarity(cdata): +# """ +# Raise an exception if any units in either of the complementarity +# expressions are inconsistent, and also check the standard block +# methods. +# """ +# if cdata._args[0] is not None: +# pyomo_unit, pint_unit = units._get_units_tuple(cdata._args[0]) +# if cdata._args[1] is not None: +# pyomo_unit, pint_unit = units._get_units_tuple(cdata._args[1]) +# _assert_units_consistent_block(cdata) def _assert_units_consistent_block(obj): """