We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
'quicksum' hides exceptions raised in generator from the user, leading to a wrong model
## Summary
If the generator passed to `quicksum` raises an exception at the first call to __next__, the exception handling logic of `quicksum` will hide the exception from the user and return an expression as `0`.  This behavior of `quicksum` may produce a wrong model unintentionally.

The example below shows that the user misspells `param.aaa` as `param.bbb` in the expression,  in fact it is a non-exsiting attribute of the class. However, the code can run fluently without any exception occurs.  

### Steps to reproduce the issue

```python
import pyomo.environ as pyo

class Param:
    def __init__(self) -> None:
        self.aaa = [1.1, 2.2, 3.3]

param = Param()

m = pyo.ConcreteModel()
m.i = pyo.RangeSet(0, 3 - 1)
m.x = pyo.Var(m.i)

m.expr = pyo.Expression(expr=pyo.quicksum(m.x[i] * param.aaa[i] for i in m.i))
m.expr.pprint()

m.expr2 = pyo.Expression(expr=pyo.quicksum(m.x[i] * param.bbb[i] for i in m.i))
m.expr2.pprint()
```
### Error Message
```console
expr : Size=1, Index=None
    Key  : Expression
    None : 1.1*x[0] + 2.2*x[1] + 3.3*x[2]
expr2 : Size=1, Index=None
    Key  : Expression
    None :        0.0
```


### Information on your system

Pyomo version: 6.4.1
Python version: 3.9.12
Operating system: Windows 11
How Pyomo was installed (PyPI, conda, source): conda



--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `pyomo/core/util.py`
Content:
```
1 #  ___________________________________________________________________________
2 #
3 #  Pyomo: Python Optimization Modeling Objects
4 #  Copyright (c) 2008-2022
5 #  National Technology and Engineering Solutions of Sandia, LLC
6 #  Under the terms of Contract DE-NA0003525 with National Technology and
7 #  Engineering Solutions of Sandia, LLC, the U.S. Government retains certain
8 #  rights in this software.
9 #  This software is distributed under the 3-clause BSD License.
10 #  ___________________________________________________________________________
11 
12 #
13 # Utility functions
14 #
15 
16 __all__ = ['sum_product', 'summation', 'dot_product', 'sequence', 'prod', 'quicksum', 'target_list']
17 
18 from pyomo.core.expr.numvalue import native_numeric_types
19 from pyomo.core.expr.numeric_expr import decompose_term
20 from pyomo.core.expr import current as EXPR
21 from pyomo.core.base.var import Var
22 from pyomo.core.base.expression import Expression
23 from pyomo.core.base.component import _ComponentBase
24 
25 def prod(terms):
26     """
27     A utility function to compute the product of a list of terms.
28 
29     Args:
30         terms (list): A list of terms that are multiplied together.
31 
32     Returns:
33         The value of the product, which may be a Pyomo expression object.
34     """
35     ans = 1
36     for term in terms:
37         ans *= term
38     return ans
39 
40 
41 def quicksum(args, start=0, linear=None):
42     """
43     A utility function to compute a sum of Pyomo expressions.
44 
45     The behavior of :func:`quicksum` is similar to the builtin :func:`sum`
46     function, but this function generates a more compact Pyomo
47     expression.
48 
49     Args:
50         args: A generator for terms in the sum.
51 
52         start: A value that is initializes the sum.  If
53             this value is not a numeric constant, then the += 
54             operator is used to add terms to this object.
55             Defaults to zero.
56 
57         linear: If :attr:`start` is not a numeric constant, then this 
58             option is ignored.  Otherwise, this value indicates
59             whether the terms in the sum are linear.  If the value
60             is :const:`False`, then the terms are
61             treated as nonlinear, and if :const:`True`, then
62             the terms are treated as linear.  Default is
63             :const:`None`, which indicates that the first term
64             in the :attr:`args` is used to determine this value.
65 
66     Returns:
67         The value of the sum, which may be a Pyomo expression object.
68     """
69     #
70     # If we're starting with a numeric value, then 
71     # create a new nonlinear sum expression but 
72     # return a static version to the user.
73     #
74     if start.__class__ in native_numeric_types:
75         if linear is None:
76             #
77             # Get the first term, which we will test for linearity
78             #
79             try:
80                 first = next(args, None)
81             except:
82                 try:
83                     args = args.__iter__()
84                     first = next(args, None)
85                 except:
86                     raise RuntimeError("The argument to quicksum() is not iterable!")
87             if first is None:
88                 return start
89             #
90             # Check if the first term is linear, and if so return the terms
91             #
92             linear, terms = decompose_term(first)
93             #
94             # Right now Pyomo5 expressions can only handle single linear
95             # terms.
96             #
97             # Also, we treat linear expressions as nonlinear if the constant
98             # term is not a native numeric type.  Otherwise, large summation
99             # objects are created for the constant term.
100             #
101             if linear:
102                 nvar=0
103                 for term in terms:
104                     c,v = term
105                     if not v is None:
106                         nvar += 1
107                     elif not c.__class__ in native_numeric_types:
108                         linear = False
109                 if nvar > 1:
110                     linear = False
111             start = start+first
112         if linear:
113             with EXPR.linear_expression() as e:
114                 e += start
115                 for arg in args:
116                     e += arg
117             # Return the constant term if the linear expression does not contains variables
118             if e.is_constant():
119                 return e.constant
120             return e
121         else:
122             with EXPR.nonlinear_expression() as e:
123                 e += start
124                 for arg in args:
125                     e += arg
126             if e.nargs() == 0:
127                 return 0
128             elif e.nargs() == 1:
129                 return e.arg(0)
130             return e
131     #
132     # Otherwise, use the context that is provided and return it.
133     #
134     e = start
135     for arg in args:
136         e += arg
137     return e
138 
139 
140 def sum_product(*args, **kwds):
141     """
142     A utility function to compute a generalized dot product.  
143 
144     This function accepts one or more components that provide terms
145     that are multiplied together.  These products are added together
146     to form a sum.
147 
148     Args:
149         *args: Variable length argument list of generators that
150             create terms in the summation.
151         **kwds: Arbitrary keyword arguments.
152 
153     Keyword Args:
154         index: A set that is used to index the components used to
155             create the terms
156         denom: A component or tuple of components that are used to
157             create the denominator of the terms
158         start: The initial value used in the sum
159 
160     Returns:
161         The value of the sum.
162     """
163     denom = kwds.pop('denom', tuple() )
164     if type(denom) not in (list, tuple):
165         denom = [denom]
166     nargs = len(args)
167     ndenom = len(denom)
168 
169     if nargs == 0 and ndenom == 0:
170         raise ValueError("The sum_product() command requires at least an " + \
171               "argument or a denominator term")
172 
173     if 'index' in kwds:
174         index=kwds['index']
175     else:
176         if nargs > 0:
177             iarg=args[-1]
178             if not isinstance(iarg,Var) and not isinstance(iarg, Expression):
179                 raise ValueError("Error executing sum_product(): The last argument value must be a variable or expression object if no 'index' option is specified")
180         else:
181             iarg=denom[-1]
182             if not isinstance(iarg,Var) and not isinstance(iarg, Expression):
183                 raise ValueError("Error executing sum_product(): The last denom argument value must be a variable or expression object if no 'index' option is specified")
184         index = iarg.index_set()
185 
186     start = kwds.get("start", 0)
187     vars_ = []
188     params_ = []
189     for arg in args:
190         if isinstance(arg, Var):
191             vars_.append(arg)
192         else:
193             params_.append(arg)
194     nvars = len(vars_)
195 
196     num_index = range(0,nargs)
197     if ndenom == 0:
198         #
199         # Sum of polynomial terms
200         #
201         if start.__class__ in native_numeric_types:
202             if nvars == 1:
203                 v = vars_[0]
204                 if len(params_) == 0:
205                     with EXPR.linear_expression() as expr:
206                         expr += start
207                         for i in index:
208                             expr += v[i]
209                 elif len(params_) == 1:    
210                     p = params_[0]
211                     with EXPR.linear_expression() as expr:
212                         expr += start
213                         for i in index:
214                             expr += p[i]*v[i]
215                 else:
216                     with EXPR.linear_expression() as expr:
217                         expr += start
218                         for i in index:
219                             term = 1
220                             for j in params_:
221                                 term *= params_[j][i]
222                             expr += term * v[i]
223                 return expr
224             #
225             with EXPR.nonlinear_expression() as expr:
226                 expr += start
227                 for i in index:
228                     term = 1
229                     for j in num_index:
230                         term *= args[j][i]
231                     expr += term
232             return expr
233         #
234         return quicksum((prod(args[j][i] for j in num_index) for i in index), start)
235     elif nargs == 0:
236         #
237         # Sum of reciprocals
238         #
239         denom_index = range(0,ndenom)
240         return quicksum((1/prod(denom[j][i] for j in denom_index) for i in index), start)
241     else:
242         #
243         # Sum of fractions
244         #
245         denom_index = range(0,ndenom)
246         return quicksum((prod(args[j][i] for j in num_index)/prod(denom[j][i] for j in denom_index) for i in index), start)
247 
248 
249 #: An alias for :func:`sum_product <pyomo.core.expr.util>`
250 dot_product = sum_product
251 
252 #: An alias for :func:`sum_product <pyomo.core.expr.util>`
253 summation = sum_product
254 
255 
256 def sequence(*args):
257     """
258     sequence([start,] stop[, step]) -> generator for a list of integers
259 
260     Return a generator that containing an arithmetic
261     progression of integers.  
262        sequence(i, j) returns [i, i+1, i+2, ..., j]; 
263        start defaults to 1.  
264        step specifies the increment (or decrement)
265     For example, sequence(4) returns [1, 2, 3, 4].
266     """
267     if len(args) == 0:
268         raise ValueError('sequence expected at least 1 arguments, got 0')
269     if len(args) > 3:
270         raise ValueError('sequence expected at most 3 arguments, got %d' % len(args))
271     if len(args) == 1:
272         return range(1,args[0]+1)
273     if len(args) == 2:
274         return range(args[0],args[1]+1)
275     return range(args[0],args[1]+1,args[2])
276 
277 def target_list(x):
278     if isinstance(x, _ComponentBase):
279         return [ x ]
280     elif hasattr(x, '__iter__'):
281         ans = []
282         for i in x:
283             if isinstance(i, _ComponentBase):
284                 ans.append(i)
285             else:
286                 raise ValueError(
287                     "Expected Component or list of Components."
288                     "\n\tReceived %s" % (type(i),))
289         return ans
290     else:
291         raise ValueError(
292             "Expected Component or list of Components."
293             "\n\tReceived %s" % (type(x),))
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/pyomo/core/util.py b/pyomo/core/util.py
--- a/pyomo/core/util.py
+++ b/pyomo/core/util.py
@@ -21,6 +21,8 @@
 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__)
 
 def prod(terms):
     """
@@ -66,6 +68,14 @@
     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 
@@ -76,14 +86,7 @@
             #
             # Get the first term, which we will test for linearity
             #
-            try:
-                first = next(args, None)
-            except:
-                try:
-                    args = args.__iter__()
-                    first = next(args, None)
-                except:
-                    raise RuntimeError("The argument to quicksum() is not iterable!")
+            first = next(args, None)
             if first is None:
                 return start
             #

{"golden_diff": "diff --git a/pyomo/core/util.py b/pyomo/core/util.py\n--- a/pyomo/core/util.py\n+++ b/pyomo/core/util.py\n@@ -21,6 +21,8 @@\n from pyomo.core.base.var import Var\n from pyomo.core.base.expression import Expression\n from pyomo.core.base.component import _ComponentBase\n+import logging\n+logger = logging.getLogger(__name__)\n \n def prod(terms):\n     \"\"\"\n@@ -66,6 +68,14 @@\n     Returns:\n         The value of the sum, which may be a Pyomo expression object.\n     \"\"\"\n+\n+    # Ensure that args is an iterator (this manages things like IndexedComponent_slice objects)\n+    try:\n+        args = iter(args)\n+    except:\n+        logger.error('The argument `args` to quicksum() is not iterable!')\n+        raise\n+\n     #\n     # If we're starting with a numeric value, then \n     # create a new nonlinear sum expression but \n@@ -76,14 +86,7 @@\n             #\n             # Get the first term, which we will test for linearity\n             #\n-            try:\n-                first = next(args, None)\n-            except:\n-                try:\n-                    args = args.__iter__()\n-                    first = next(args, None)\n-                except:\n-                    raise RuntimeError(\"The argument to quicksum() is not iterable!\")\n+            first = next(args, None)\n             if first is None:\n                 return start\n             #\n", "issue": "'quicksum' hides exceptions raised in generator from the user, leading to a wrong model\n## Summary\r\nIf the generator passed to `quicksum` raises an exception at the first call to __next__, the exception handling logic of `quicksum` will hide the exception from the user and return an expression as `0`.  This behavior of `quicksum` may produce a wrong model unintentionally.\r\n\r\nThe example below shows that the user misspells `param.aaa` as `param.bbb` in the expression,  in fact it is a non-exsiting attribute of the class. However, the code can run fluently without any exception occurs.  \r\n\r\n### Steps to reproduce the issue\r\n\r\n```python\r\nimport pyomo.environ as pyo\r\n\r\nclass Param:\r\n    def __init__(self) -> None:\r\n        self.aaa = [1.1, 2.2, 3.3]\r\n\r\nparam = Param()\r\n\r\nm = pyo.ConcreteModel()\r\nm.i = pyo.RangeSet(0, 3 - 1)\r\nm.x = pyo.Var(m.i)\r\n\r\nm.expr = pyo.Expression(expr=pyo.quicksum(m.x[i] * param.aaa[i] for i in m.i))\r\nm.expr.pprint()\r\n\r\nm.expr2 = pyo.Expression(expr=pyo.quicksum(m.x[i] * param.bbb[i] for i in m.i))\r\nm.expr2.pprint()\r\n```\r\n### Error Message\r\n```console\r\nexpr : Size=1, Index=None\r\n    Key  : Expression\r\n    None : 1.1*x[0] + 2.2*x[1] + 3.3*x[2]\r\nexpr2 : Size=1, Index=None\r\n    Key  : Expression\r\n    None :        0.0\r\n```\r\n\r\n\r\n### Information on your system\r\n\r\nPyomo version: 6.4.1\r\nPython version: 3.9.12\r\nOperating system: Windows 11\r\nHow Pyomo was installed (PyPI, conda, source): conda\r\n\r\n\n", "before_files": [{"content": "#  ___________________________________________________________________________\n#\n#  Pyomo: Python Optimization Modeling Objects\n#  Copyright (c) 2008-2022\n#  National Technology and Engineering Solutions of Sandia, LLC\n#  Under the terms of Contract DE-NA0003525 with National Technology and\n#  Engineering Solutions of Sandia, LLC, the U.S. Government retains certain\n#  rights in this software.\n#  This software is distributed under the 3-clause BSD License.\n#  ___________________________________________________________________________\n\n#\n# Utility functions\n#\n\n__all__ = ['sum_product', 'summation', 'dot_product', 'sequence', 'prod', 'quicksum', 'target_list']\n\nfrom pyomo.core.expr.numvalue import native_numeric_types\nfrom pyomo.core.expr.numeric_expr import decompose_term\nfrom pyomo.core.expr import current as EXPR\nfrom pyomo.core.base.var import Var\nfrom pyomo.core.base.expression import Expression\nfrom pyomo.core.base.component import _ComponentBase\n\ndef prod(terms):\n    \"\"\"\n    A utility function to compute the product of a list of terms.\n\n    Args:\n        terms (list): A list of terms that are multiplied together.\n\n    Returns:\n        The value of the product, which may be a Pyomo expression object.\n    \"\"\"\n    ans = 1\n    for term in terms:\n        ans *= term\n    return ans\n\n\ndef quicksum(args, start=0, linear=None):\n    \"\"\"\n    A utility function to compute a sum of Pyomo expressions.\n\n    The behavior of :func:`quicksum` is similar to the builtin :func:`sum`\n    function, but this function generates a more compact Pyomo\n    expression.\n\n    Args:\n        args: A generator for terms in the sum.\n\n        start: A value that is initializes the sum.  If\n            this value is not a numeric constant, then the += \n            operator is used to add terms to this object.\n            Defaults to zero.\n\n        linear: If :attr:`start` is not a numeric constant, then this \n            option is ignored.  Otherwise, this value indicates\n            whether the terms in the sum are linear.  If the value\n            is :const:`False`, then the terms are\n            treated as nonlinear, and if :const:`True`, then\n            the terms are treated as linear.  Default is\n            :const:`None`, which indicates that the first term\n            in the :attr:`args` is used to determine this value.\n\n    Returns:\n        The value of the sum, which may be a Pyomo expression object.\n    \"\"\"\n    #\n    # If we're starting with a numeric value, then \n    # create a new nonlinear sum expression but \n    # return a static version to the user.\n    #\n    if start.__class__ in native_numeric_types:\n        if linear is None:\n            #\n            # Get the first term, which we will test for linearity\n            #\n            try:\n                first = next(args, None)\n            except:\n                try:\n                    args = args.__iter__()\n                    first = next(args, None)\n                except:\n                    raise RuntimeError(\"The argument to quicksum() is not iterable!\")\n            if first is None:\n                return start\n            #\n            # Check if the first term is linear, and if so return the terms\n            #\n            linear, terms = decompose_term(first)\n            #\n            # Right now Pyomo5 expressions can only handle single linear\n            # terms.\n            #\n            # Also, we treat linear expressions as nonlinear if the constant\n            # term is not a native numeric type.  Otherwise, large summation\n            # objects are created for the constant term.\n            #\n            if linear:\n                nvar=0\n                for term in terms:\n                    c,v = term\n                    if not v is None:\n                        nvar += 1\n                    elif not c.__class__ in native_numeric_types:\n                        linear = False\n                if nvar > 1:\n                    linear = False\n            start = start+first\n        if linear:\n            with EXPR.linear_expression() as e:\n                e += start\n                for arg in args:\n                    e += arg\n            # Return the constant term if the linear expression does not contains variables\n            if e.is_constant():\n                return e.constant\n            return e\n        else:\n            with EXPR.nonlinear_expression() as e:\n                e += start\n                for arg in args:\n                    e += arg\n            if e.nargs() == 0:\n                return 0\n            elif e.nargs() == 1:\n                return e.arg(0)\n            return e\n    #\n    # Otherwise, use the context that is provided and return it.\n    #\n    e = start\n    for arg in args:\n        e += arg\n    return e\n\n\ndef sum_product(*args, **kwds):\n    \"\"\"\n    A utility function to compute a generalized dot product.  \n\n    This function accepts one or more components that provide terms\n    that are multiplied together.  These products are added together\n    to form a sum.\n\n    Args:\n        *args: Variable length argument list of generators that\n            create terms in the summation.\n        **kwds: Arbitrary keyword arguments.\n\n    Keyword Args:\n        index: A set that is used to index the components used to\n            create the terms\n        denom: A component or tuple of components that are used to\n            create the denominator of the terms\n        start: The initial value used in the sum\n\n    Returns:\n        The value of the sum.\n    \"\"\"\n    denom = kwds.pop('denom', tuple() )\n    if type(denom) not in (list, tuple):\n        denom = [denom]\n    nargs = len(args)\n    ndenom = len(denom)\n\n    if nargs == 0 and ndenom == 0:\n        raise ValueError(\"The sum_product() command requires at least an \" + \\\n              \"argument or a denominator term\")\n\n    if 'index' in kwds:\n        index=kwds['index']\n    else:\n        if nargs > 0:\n            iarg=args[-1]\n            if not isinstance(iarg,Var) and not isinstance(iarg, Expression):\n                raise ValueError(\"Error executing sum_product(): The last argument value must be a variable or expression object if no 'index' option is specified\")\n        else:\n            iarg=denom[-1]\n            if not isinstance(iarg,Var) and not isinstance(iarg, Expression):\n                raise ValueError(\"Error executing sum_product(): The last denom argument value must be a variable or expression object if no 'index' option is specified\")\n        index = iarg.index_set()\n\n    start = kwds.get(\"start\", 0)\n    vars_ = []\n    params_ = []\n    for arg in args:\n        if isinstance(arg, Var):\n            vars_.append(arg)\n        else:\n            params_.append(arg)\n    nvars = len(vars_)\n\n    num_index = range(0,nargs)\n    if ndenom == 0:\n        #\n        # Sum of polynomial terms\n        #\n        if start.__class__ in native_numeric_types:\n            if nvars == 1:\n                v = vars_[0]\n                if len(params_) == 0:\n                    with EXPR.linear_expression() as expr:\n                        expr += start\n                        for i in index:\n                            expr += v[i]\n                elif len(params_) == 1:    \n                    p = params_[0]\n                    with EXPR.linear_expression() as expr:\n                        expr += start\n                        for i in index:\n                            expr += p[i]*v[i]\n                else:\n                    with EXPR.linear_expression() as expr:\n                        expr += start\n                        for i in index:\n                            term = 1\n                            for j in params_:\n                                term *= params_[j][i]\n                            expr += term * v[i]\n                return expr\n            #\n            with EXPR.nonlinear_expression() as expr:\n                expr += start\n                for i in index:\n                    term = 1\n                    for j in num_index:\n                        term *= args[j][i]\n                    expr += term\n            return expr\n        #\n        return quicksum((prod(args[j][i] for j in num_index) for i in index), start)\n    elif nargs == 0:\n        #\n        # Sum of reciprocals\n        #\n        denom_index = range(0,ndenom)\n        return quicksum((1/prod(denom[j][i] for j in denom_index) for i in index), start)\n    else:\n        #\n        # Sum of fractions\n        #\n        denom_index = range(0,ndenom)\n        return quicksum((prod(args[j][i] for j in num_index)/prod(denom[j][i] for j in denom_index) for i in index), start)\n\n\n#: An alias for :func:`sum_product <pyomo.core.expr.util>`\ndot_product = sum_product\n\n#: An alias for :func:`sum_product <pyomo.core.expr.util>`\nsummation = sum_product\n\n\ndef sequence(*args):\n    \"\"\"\n    sequence([start,] stop[, step]) -> generator for a list of integers\n\n    Return a generator that containing an arithmetic\n    progression of integers.  \n       sequence(i, j) returns [i, i+1, i+2, ..., j]; \n       start defaults to 1.  \n       step specifies the increment (or decrement)\n    For example, sequence(4) returns [1, 2, 3, 4].\n    \"\"\"\n    if len(args) == 0:\n        raise ValueError('sequence expected at least 1 arguments, got 0')\n    if len(args) > 3:\n        raise ValueError('sequence expected at most 3 arguments, got %d' % len(args))\n    if len(args) == 1:\n        return range(1,args[0]+1)\n    if len(args) == 2:\n        return range(args[0],args[1]+1)\n    return range(args[0],args[1]+1,args[2])\n\ndef target_list(x):\n    if isinstance(x, _ComponentBase):\n        return [ x ]\n    elif hasattr(x, '__iter__'):\n        ans = []\n        for i in x:\n            if isinstance(i, _ComponentBase):\n                ans.append(i)\n            else:\n                raise ValueError(\n                    \"Expected Component or list of Components.\"\n                    \"\\n\\tReceived %s\" % (type(i),))\n        return ans\n    else:\n        raise ValueError(\n            \"Expected Component or list of Components.\"\n            \"\\n\\tReceived %s\" % (type(x),))", "path": "pyomo/core/util.py"}], "after_files": [{"content": "#  ___________________________________________________________________________\n#\n#  Pyomo: Python Optimization Modeling Objects\n#  Copyright (c) 2008-2022\n#  National Technology and Engineering Solutions of Sandia, LLC\n#  Under the terms of Contract DE-NA0003525 with National Technology and\n#  Engineering Solutions of Sandia, LLC, the U.S. Government retains certain\n#  rights in this software.\n#  This software is distributed under the 3-clause BSD License.\n#  ___________________________________________________________________________\n\n#\n# Utility functions\n#\n\n__all__ = ['sum_product', 'summation', 'dot_product', 'sequence', 'prod', 'quicksum', 'target_list']\n\nfrom pyomo.core.expr.numvalue import native_numeric_types\nfrom pyomo.core.expr.numeric_expr import decompose_term\nfrom pyomo.core.expr import current as EXPR\nfrom pyomo.core.base.var import Var\nfrom pyomo.core.base.expression import Expression\nfrom pyomo.core.base.component import _ComponentBase\nimport logging\nlogger = logging.getLogger(__name__)\n\ndef prod(terms):\n    \"\"\"\n    A utility function to compute the product of a list of terms.\n\n    Args:\n        terms (list): A list of terms that are multiplied together.\n\n    Returns:\n        The value of the product, which may be a Pyomo expression object.\n    \"\"\"\n    ans = 1\n    for term in terms:\n        ans *= term\n    return ans\n\n\ndef quicksum(args, start=0, linear=None):\n    \"\"\"\n    A utility function to compute a sum of Pyomo expressions.\n\n    The behavior of :func:`quicksum` is similar to the builtin :func:`sum`\n    function, but this function generates a more compact Pyomo\n    expression.\n\n    Args:\n        args: A generator for terms in the sum.\n\n        start: A value that is initializes the sum.  If\n            this value is not a numeric constant, then the += \n            operator is used to add terms to this object.\n            Defaults to zero.\n\n        linear: If :attr:`start` is not a numeric constant, then this \n            option is ignored.  Otherwise, this value indicates\n            whether the terms in the sum are linear.  If the value\n            is :const:`False`, then the terms are\n            treated as nonlinear, and if :const:`True`, then\n            the terms are treated as linear.  Default is\n            :const:`None`, which indicates that the first term\n            in the :attr:`args` is used to determine this value.\n\n    Returns:\n        The value of the sum, which may be a Pyomo expression object.\n    \"\"\"\n\n    # Ensure that args is an iterator (this manages things like IndexedComponent_slice objects)\n    try:\n        args = iter(args)\n    except:\n        logger.error('The argument `args` to quicksum() is not iterable!')\n        raise\n\n    #\n    # If we're starting with a numeric value, then \n    # create a new nonlinear sum expression but \n    # return a static version to the user.\n    #\n    if start.__class__ in native_numeric_types:\n        if linear is None:\n            #\n            # Get the first term, which we will test for linearity\n            #\n            first = next(args, None)\n            if first is None:\n                return start\n            #\n            # Check if the first term is linear, and if so return the terms\n            #\n            linear, terms = decompose_term(first)\n            #\n            # Right now Pyomo5 expressions can only handle single linear\n            # terms.\n            #\n            # Also, we treat linear expressions as nonlinear if the constant\n            # term is not a native numeric type.  Otherwise, large summation\n            # objects are created for the constant term.\n            #\n            if linear:\n                nvar=0\n                for term in terms:\n                    c,v = term\n                    if not v is None:\n                        nvar += 1\n                    elif not c.__class__ in native_numeric_types:\n                        linear = False\n                if nvar > 1:\n                    linear = False\n            start = start+first\n        if linear:\n            with EXPR.linear_expression() as e:\n                e += start\n                for arg in args:\n                    e += arg\n            # Return the constant term if the linear expression does not contains variables\n            if e.is_constant():\n                return e.constant\n            return e\n        else:\n            with EXPR.nonlinear_expression() as e:\n                e += start\n                for arg in args:\n                    e += arg\n            if e.nargs() == 0:\n                return 0\n            elif e.nargs() == 1:\n                return e.arg(0)\n            return e\n    #\n    # Otherwise, use the context that is provided and return it.\n    #\n    e = start\n    for arg in args:\n        e += arg\n    return e\n\n\ndef sum_product(*args, **kwds):\n    \"\"\"\n    A utility function to compute a generalized dot product.  \n\n    This function accepts one or more components that provide terms\n    that are multiplied together.  These products are added together\n    to form a sum.\n\n    Args:\n        *args: Variable length argument list of generators that\n            create terms in the summation.\n        **kwds: Arbitrary keyword arguments.\n\n    Keyword Args:\n        index: A set that is used to index the components used to\n            create the terms\n        denom: A component or tuple of components that are used to\n            create the denominator of the terms\n        start: The initial value used in the sum\n\n    Returns:\n        The value of the sum.\n    \"\"\"\n    denom = kwds.pop('denom', tuple() )\n    if type(denom) not in (list, tuple):\n        denom = [denom]\n    nargs = len(args)\n    ndenom = len(denom)\n\n    if nargs == 0 and ndenom == 0:\n        raise ValueError(\"The sum_product() command requires at least an \" + \\\n              \"argument or a denominator term\")\n\n    if 'index' in kwds:\n        index=kwds['index']\n    else:\n        if nargs > 0:\n            iarg=args[-1]\n            if not isinstance(iarg,Var) and not isinstance(iarg, Expression):\n                raise ValueError(\"Error executing sum_product(): The last argument value must be a variable or expression object if no 'index' option is specified\")\n        else:\n            iarg=denom[-1]\n            if not isinstance(iarg,Var) and not isinstance(iarg, Expression):\n                raise ValueError(\"Error executing sum_product(): The last denom argument value must be a variable or expression object if no 'index' option is specified\")\n        index = iarg.index_set()\n\n    start = kwds.get(\"start\", 0)\n    vars_ = []\n    params_ = []\n    for arg in args:\n        if isinstance(arg, Var):\n            vars_.append(arg)\n        else:\n            params_.append(arg)\n    nvars = len(vars_)\n\n    num_index = range(0,nargs)\n    if ndenom == 0:\n        #\n        # Sum of polynomial terms\n        #\n        if start.__class__ in native_numeric_types:\n            if nvars == 1:\n                v = vars_[0]\n                if len(params_) == 0:\n                    with EXPR.linear_expression() as expr:\n                        expr += start\n                        for i in index:\n                            expr += v[i]\n                elif len(params_) == 1:    \n                    p = params_[0]\n                    with EXPR.linear_expression() as expr:\n                        expr += start\n                        for i in index:\n                            expr += p[i]*v[i]\n                else:\n                    with EXPR.linear_expression() as expr:\n                        expr += start\n                        for i in index:\n                            term = 1\n                            for j in params_:\n                                term *= params_[j][i]\n                            expr += term * v[i]\n                return expr\n            #\n            with EXPR.nonlinear_expression() as expr:\n                expr += start\n                for i in index:\n                    term = 1\n                    for j in num_index:\n                        term *= args[j][i]\n                    expr += term\n            return expr\n        #\n        return quicksum((prod(args[j][i] for j in num_index) for i in index), start)\n    elif nargs == 0:\n        #\n        # Sum of reciprocals\n        #\n        denom_index = range(0,ndenom)\n        return quicksum((1/prod(denom[j][i] for j in denom_index) for i in index), start)\n    else:\n        #\n        # Sum of fractions\n        #\n        denom_index = range(0,ndenom)\n        return quicksum((prod(args[j][i] for j in num_index)/prod(denom[j][i] for j in denom_index) for i in index), start)\n\n\n#: An alias for :func:`sum_product <pyomo.core.expr.util>`\ndot_product = sum_product\n\n#: An alias for :func:`sum_product <pyomo.core.expr.util>`\nsummation = sum_product\n\n\ndef sequence(*args):\n    \"\"\"\n    sequence([start,] stop[, step]) -> generator for a list of integers\n\n    Return a generator that containing an arithmetic\n    progression of integers.  \n       sequence(i, j) returns [i, i+1, i+2, ..., j]; \n       start defaults to 1.  \n       step specifies the increment (or decrement)\n    For example, sequence(4) returns [1, 2, 3, 4].\n    \"\"\"\n    if len(args) == 0:\n        raise ValueError('sequence expected at least 1 arguments, got 0')\n    if len(args) > 3:\n        raise ValueError('sequence expected at most 3 arguments, got %d' % len(args))\n    if len(args) == 1:\n        return range(1,args[0]+1)\n    if len(args) == 2:\n        return range(args[0],args[1]+1)\n    return range(args[0],args[1]+1,args[2])\n\ndef target_list(x):\n    if isinstance(x, _ComponentBase):\n        return [ x ]\n    elif hasattr(x, '__iter__'):\n        ans = []\n        for i in x:\n            if isinstance(i, _ComponentBase):\n                ans.append(i)\n            else:\n                raise ValueError(\n                    \"Expected Component or list of Components.\"\n                    \"\\n\\tReceived %s\" % (type(i),))\n        return ans\n    else:\n        raise ValueError(\n            \"Expected Component or list of Components.\"\n            \"\\n\\tReceived %s\" % (type(x),))", "path": "pyomo/core/util.py"}]}