Skip to content

Commit

Permalink
Merge branch 'main' into lp-nl-cast-floats
Browse files Browse the repository at this point in the history
  • Loading branch information
jsiirola committed Aug 21, 2023
2 parents 996ac2b + c569b78 commit a5687bf
Show file tree
Hide file tree
Showing 10 changed files with 185 additions and 80 deletions.
7 changes: 0 additions & 7 deletions doc/OnlineDocs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,13 +146,6 @@

html_theme = 'sphinx_rtd_theme'

# Force HTML4: If we don't explicitly force HTML4, then the background
# of the Parameters/Returns/Return type headers is shaded the same as the
# method prototype (tested 15 April 21 with Sphinx=3.5.4 and
# sphinx-rtd-theme=0.5.2).
html4_writer = True
# html5_writer = True

if not on_rtd: # only import and set the theme if we're building docs locally
import sphinx_rtd_theme

Expand Down
9 changes: 5 additions & 4 deletions pyomo/contrib/fbbt/fbbt.py
Original file line number Diff line number Diff line change
Expand Up @@ -443,10 +443,11 @@ def _prop_bnds_leaf_to_root_GeneralExpression(node, bnds_dict, feasibility_tol):
region is removed due to floating point arithmetic and to prevent math domain errors (a larger value
is more conservative).
"""
if node.expr.__class__ in native_types:
expr_lb = expr_ub = node.expr
(expr,) = node.args
if expr.__class__ in native_types:
expr_lb = expr_ub = expr
else:
expr_lb, expr_ub = bnds_dict[node.expr]
expr_lb, expr_ub = bnds_dict[expr]
bnds_dict[node] = (expr_lb, expr_ub)


Expand Down Expand Up @@ -1138,7 +1139,7 @@ def visiting_potential_leaf(self, node):
ub = interval.inf
if lb - self.feasibility_tol > ub:
raise InfeasibleConstraintException(
'Variable has a lower bound which is larger than its upper bound: {0}'.format(
'Variable has a lower bound that is larger than its upper bound: {0}'.format(
str(node)
)
)
Expand Down
28 changes: 20 additions & 8 deletions pyomo/contrib/pynumero/sparse/mpi_block_vector.py
Original file line number Diff line number Diff line change
Expand Up @@ -567,10 +567,16 @@ def min(self, axis=None, out=None, keepdims=False):
assert_block_structure(self)
local_min = np.inf
for i in self._owned_blocks:
lmin = self._block_vector.get_block(i).min()
if lmin <= local_min:
local_min = lmin
return self._mpiw.allreduce(local_min, op=mpi4py.MPI.MIN)
block = self._block_vector.get_block(i)
if block.size > 0:
lmin = block.min()
if lmin <= local_min:
local_min = lmin
res = self._mpiw.allreduce(local_min, op=mpi4py.MPI.MIN)
if res == np.inf:
if self.size == 0:
raise ValueError('cannot get the min of a size 0 array')
return res

def max(self, axis=None, out=None, keepdims=False):
"""
Expand All @@ -580,10 +586,16 @@ def max(self, axis=None, out=None, keepdims=False):
assert_block_structure(self)
local_max = -np.inf
for i in self._owned_blocks:
lmax = self._block_vector.get_block(i).max()
if lmax >= local_max:
local_max = lmax
return self._mpiw.allreduce(local_max, op=mpi4py.MPI.MAX)
block = self._block_vector.get_block(i)
if block.size > 0:
lmax = block.max()
if lmax >= local_max:
local_max = lmax
res = self._mpiw.allreduce(local_max, op=mpi4py.MPI.MAX)
if res == -np.inf:
if self.size == 0:
raise ValueError('cannot get the max of a size 0 array')
return res

def sum(self, axis=None, dtype=None, out=None, keepdims=False):
"""
Expand Down
29 changes: 29 additions & 0 deletions pyomo/contrib/pynumero/sparse/tests/test_mpi_block_vector.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,35 @@ def test_min(self):
self.assertEqual(self.v1.min(), 0.0)
self.assertEqual(self.v2.min(), 0.0)

def test_min_max_with_size0_blocks(self):
v = MPIBlockVector(3, [0, 1, 2], comm)
rank = comm.Get_rank()
if rank == 0:
v.set_block(0, np.array([8, 4, 7, 12]))
if rank == 1:
v.set_block(1, np.array([]))
if rank == 2:
v.set_block(2, np.array([5, 6, 3]))
self.assertAlmostEqual(v.min(), 3)
self.assertAlmostEqual(v.max(), 12)

if rank == 0:
v.set_block(0, np.array([np.inf, np.inf, np.inf, np.inf]))
if rank == 2:
v.set_block(2, np.array([np.inf, np.inf, np.inf]))
self.assertEqual(v.min(), np.inf)
self.assertEqual(v.max(), np.inf)
v *= -1
self.assertEqual(v.min(), -np.inf)
self.assertEqual(v.max(), -np.inf)

v = MPIBlockVector(3, [0, 1, 2], comm)
v.set_block(rank, np.array([]))
with self.assertRaisesRegex(ValueError, 'cannot get the min of a size 0 array'):
v.min()
with self.assertRaisesRegex(ValueError, 'cannot get the max of a size 0 array'):
v.max()

def test_max(self):
v = MPIBlockVector(2, [0, 1], comm)
rank = comm.Get_rank()
Expand Down
85 changes: 45 additions & 40 deletions pyomo/core/base/expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,11 @@ class _ExpressionData(numeric_expr.NumericValue):

def __call__(self, exception=True):
"""Compute the value of this expression."""
if self.expr.__class__ in native_types:
(arg,) = self._args_
if arg.__class__ in native_types:
# Note: native_types includes NoneType
return self.expr
return self.expr(exception=exception)
return arg
return arg(exception=exception)

def is_named_expression_type(self):
"""A boolean indicating whether this in a named expression."""
Expand All @@ -73,25 +74,21 @@ def is_expression_type(self, expression_system=None):
return expression_system is None or expression_system == self.EXPRESSION_SYSTEM

def arg(self, index):
if index < 0 or index >= 1:
if index != 0:
raise KeyError("Invalid index for expression argument: %d" % index)
return self.expr

@property
def _args_(self):
return (self.expr,)
return self._args_[0]

@property
def args(self):
return (self.expr,)
return self._args_

def nargs(self):
return 1

def _to_string(self, values, verbose, smap):
if verbose:
return "%s{%s}" % (str(self), values[0])
if self.expr is None:
if self._args_[0] is None:
return "%s{None}" % str(self)
return values[0]

Expand All @@ -106,8 +103,8 @@ def _apply_operation(self, result):

def polynomial_degree(self):
"""A tuple of subexpressions involved in this expressions operation."""
if self.expr.__class__ in native_types:
return 0
if self._args_[0] is None:
return None
return self.expr.polynomial_degree()

def _compute_polynomial_degree(self, result):
Expand All @@ -122,8 +119,14 @@ def _is_fixed(self, values):

@property
def expr(self):
"""Return expression on this expression."""
raise NotImplementedError
(arg,) = self._args_
if arg is None:
return None
return as_numeric(arg)

@expr.setter
def expr(self, value):
self.set_value(value)

def set_value(self, expr):
"""Set the expression on this expression."""
Expand Down Expand Up @@ -158,7 +161,7 @@ class _GeneralExpressionDataImpl(_ExpressionData):
__slots__ = ()

def __init__(self, expr=None):
self._expr = as_numeric(expr) if (expr is not None) else None
self._args_ = (expr,)

def create_node_with_local_data(self, values):
"""
Expand All @@ -170,34 +173,25 @@ def create_node_with_local_data(self, values):
"""
obj = ScalarExpression()
obj.construct()
obj.expr = values[0]
obj._args_ = values
return obj

#
# Abstract Interface
#

@property
def expr(self):
"""Return expression on this expression."""
return self._expr

@expr.setter
def expr(self, expr):
self.set_value(expr)

def set_value(self, expr):
"""Set the expression on this expression."""
if expr is None or expr.__class__ in native_numeric_types:
self._expr = expr
self._args_ = (expr,)
return
try:
if expr.is_numeric_type():
self._expr = expr
self._args_ = (expr,)
return
except AttributeError:
if check_if_numeric_type(expr):
self._expr = expr
self._args_ = (expr,)
return
raise ValueError(
f"Cannot assign {expr.__class__.__name__} to "
Expand All @@ -213,33 +207,34 @@ def is_constant(self):

def is_fixed(self):
"""A boolean indicating whether this expression is fixed."""
return self._expr.__class__ in native_types or self._expr.is_fixed()
(e,) = self._args_
return e.__class__ in native_types or e.is_fixed()

# Override the in-place operators here so that we can redirect the
# dispatcher based on the current contained expression type and not
# this Expression object (which would map to "other")

def __iadd__(self, other):
e = self._expr
(e,) = self._args_
return numeric_expr._add_dispatcher[e.__class__, other.__class__](e, other)

# Note: the default implementation of __isub__ leverages __iadd__
# and doesn't need to be reimplemented here

def __imul__(self, other):
e = self._expr
(e,) = self._args_
return numeric_expr._mul_dispatcher[e.__class__, other.__class__](e, other)

def __idiv__(self, other):
e = self._expr
(e,) = self._args_
return numeric_expr._div_dispatcher[e.__class__, other.__class__](e, other)

def __itruediv__(self, other):
e = self._expr
(e,) = self._args_
return numeric_expr._div_dispatcher[e.__class__, other.__class__](e, other)

def __ipow__(self, other):
e = self._expr
(e,) = self._args_
return numeric_expr._pow_dispatcher[e.__class__, other.__class__](e, other)


Expand All @@ -258,7 +253,7 @@ class _GeneralExpressionData(_GeneralExpressionDataImpl, ComponentData):
_component The expression component.
"""

__slots__ = ('_expr',)
__slots__ = ('_args_',)

def __init__(self, expr=None, component=None):
_GeneralExpressionDataImpl.__init__(self, expr)
Expand Down Expand Up @@ -418,13 +413,23 @@ def __init__(self, *args, **kwds):
# construction
#

def __call__(self, exception=True):
"""Return expression on this expression."""
if self._constructed:
return super().__call__(exception)
raise ValueError(
"Evaluating the expression of Expression '%s' "
"before the Expression has been constructed (there "
"is currently no value to return)." % (self.name)
)

@property
def expr(self):
"""Return expression on this expression."""
if self._constructed:
return _GeneralExpressionData.expr.fget(self)
raise ValueError(
"Accessing the expression of expression '%s' "
"Accessing the expression of Expression '%s' "
"before the Expression has been constructed (there "
"is currently no value to return)." % (self.name)
)
Expand All @@ -442,7 +447,7 @@ def set_value(self, expr):
if self._constructed:
return _GeneralExpressionData.set_value(self, expr)
raise ValueError(
"Setting the expression of expression '%s' "
"Setting the expression of Expression '%s' "
"before the Expression has been constructed (there "
"is currently no object to set)." % (self.name)
)
Expand All @@ -452,7 +457,7 @@ def is_constant(self):
if self._constructed:
return _GeneralExpressionData.is_constant(self)
raise ValueError(
"Accessing the is_constant flag of expression '%s' "
"Accessing the is_constant flag of Expression '%s' "
"before the Expression has been constructed (there "
"is currently no value to return)." % (self.name)
)
Expand All @@ -462,7 +467,7 @@ def is_fixed(self):
if self._constructed:
return _GeneralExpressionData.is_fixed(self)
raise ValueError(
"Accessing the is_fixed flag of expression '%s' "
"Accessing the is_fixed flag of Expression '%s' "
"before the Expression has been constructed (there "
"is currently no value to return)." % (self.name)
)
Expand Down
5 changes: 4 additions & 1 deletion pyomo/core/base/initializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,10 @@ def Initializer(
# 'int'). We will just have to assume this is a "normal"
# IndexedCallInitializer
return IndexedCallInitializer(arg)
if len(_args.args) - len(arg.args) == 1 and _args.varargs is None:
_positional_args = set(_args.args)
for key in arg.keywords:
_positional_args.discard(key)
if len(_positional_args) - len(arg.args) == 1 and _args.varargs is None:
return ScalarCallInitializer(arg)
else:
return IndexedCallInitializer(arg)
Expand Down
Loading

0 comments on commit a5687bf

Please sign in to comment.