Repository: Pyomo/pyomo

Issue:
Setting var .value with units does not behave as expected
Pyomo allows a user to set the .value property on a var where the rhs contains an expression with units. This either needs to throw an error or do the conversion, but not silently allow this with no conversion. This needs to be investigated - I believe that this will just ignore the units now (which is problematic).

Need to decide if we will do implicit conversion or throw an error (and require v.set_unitted_value(...)) instead. @andrewlee94 favors the explicit use of something set_unitted_value.


Discussion:

Resolving patch:
diff --git a/pyomo/core/base/units_container.py b/pyomo/core/base/units_container.py
--- a/pyomo/core/base/units_container.py
+++ b/pyomo/core/base/units_container.py
@@ -127,6 +127,7 @@
 
 logger = logging.getLogger(__name__)
 
+
 class UnitsError(Exception):
     """
     An exception class for all general errors/warnings associated with units
@@ -145,7 +146,7 @@ class InconsistentUnitsError(UnitsError):
     E.g., x == y, where x is in units of kg and y is in units of meter
     """
     def __init__(self, exp1, exp2, msg):
-        msg = '{}: {} not compatible with {}.'.format(str(msg), str(exp1), str(exp2))
+        msg = f'{msg}: {exp1} not compatible with {exp2}.'
         super(InconsistentUnitsError, self).__init__(msg)
 
 
@@ -307,6 +308,12 @@ def __deepcopy__(self, memo):
         # as outside the model scope and DO NOT duplicate them.
         return self
 
+    def __eq__(self, other):
+        if other.__class__ is _PyomoUnit:
+            return ( self._pint_registry is other._pint_registry and
+                     self._pint_unit == other._pint_unit )
+        return super().__eq__(other)
+
     # __bool__ uses NumericValue base class implementation
     # __float__ uses NumericValue base class implementation
     # __int__ uses NumericValue base class implementation
@@ -426,8 +433,7 @@ def __init__(self, pyomo_units_container, units_equivalence_tolerance=1e-12):
         """
         super(PintUnitExtractionVisitor, self).__init__()
         self._pyomo_units_container = pyomo_units_container
-        self._pint_dimensionless = pyomo_units_container._pint_dimensionless
-        self._pint_radian = pyomo_units_container._pint_registry.radian
+        self._pint_dimensionless = None
         self._equivalent_pint_units = pyomo_units_container._equivalent_pint_units
         self._equivalent_to_dimensionless = pyomo_units_container._equivalent_to_dimensionless
 
@@ -535,9 +541,8 @@ def _get_unit_for_pow(self, node, child_units):
         if not self._equivalent_to_dimensionless(child_units[1]):
             # todo: allow radians?
             raise UnitsError(
-                "Error in sub-expression: {}. "
-                "Exponents in a pow expression must be dimensionless."
-                "".format(node))
+                f"Error in sub-expression: {node}. "
+                "Exponents in a pow expression must be dimensionless.")
 
         # common case - exponent is a constant number
         exponent = node.args[1]
@@ -551,9 +556,9 @@ def _get_unit_for_pow(self, node, child_units):
         # base is not dimensionless, exponent is dimensionless
         # ensure that the exponent is fixed
         if not exponent.is_fixed():
-            raise UnitsError("The base of an exponent has units {}, but "
-                             "the exponent is not a fixed numerical value."
-                             "".format(child_units[0]))
+            raise UnitsError(
+                f"The base of an exponent has units {child_units[0]}, but "
+                "the exponent is not a fixed numerical value.")
 
         return child_units[0]**value(exponent)
 
@@ -579,7 +584,7 @@ def _get_unit_for_single_child(self, node, child_units):
 
     def _get_units_ExternalFunction(self, node, child_units):
         """
-        Check to make sure that any child arguments are consistent with 
+        Check to make sure that any child arguments are consistent with
         arg_units return the value from node.get_units() This
         was written for ExternalFunctionExpression where the external
         function has units assigned to its return value and arguments
@@ -638,8 +643,8 @@ def _get_dimensionless_with_dimensionless_children(self, node, child_units):
         for pint_unit in child_units:
             if not self._equivalent_to_dimensionless(pint_unit):
                 raise UnitsError(
-                    'Expected no units or dimensionless units in {}, '
-                    'but found {}.'.format(str(node), str(pint_unit)))
+                    f'Expected no units or dimensionless units in {node}, '
+                    f'but found {pint_unit}.')
 
         return self._pint_dimensionless
 
@@ -687,8 +692,9 @@ def _get_unit_for_unary_function(self, node, child_units):
         func_name = node.getname()
         node_func = self.unary_function_method_map.get(func_name, None)
         if node_func is None:
-            raise TypeError('An unhandled unary function: {} was encountered while retrieving the'
-                        ' units of expression {}'.format(func_name, str(node)))
+            raise TypeError(
+                f'An unhandled unary function: {func_name} was encountered '
+                f'while retrieving the units of expression {node}')
         return node_func(self, node, child_units)
 
     def _get_unit_for_expr_if(self, node, child_units):
@@ -744,7 +750,9 @@ def _get_dimensionless_with_radians_child(self, node, child_units):
 
         if self._equivalent_to_dimensionless(child_units[0]):
             return self._pint_dimensionless
-        if self._equivalent_pint_units(child_units[0], self._pint_radian):
+        if self._equivalent_pint_units(
+                child_units[0],
+                self._pyomo_units_container._pint_registry.radian):
             return self._pint_dimensionless
 
         # units are not None, dimensionless, or radians
@@ -773,11 +781,11 @@ def _get_radians_with_dimensionless_child(self, node, child_units):
         assert len(child_units) == 1
 
         if self._equivalent_to_dimensionless(child_units[0]):
-            return self._pint_radian
+            return self._pyomo_units_container._pint_registry.radian
 
         raise UnitsError(
-            'Expected dimensionless argument to function in expression {},'
-            ' but found {}'.format(str(node), str(child_units[0])))
+            f'Expected dimensionless argument to function in expression {node},'
+            f' but found {child_units[0]}')
 
     def _get_unit_sqrt(self, node, child_units):
         """
@@ -847,39 +855,64 @@ def _get_unit_sqrt(self, node, child_units):
         'floor': _get_unit_for_single_child
     }
 
+    def initializeWalker(self, expr):
+        # Refresh the cached dimensionless (in case the underlying pint
+        # registry was either changed or had not been set when the
+        # PyomoUnitsContainer was originally created).
+        self._pint_dimensionless \
+            = self._pyomo_units_container._pint_dimensionless
+        walk, result = self.beforeChild(None, expr, 0)
+        if not walk:
+            result = self.finalizeResult(result)
+        return walk, result
+
+    def beforeChild(self, node, child, child_idx):
+        ctype = child.__class__
+        if ctype in native_types or ctype in pyomo_constant_types:
+            return False, self._pint_dimensionless
+
+        if child.is_expression_type():
+            return True, None
+
+        # this is a leaf, but not a native type
+        if ctype is _PyomoUnit:
+            return False, child._get_pint_unit()
+        elif hasattr(child, 'get_units'):
+            # might want to add other common types here
+            pyomo_unit = child.get_units()
+            pint_unit = self._pyomo_units_container._get_pint_units(pyomo_unit)
+            return False, pint_unit
+
+        return True, None
+
     def exitNode(self, node, data):
-        """ Callback for :class:`pyomo.core.current.StreamBasedExpressionVisitor`. This
-        method is called when moving back up the tree in a depth first search."""
-        
-        # first check if the node is a leaf
-        nodetype = type(node)
+        """Visitor callback when moving up the expression tree.
 
-        if nodetype in native_types or nodetype in pyomo_constant_types:
-            return self._pint_dimensionless
+        Callback for
+        :class:`pyomo.core.current.StreamBasedExpressionVisitor`. This
+        method is called when moving back up the tree in a depth first
+        search.
 
-        node_func = self.node_type_method_map.get(nodetype, None)
+        """
+        node_func = self.node_type_method_map.get(node.__class__, None)
         if node_func is not None:
             return node_func(self, node, data)
 
-        elif not node.is_expression_type():
-            # this is a leaf, but not a native type
-            if nodetype is _PyomoUnit:
-                return node._get_pint_unit()
-            elif hasattr(node, 'get_units'):
-                # might want to add other common types here
-                pyomo_unit = node.get_units()
-                pint_unit = self._pyomo_units_container._get_pint_units(pyomo_unit)
-                return pint_unit
-
         # not a leaf - check if it is a named expression
         if hasattr(node, 'is_named_expression_type') and node.is_named_expression_type():
             pint_unit = self._get_unit_for_single_child(node, data)
             return pint_unit
 
-        raise TypeError('An unhandled expression node type: {} was encountered while retrieving the'
-                        ' units of expression'.format(str(nodetype), str(node)))
+        raise TypeError(
+            f'An unhandled expression node type: {type(node)} was encountered '
+            f'while retrieving the units of expression {node}')
+
+    def finalizeResult(self, result):
+        if hasattr(result, 'units'):
+            # likely, we got a quantity object and not a units object
+            return result.units
+        return result
 
-    
 class PyomoUnitsContainer(object):
     """Class that is used to create and contain units in Pyomo.
 
@@ -914,6 +947,7 @@ def __init__(self, pint_registry=NOTSET):
             self._pint_dimensionless = None
         else:
             self._pint_dimensionless = self._pint_registry.dimensionless
+        self._pintUnitExtractionVisitor = PintUnitExtractionVisitor(self)
 
     def load_definitions_from_file(self, definition_file):
         """Load new units definitions from a file
@@ -1020,9 +1054,10 @@ def __getattr__(self, item):
                 pint_unit_container = pint_module.util.to_units_container(pint_unit, pint_registry)
                 for (u, e) in pint_unit_container.items():
                     if not pint_registry._units[u].is_multiplicative:
-                        raise UnitsError('Pyomo units system does not support the offset units "{}".'
-                                         ' Use absolute units (e.g. kelvin instead of degC) instead.'
-                                         ''.format(item))
+                        raise UnitsError(
+                            'Pyomo units system does not support the offset '
+                            f'units "{item}". Use absolute units '
+                            '(e.g. kelvin instead of degC) instead.')
 
                 unit = _PyomoUnit(pint_unit, pint_registry)
                 setattr(self, item, unit)
@@ -1031,7 +1066,7 @@ def __getattr__(self, item):
             pint_unit = None
 
         if pint_unit is None:
-            raise AttributeError('Attribute {0} not found.'.format(str(item)))
+            raise AttributeError(f'Attribute {item} not found.')
 
     # We added support to specify a units definition file instead of this programatic interface
     # def create_new_base_dimension(self, dimension_name, base_unit_name):
@@ -1130,13 +1165,8 @@ def _get_pint_units(self, expr):
         """
         if expr is None:
             return self._pint_dimensionless
+        return self._pintUnitExtractionVisitor.walk_expression(expr=expr)
 
-        pint_units = PintUnitExtractionVisitor(self).walk_expression(expr=expr)
-        if hasattr(pint_units, 'units'):
-            # likely, we got a quantity object and not a units object
-            return pint_units.units
-        return pint_units
-    
     def get_units(self, expr):
         """
         Return the Pyomo units corresponding to this expression (also performs validation
@@ -1157,22 +1187,18 @@ def get_units(self, expr):
         :py:class:`pyomo.core.base.units_container.UnitsError`, :py:class:`pyomo.core.base.units_container.InconsistentUnitsError`
 
         """
-        pint_unit = self._get_pint_units(expr)
-        if pint_unit.dimensionless:
-            if pint_unit == self._pint_dimensionless:
-                return None
-        return _PyomoUnit(pint_unit, self._pint_registry)
+        return _PyomoUnit(self._get_pint_units(expr), self._pint_registry)
 
     def _pint_convert_temp_from_to(self, numerical_value, pint_from_units, pint_to_units):
         if type(numerical_value) not in native_numeric_types:
             raise UnitsError('Conversion routines for absolute and relative temperatures require a numerical value only.'
                              ' Pyomo objects (Var, Param, expressions) are not supported. Please use value(x) to'
                              ' extract the numerical value if necessary.')
-        
+
         src_quantity = self._pint_registry.Quantity(numerical_value, pint_from_units)
         dest_quantity = src_quantity.to(pint_to_units)
         return dest_quantity.magnitude
-        
+
     def convert_temp_K_to_C(self, value_in_K):
         """
         Convert a value in Kelvin to degrees Celcius.  Note that this method
@@ -1270,9 +1296,10 @@ def convert_value(self, num_value, from_units=None, to_units=None):
 
         """
         if type(num_value) not in native_numeric_types:
-            raise UnitsError('The argument "num_value" in convert_value must be a native numeric type, but'
-                             ' instead type {} was found.'.format(type(num_value)))
-        
+            raise UnitsError(
+                'The argument "num_value" in convert_value must be a native '
+                'numeric type, but instead type {type(num_value)} was found.')
+
         from_pint_unit = self._get_pint_units(from_units)
         to_pint_unit = self._get_pint_units(to_units)
         if from_pint_unit == to_pint_unit:
@@ -1394,6 +1421,7 @@ def _handle_external(self, node, values):
     EXPR.NPV_ExternalFunctionExpression: _QuantityVisitor._handle_external,
 }
 
+
 def as_quantity(expr):
     return _QuantityVisitor().dfs_postorder_stack(expr)
 


Test patch:
diff --git a/pyomo/core/tests/unit/test_units.py b/pyomo/core/tests/unit/test_units.py
--- a/pyomo/core/tests/unit/test_units.py
+++ b/pyomo/core/tests/unit/test_units.py
@@ -32,7 +32,8 @@
 import pyomo.core.expr.current as EXPR
 from pyomo.core.base.units_container import (
     pint_available, pint_module, _DeferredUnitsSingleton,
-    InconsistentUnitsError, UnitsError, PyomoUnitsContainer, as_quantity
+    InconsistentUnitsError, UnitsError, PyomoUnitsContainer, as_quantity,
+    _PyomoUnit,
 )
 from io import StringIO
 
@@ -275,7 +276,7 @@ def test_get_check_units_on_all_expressions(self):
 
         # test PowExpression, NPV_PowExpression
         self._get_check_units_ok(kg**2, uc, 'kg**2', EXPR.NPV_PowExpression)
-        self._get_check_units_ok(model.p**model.p, uc, 'None', EXPR.NPV_PowExpression)
+        self._get_check_units_ok(model.p**model.p, uc, 'dimensionless', EXPR.NPV_PowExpression)
         self._get_check_units_ok(kg**model.p, uc, 'kg**42', EXPR.NPV_PowExpression)
         self._get_check_units_ok((model.x*kg**2)**3, uc, 'kg**6', EXPR.PowExpression)
         self._get_check_units_fail(kg**model.x, uc, EXPR.PowExpression, UnitsError)
@@ -295,51 +296,51 @@ def test_get_check_units_on_all_expressions(self):
 
         # test the different UnaryFunctionExpression / NPV_UnaryFunctionExpression types
         # log
-        self._get_check_units_ok(log(3.0*model.x), uc, None, EXPR.UnaryFunctionExpression)
+        self._get_check_units_ok(log(3.0*model.x), uc, 'dimensionless', EXPR.UnaryFunctionExpression)
         self._get_check_units_fail(log(3.0*kg*model.x), uc, EXPR.UnaryFunctionExpression, UnitsError)
-        self._get_check_units_ok(log(3.0*model.p), uc, None, EXPR.NPV_UnaryFunctionExpression)
+        self._get_check_units_ok(log(3.0*model.p), uc, 'dimensionless', EXPR.NPV_UnaryFunctionExpression)
         self._get_check_units_fail(log(3.0*kg), uc, EXPR.NPV_UnaryFunctionExpression, UnitsError)
         # log10
-        self._get_check_units_ok(log10(3.0*model.x), uc, None, EXPR.UnaryFunctionExpression)
+        self._get_check_units_ok(log10(3.0*model.x), uc, 'dimensionless', EXPR.UnaryFunctionExpression)
         self._get_check_units_fail(log10(3.0*kg*model.x), uc, EXPR.UnaryFunctionExpression, UnitsError)
-        self._get_check_units_ok(log10(3.0*model.p), uc, None, EXPR.NPV_UnaryFunctionExpression)
+        self._get_check_units_ok(log10(3.0*model.p), uc, 'dimensionless', EXPR.NPV_UnaryFunctionExpression)
         self._get_check_units_fail(log10(3.0*kg), uc, EXPR.NPV_UnaryFunctionExpression, UnitsError)
         # sin
-        self._get_check_units_ok(sin(3.0*model.x), uc, None, EXPR.UnaryFunctionExpression)
-        self._get_check_units_ok(sin(3.0*model.x*uc.radians), uc, None, EXPR.UnaryFunctionExpression)
+        self._get_check_units_ok(sin(3.0*model.x), uc, 'dimensionless', EXPR.UnaryFunctionExpression)
+        self._get_check_units_ok(sin(3.0*model.x*uc.radians), uc, 'dimensionless', EXPR.UnaryFunctionExpression)
         self._get_check_units_fail(sin(3.0*kg*model.x), uc, EXPR.UnaryFunctionExpression, UnitsError)
         self._get_check_units_fail(sin(3.0*kg*model.x*uc.kg), uc, EXPR.UnaryFunctionExpression, UnitsError)
-        self._get_check_units_ok(sin(3.0*model.p*uc.radians), uc, None, EXPR.NPV_UnaryFunctionExpression)
+        self._get_check_units_ok(sin(3.0*model.p*uc.radians), uc, 'dimensionless', EXPR.NPV_UnaryFunctionExpression)
         self._get_check_units_fail(sin(3.0*kg), uc, EXPR.NPV_UnaryFunctionExpression, UnitsError)
         # cos
-        self._get_check_units_ok(cos(3.0*model.x*uc.radians), uc, None, EXPR.UnaryFunctionExpression)
+        self._get_check_units_ok(cos(3.0*model.x*uc.radians), uc, 'dimensionless', EXPR.UnaryFunctionExpression)
         self._get_check_units_fail(cos(3.0*kg*model.x), uc, EXPR.UnaryFunctionExpression, UnitsError)
         self._get_check_units_fail(cos(3.0*kg*model.x*uc.kg), uc, EXPR.UnaryFunctionExpression, UnitsError)
-        self._get_check_units_ok(cos(3.0*model.p*uc.radians), uc, None, EXPR.NPV_UnaryFunctionExpression)
+        self._get_check_units_ok(cos(3.0*model.p*uc.radians), uc, 'dimensionless', EXPR.NPV_UnaryFunctionExpression)
         self._get_check_units_fail(cos(3.0*kg), uc, EXPR.NPV_UnaryFunctionExpression, UnitsError)
         # tan
-        self._get_check_units_ok(tan(3.0*model.x*uc.radians), uc, None, EXPR.UnaryFunctionExpression)
+        self._get_check_units_ok(tan(3.0*model.x*uc.radians), uc, 'dimensionless', EXPR.UnaryFunctionExpression)
         self._get_check_units_fail(tan(3.0*kg*model.x), uc, EXPR.UnaryFunctionExpression, UnitsError)
         self._get_check_units_fail(tan(3.0*kg*model.x*uc.kg), uc, EXPR.UnaryFunctionExpression, UnitsError)
-        self._get_check_units_ok(tan(3.0*model.p*uc.radians), uc, None, EXPR.NPV_UnaryFunctionExpression)
+        self._get_check_units_ok(tan(3.0*model.p*uc.radians), uc, 'dimensionless', EXPR.NPV_UnaryFunctionExpression)
         self._get_check_units_fail(tan(3.0*kg), uc, EXPR.NPV_UnaryFunctionExpression, UnitsError)
         # sin
-        self._get_check_units_ok(sinh(3.0*model.x*uc.radians), uc, None, EXPR.UnaryFunctionExpression)
+        self._get_check_units_ok(sinh(3.0*model.x*uc.radians), uc, 'dimensionless', EXPR.UnaryFunctionExpression)
         self._get_check_units_fail(sinh(3.0*kg*model.x), uc, EXPR.UnaryFunctionExpression, UnitsError)
         self._get_check_units_fail(sinh(3.0*kg*model.x*uc.kg), uc, EXPR.UnaryFunctionExpression, UnitsError)
-        self._get_check_units_ok(sinh(3.0*model.p*uc.radians), uc, None, EXPR.NPV_UnaryFunctionExpression)
+        self._get_check_units_ok(sinh(3.0*model.p*uc.radians), uc, 'dimensionless', EXPR.NPV_UnaryFunctionExpression)
         self._get_check_units_fail(sinh(3.0*kg), uc, EXPR.NPV_UnaryFunctionExpression, UnitsError)
         # cos
-        self._get_check_units_ok(cosh(3.0*model.x*uc.radians), uc, None, EXPR.UnaryFunctionExpression)
+        self._get_check_units_ok(cosh(3.0*model.x*uc.radians), uc, 'dimensionless', EXPR.UnaryFunctionExpression)
         self._get_check_units_fail(cosh(3.0*kg*model.x), uc, EXPR.UnaryFunctionExpression, UnitsError)
         self._get_check_units_fail(cosh(3.0*kg*model.x*uc.kg), uc, EXPR.UnaryFunctionExpression, UnitsError)
-        self._get_check_units_ok(cosh(3.0*model.p*uc.radians), uc, None, EXPR.NPV_UnaryFunctionExpression)
+        self._get_check_units_ok(cosh(3.0*model.p*uc.radians), uc, 'dimensionless', EXPR.NPV_UnaryFunctionExpression)
         self._get_check_units_fail(cosh(3.0*kg), uc, EXPR.NPV_UnaryFunctionExpression, UnitsError)
         # tan
-        self._get_check_units_ok(tanh(3.0*model.x*uc.radians), uc, None, EXPR.UnaryFunctionExpression)
+        self._get_check_units_ok(tanh(3.0*model.x*uc.radians), uc, 'dimensionless', EXPR.UnaryFunctionExpression)
         self._get_check_units_fail(tanh(3.0*kg*model.x), uc, EXPR.UnaryFunctionExpression, UnitsError)
         self._get_check_units_fail(tanh(3.0*kg*model.x*uc.kg), uc, EXPR.UnaryFunctionExpression, UnitsError)
-        self._get_check_units_ok(tanh(3.0*model.p*uc.radians), uc, None, EXPR.NPV_UnaryFunctionExpression)
+        self._get_check_units_ok(tanh(3.0*model.p*uc.radians), uc, 'dimensionless', EXPR.NPV_UnaryFunctionExpression)
         self._get_check_units_fail(tanh(3.0*kg), uc, EXPR.NPV_UnaryFunctionExpression, UnitsError)
         # asin
         self._get_check_units_ok(asin(3.0*model.x), uc, 'rad', EXPR.UnaryFunctionExpression)
@@ -357,15 +358,15 @@ def test_get_check_units_on_all_expressions(self):
         self._get_check_units_ok(atan(3.0*model.p), uc, 'rad', EXPR.NPV_UnaryFunctionExpression)
         self._get_check_units_fail(atan(3.0*model.p*kg), uc, EXPR.NPV_UnaryFunctionExpression, UnitsError)
         # exp
-        self._get_check_units_ok(exp(3.0*model.x), uc, None, EXPR.UnaryFunctionExpression)
+        self._get_check_units_ok(exp(3.0*model.x), uc, 'dimensionless', EXPR.UnaryFunctionExpression)
         self._get_check_units_fail(exp(3.0*kg*model.x), uc, EXPR.UnaryFunctionExpression, UnitsError)
-        self._get_check_units_ok(exp(3.0*model.p), uc, None, EXPR.NPV_UnaryFunctionExpression)
+        self._get_check_units_ok(exp(3.0*model.p), uc, 'dimensionless', EXPR.NPV_UnaryFunctionExpression)
         self._get_check_units_fail(exp(3.0*kg), uc, EXPR.NPV_UnaryFunctionExpression, UnitsError)
         # sqrt
-        self._get_check_units_ok(sqrt(3.0*model.x), uc, None, EXPR.UnaryFunctionExpression)
+        self._get_check_units_ok(sqrt(3.0*model.x), uc, 'dimensionless', EXPR.UnaryFunctionExpression)
         self._get_check_units_ok(sqrt(3.0*model.x*kg**2), uc, 'kg', EXPR.UnaryFunctionExpression)
         self._get_check_units_ok(sqrt(3.0*model.x*kg), uc, 'kg**0.5', EXPR.UnaryFunctionExpression)
-        self._get_check_units_ok(sqrt(3.0*model.p), uc, None, EXPR.NPV_UnaryFunctionExpression)
+        self._get_check_units_ok(sqrt(3.0*model.p), uc, 'dimensionless', EXPR.NPV_UnaryFunctionExpression)
         self._get_check_units_ok(sqrt(3.0*model.p*kg**2), uc, 'kg', EXPR.NPV_UnaryFunctionExpression)
         self._get_check_units_ok(sqrt(3.0*model.p*kg), uc, 'kg**0.5', EXPR.NPV_UnaryFunctionExpression)
         # asinh
@@ -401,7 +402,7 @@ def test_get_check_units_on_all_expressions(self):
                                  uc, 'kg', EXPR.Expr_ifExpression)
         # consistent if, unitless then/else
         self._get_check_units_ok(EXPR.Expr_if(IF=model.x*kg + kg >= 2.0*kg, THEN=model.x, ELSE=model.x),
-                                 uc, None, EXPR.Expr_ifExpression)
+                                 uc, 'dimensionless', EXPR.Expr_ifExpression)
         # inconsistent then/else
         self._get_check_units_fail(EXPR.Expr_if(IF=model.x >= 2.0, THEN=model.x*m, ELSE=model.y*kg),
                                  uc, EXPR.Expr_ifExpression)
@@ -416,15 +417,15 @@ def test_get_check_units_on_all_expressions(self):
         model.S = Set()
         i = EXPR.IndexTemplate(model.S)
         j = EXPR.IndexTemplate(model.S)
-        self._get_check_units_ok(i, uc, None, EXPR.IndexTemplate)
+        self._get_check_units_ok(i, uc, 'dimensionless', EXPR.IndexTemplate)
 
         model.mat = Var(model.S, model.S)
-        self._get_check_units_ok(model.mat[i,j+1], uc, None, EXPR.GetItemExpression)
+        self._get_check_units_ok(model.mat[i,j+1], uc, 'dimensionless', EXPR.GetItemExpression)
 
         # test ExternalFunctionExpression, NPV_ExternalFunctionExpression
         model.ef = ExternalFunction(python_callback_function)
-        self._get_check_units_ok(model.ef(model.x, model.y), uc, None, EXPR.ExternalFunctionExpression)
-        self._get_check_units_ok(model.ef(1.0, 2.0), uc, None, EXPR.NPV_ExternalFunctionExpression)
+        self._get_check_units_ok(model.ef(model.x, model.y), uc, 'dimensionless', EXPR.ExternalFunctionExpression)
+        self._get_check_units_ok(model.ef(1.0, 2.0), uc, 'dimensionless', EXPR.NPV_ExternalFunctionExpression)
         self._get_check_units_fail(model.ef(model.x*kg, model.y), uc, EXPR.ExternalFunctionExpression, UnitsError)
         self._get_check_units_fail(model.ef(2.0*kg, 1.0), uc, EXPR.NPV_ExternalFunctionExpression, UnitsError)
 
@@ -456,7 +457,7 @@ def test_linear_expression(self):
         # test LinearExpression
         # ToDo: Once this test is working correctly, this code should be moved to the test above
         model.vv = Var(['A', 'B', 'C'])
-        self._get_check_units_ok(sum_product(model.vv), uc, None, EXPR.LinearExpression)
+        self._get_check_units_ok(sum_product(model.vv), uc, 'dimensionless', EXPR.LinearExpression)
 
         linex1 = sum_product(model.vv, {'A': kg, 'B': kg, 'C':kg}, index=['A', 'B', 'C'])
         self._get_check_units_ok(linex1, uc, 'kg', EXPR.LinearExpression)
@@ -482,10 +483,16 @@ def test_dimensionless(self):
         uc = units
         kg = uc.kg
         dless = uc.dimensionless
-        self._get_check_units_ok(2.0 == 2.0*dless, uc, None, EXPR.EqualityExpression)
-        self.assertEqual(uc.get_units(2.0*dless), uc.get_units(2.0))
-        self.assertIsNone(uc.get_units(2.0*dless))
-        self.assertIsNone(uc.get_units(kg/kg))
+        self._get_check_units_ok(2.0 == 2.0*dless, uc, 'dimensionless', EXPR.EqualityExpression)
+        x = uc.get_units(2.0)
+        self.assertIs(type(x), _PyomoUnit)
+        self.assertEqual(x, dless)
+        x = uc.get_units(2.0*dless)
+        self.assertIs(type(x), _PyomoUnit)
+        self.assertEqual(x, dless)
+        x = uc.get_units(kg/kg)
+        self.assertIs(type(x), _PyomoUnit)
+        self.assertEqual(x, dless)
 
     def test_temperatures(self):
         uc = units
@@ -834,6 +841,44 @@ def MyAdder(x, y):
         self.assertIs(q.__class__, Quantity)
         self.assertAlmostEqual(q, 3 * _pint.kg)
 
+    def test_var_set_value(self):
+        # Tests for #1570
+        m = ConcreteModel()
+
+        # Un-united variables just strip the units
+        m.x = Var()
+        m.x.value = 10
+        self.assertEqual(m.x.value, 10)
+        m.x.value = 20*units.kg
+        self.assertEqual(m.x.value, 20)
+        m.x.value = 30*units.dimensionless
+        self.assertEqual(m.x.value, 30)
+        del m.x
+
+        # Dimensionless variables require dimensionless args
+        m.x = Var(units=units.dimensionless)
+        m.x.value = 10
+        self.assertEqual(m.x.value, 10)
+        with self.assertRaisesRegex(
+                UnitsError, 'Cannot convert kg to dimensionless'):
+            m.x.value = 20*units.kg
+        self.assertEqual(m.x.value, 10)
+        m.x.value = 30*units.dimensionless
+        self.assertEqual(m.x.value, 30)
+        del m.x
+
+        # Dimensioned variables require bare or dimensioned args
+        m.x = Var(units=units.gram)
+        m.x.value = 10
+        self.assertEqual(m.x.value, 10)
+        m.x.value = 20*units.kg
+        self.assertEqual(m.x.value, 20000)
+        with self.assertRaisesRegex(
+                UnitsError, 'Cannot convert dimensionless to g'):
+            m.x.value = 30*units.dimensionless
+        self.assertEqual(m.x.value, 20000)
+        del m.x
+
 
 if __name__ == "__main__":
     unittest.main()