Skip to content

Commit fa799a9

Browse files
authored
Merge pull request #3651 from jsiirola/repn-refactor
Refactor linear/quadratic expression compilers
2 parents eb3ce81 + f8562a0 commit fa799a9

24 files changed

+1450
-1435
lines changed

pyomo/contrib/pyros/util.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959
from pyomo.core.util import prod
6060
from pyomo.opt import SolverFactory
6161
import pyomo.repn.ampl as pyomo_ampl_repn
62-
from pyomo.repn.parameterized_quadratic import ParameterizedQuadraticRepnVisitor
62+
from pyomo.repn.parameterized import ParameterizedQuadraticRepnVisitor
6363
import pyomo.repn.plugins.nl_writer as pyomo_nl_writer
6464
from pyomo.repn.util import OrderedVarRecorder
6565
from pyomo.util.vars_from_expressions import get_vars_from_components

pyomo/core/base/constraint.py

Lines changed: 63 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@
6363
TEMPLATIZE_CONSTRAINTS = False
6464

6565
_inf = float('inf')
66-
_nonfinite_values = {_inf, -_inf}
66+
_ninf = -_inf
67+
_nonfinite_values = {_inf, _ninf}
6768
_known_relational_expression_types = {
6869
EqualityExpression,
6970
InequalityExpression,
@@ -246,21 +247,31 @@ def to_bounded_expression(self, evaluate_bounds=False):
246247

247248
if evaluate_bounds:
248249
lb, body, ub = ans
249-
return self._evaluate_bound(lb, True), body, self._evaluate_bound(ub, False)
250+
return self._evaluate_bound(lb, _ninf), body, self._evaluate_bound(ub, _inf)
250251
return ans
251252

252-
def _evaluate_bound(self, bound, is_lb):
253+
def _evaluate_bound(self, bound, unbounded):
253254
if bound is None:
254255
return None
255256
if bound.__class__ not in native_numeric_types:
256-
bound = float(value(bound))
257+
bound = value(bound)
258+
if bound.__class__ not in native_numeric_types:
259+
# Starting in numpy 1.25, casting 1-element ndarray to
260+
# float is deprecated. We still want to support
261+
# that... but without enforcing a hard numpy dependence
262+
for cls in bound.__class__.__mro__:
263+
if cls.__name__ == 'ndarray' and cls.__module__ == 'numpy':
264+
if len(bound) == 1:
265+
bound = bound[0]
266+
break
267+
bound = float(bound)
257268
# Note that "bound != bound" catches float('nan')
258269
if bound in _nonfinite_values or bound != bound:
259-
if bound == (-_inf if is_lb else _inf):
270+
if bound == unbounded:
260271
return None
261272
raise ValueError(
262273
f"Constraint '{self.name}' created with an invalid non-finite "
263-
f"{'lower' if is_lb else 'upper'} bound ({bound})."
274+
f"{'upper' if unbounded==_inf else 'lower'} bound ({bound})."
264275
)
265276
return bound
266277

@@ -333,12 +344,12 @@ def upper(self):
333344
@property
334345
def lb(self):
335346
"""float : the value of the lower bound of a constraint expression."""
336-
return self._evaluate_bound(self.to_bounded_expression()[0], True)
347+
return self._evaluate_bound(self.to_bounded_expression()[0], _ninf)
337348

338349
@property
339350
def ub(self):
340351
"""float : the value of the upper bound of a constraint expression."""
341-
return self._evaluate_bound(self.to_bounded_expression()[2], False)
352+
return self._evaluate_bound(self.to_bounded_expression()[2], _inf)
342353

343354
@property
344355
def equality(self):
@@ -527,20 +538,9 @@ class _GeneralConstraintData(metaclass=RenamedClass):
527538
__renamed__version__ = '6.7.2'
528539

529540

530-
class TemplateConstraintData(ConstraintData):
541+
class TemplateDataMixin(object):
531542
__slots__ = ()
532543

533-
def __init__(self, template_info, component, index):
534-
# These lines represent in-lining of the
535-
# following constructors:
536-
# - ConstraintData,
537-
# - ActiveComponentData
538-
# - ComponentData
539-
self._component = component
540-
self._active = True
541-
self._index = index
542-
self._expr = template_info
543-
544544
@property
545545
def expr(self):
546546
# Note that it is faster to just generate the expression from
@@ -552,17 +552,41 @@ def template_expr(self):
552552
return self._expr
553553

554554
def set_value(self, expr):
555-
self.__class__ = ConstraintData
555+
# Setting a value will convert this instance from a templatized
556+
# type to the original Data type (and call the original set_value()).
557+
#
558+
# Note: We assume that the templatized type is created by
559+
# inheriting (TemplateDataMixin, <original data class>), and
560+
# that this instance doesn't have additional multiple
561+
# inheritance that could re-order the MRO.
562+
self.__class__ = self.__class__.__mro__[
563+
self.__class__.__mro__.index(TemplateDataMixin) + 1
564+
]
556565
return self.set_value(expr)
557566

558-
def to_bounded_expression(self):
567+
def to_bounded_expression(self, evaluate_bounds=False):
559568
tmp, self._expr = self._expr, self._expr[0]
560569
try:
561-
return super().to_bounded_expression()
570+
return super().to_bounded_expression(evaluate_bounds)
562571
finally:
563572
self._expr = tmp
564573

565574

575+
class TemplateConstraintData(TemplateDataMixin, ConstraintData):
576+
__slots__ = ()
577+
578+
def __init__(self, template_info, component, index):
579+
# These lines represent in-lining of the
580+
# following constructors:
581+
# - ConstraintData,
582+
# - ActiveComponentData
583+
# - ComponentData
584+
self._component = component
585+
self._active = True
586+
self._index = index
587+
self._expr = template_info
588+
589+
566590
@ModelComponentFactory.register("General constraint expressions.")
567591
class Constraint(ActiveIndexedComponent):
568592
"""
@@ -695,11 +719,17 @@ def construct(self, data=None):
695719
if TEMPLATIZE_CONSTRAINTS:
696720
try:
697721
template_info = templatize_constraint(self)
698-
comp = weakref_ref(self)
699-
self._data = {
700-
idx: TemplateConstraintData(template_info, comp, idx)
701-
for idx in self.index_set()
702-
}
722+
if self.is_indexed():
723+
comp = weakref_ref(self)
724+
self._data = {
725+
idx: TemplateConstraintData(template_info, comp, idx)
726+
for idx in self.index_set()
727+
}
728+
else:
729+
assert self.__class__ is ScalarConstraint
730+
self.__class__ = TemplateScalarConstraint
731+
self._expr = template_info
732+
self._data = {None: self}
703733
return
704734
except TemplateExpressionError:
705735
pass
@@ -926,6 +956,7 @@ class SimpleConstraint(metaclass=RenamedClass):
926956

927957
@disable_methods(
928958
{
959+
'__call__',
929960
'add',
930961
'set_value',
931962
'to_bounded_expression',
@@ -947,6 +978,10 @@ class AbstractSimpleConstraint(metaclass=RenamedClass):
947978
__renamed__version__ = '6.0'
948979

949980

981+
class TemplateScalarConstraint(TemplateDataMixin, ScalarConstraint):
982+
pass
983+
984+
950985
class IndexedConstraint(Constraint):
951986
#
952987
# Leaving this method for backward compatibility reasons

0 commit comments

Comments
 (0)