# ___________________________________________________________________________
#
# Pyomo: Python Optimization Modeling Objects
# Copyright (c) 2008-2022
# 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.
# ___________________________________________________________________________
#
# Utility functions
#
__all__ = ['sum_product', 'summation', 'dot_product', 'sequence', 'prod', 'quicksum', 'target_list']
from pyomo.core.expr.numvalue import native_numeric_types
from pyomo.core.expr.numeric_expr import decompose_term
from pyomo.core.expr import current as EXPR
from pyomo.core.base.var import Var
from pyomo.core.base.expression import Expression
from pyomo.core.base.component import _ComponentBase
import logging
logger = logging.getLogger(__name__)
[docs]def prod(terms):
"""
A utility function to compute the product of a list of terms.
Args:
terms (list): A list of terms that are multiplied together.
Returns:
The value of the product, which may be a Pyomo expression object.
"""
ans = 1
for term in terms:
ans *= term
return ans
[docs]def quicksum(args, start=0, linear=None):
"""
A utility function to compute a sum of Pyomo expressions.
The behavior of :func:`quicksum` is similar to the builtin :func:`sum`
function, but this function generates a more compact Pyomo
expression.
Args:
args: A generator for terms in the sum.
start: A value that is initializes the sum. If
this value is not a numeric constant, then the +=
operator is used to add terms to this object.
Defaults to zero.
linear: If :attr:`start` is not a numeric constant, then this
option is ignored. Otherwise, this value indicates
whether the terms in the sum are linear. If the value
is :const:`False`, then the terms are
treated as nonlinear, and if :const:`True`, then
the terms are treated as linear. Default is
:const:`None`, which indicates that the first term
in the :attr:`args` is used to determine this value.
Returns:
The value of the sum, which may be a Pyomo expression object.
"""
# Ensure that args is an iterator (this manages things like IndexedComponent_slice objects)
try:
args = iter(args)
except:
logger.error('The argument `args` to quicksum() is not iterable!')
raise
#
# If we're starting with a numeric value, then
# create a new nonlinear sum expression but
# return a static version to the user.
#
if start.__class__ in native_numeric_types:
if linear is None:
#
# Get the first term, which we will test for linearity
#
first = next(args, None)
if first is None:
return start
#
# Check if the first term is linear, and if so return the terms
#
linear, terms = decompose_term(first)
#
# Right now Pyomo5 expressions can only handle single linear
# terms.
#
# Also, we treat linear expressions as nonlinear if the constant
# term is not a native numeric type. Otherwise, large summation
# objects are created for the constant term.
#
if linear:
nvar=0
for term in terms:
c,v = term
if not v is None:
nvar += 1
elif not c.__class__ in native_numeric_types:
linear = False
if nvar > 1:
linear = False
start = start+first
if linear:
with EXPR.linear_expression() as e:
e += start
for arg in args:
e += arg
# Return the constant term if the linear expression does not
# contain variables
#
# getattr() because the linear expression may not have ended
# up being linear (and e could be a SumExpression)
if not getattr(e, 'linear_vars', True):
return e.constant
return e
else:
with EXPR.nonlinear_expression() as e:
e += start
for arg in args:
e += arg
if e.nargs() == 0:
return 0
elif e.nargs() == 1:
return e.arg(0)
return e
#
# Otherwise, use the context that is provided and return it.
#
e = start
for arg in args:
e += arg
return e
[docs]def sum_product(*args, **kwds):
"""
A utility function to compute a generalized dot product.
This function accepts one or more components that provide terms
that are multiplied together. These products are added together
to form a sum.
Args:
*args: Variable length argument list of generators that
create terms in the summation.
**kwds: Arbitrary keyword arguments.
Keyword Args:
index: A set that is used to index the components used to
create the terms
denom: A component or tuple of components that are used to
create the denominator of the terms
start: The initial value used in the sum
Returns:
The value of the sum.
"""
denom = kwds.pop('denom', tuple() )
if type(denom) not in (list, tuple):
denom = [denom]
nargs = len(args)
ndenom = len(denom)
if nargs == 0 and ndenom == 0:
raise ValueError("The sum_product() command requires at least an " + \
"argument or a denominator term")
if 'index' in kwds:
index=kwds['index']
else:
if nargs > 0:
iarg=args[-1]
if not isinstance(iarg,Var) and not isinstance(iarg, Expression):
raise ValueError("Error executing sum_product(): The last argument value must be a variable or expression object if no 'index' option is specified")
else:
iarg=denom[-1]
if not isinstance(iarg,Var) and not isinstance(iarg, Expression):
raise ValueError("Error executing sum_product(): The last denom argument value must be a variable or expression object if no 'index' option is specified")
index = iarg.index_set()
start = kwds.get("start", 0)
vars_ = []
params_ = []
for arg in args:
if isinstance(arg, Var):
vars_.append(arg)
else:
params_.append(arg)
nvars = len(vars_)
if ndenom == 0:
#
# Sum of polynomial terms
#
if start.__class__ in native_numeric_types:
if nvars == 1:
v = vars_[0]
if len(params_) == 0:
with EXPR.linear_expression() as expr:
expr += start
for i in index:
expr += v[i]
elif len(params_) == 1:
p = params_[0]
with EXPR.linear_expression() as expr:
expr += start
for i in index:
expr += p[i]*v[i]
else:
with EXPR.linear_expression() as expr:
expr += start
for i in index:
term = 1
for p in params_:
term *= p[i]
expr += term * v[i]
return expr
#
with EXPR.nonlinear_expression() as expr:
expr += start
for i in index:
term = 1
for arg in args:
term *= arg[i]
expr += term
return expr
#
return quicksum((prod(arg[i] for arg in args) for i in index), start)
elif nargs == 0:
#
# Sum of reciprocals
#
return quicksum((1/prod(den[i] for den in denom) for i in index), start)
else:
#
# Sum of fractions
#
return quicksum((
prod(arg[i] for arg in args) / prod(den[i] for den in denom)
for i in index), start)
#: An alias for :func:`sum_product <pyomo.core.expr.util>`
dot_product = sum_product
#: An alias for :func:`sum_product <pyomo.core.expr.util>`
summation = sum_product
def sequence(*args):
"""
sequence([start,] stop[, step]) -> generator for a list of integers
Return a generator that containing an arithmetic
progression of integers.
sequence(i, j) returns [i, i+1, i+2, ..., j];
start defaults to 1.
step specifies the increment (or decrement)
For example, sequence(4) returns [1, 2, 3, 4].
"""
if len(args) == 0:
raise ValueError('sequence expected at least 1 arguments, got 0')
if len(args) > 3:
raise ValueError('sequence expected at most 3 arguments, got %d' % len(args))
if len(args) == 1:
return range(1,args[0]+1)
if len(args) == 2:
return range(args[0],args[1]+1)
return range(args[0],args[1]+1,args[2])
def target_list(x):
if isinstance(x, _ComponentBase):
return [ x ]
elif hasattr(x, '__iter__'):
ans = []
for i in x:
if isinstance(i, _ComponentBase):
ans.append(i)
else:
raise ValueError(
"Expected Component or list of Components."
"\n\tReceived %s" % (type(i),))
return ans
else:
raise ValueError(
"Expected Component or list of Components."
"\n\tReceived %s" % (type(x),))