Skip to content

Commit e55e4a2

Browse files
authored
Merge pull request #3890 from DLWoodruff/doc-mutable-params
docs: improve documentation for mutable vs immutable Params
2 parents 8bd2696 + 0a72ee3 commit e55e4a2

File tree

2 files changed

+191
-47
lines changed

2 files changed

+191
-47
lines changed

doc/OnlineDocs/explanation/modeling/math_programming/parameters.rst

Lines changed: 159 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -8,42 +8,77 @@ Parameters
88
>>> import pyomo.environ as pyo
99
>>> model = pyo.ConcreteModel()
1010

11-
The word "parameters" is used in many settings. When discussing a Pyomo
12-
model, we use the word to refer to data that must be provided in order
13-
to find an optimal (or good) assignment of values to the decision
14-
variables. Parameters are declared as instances of a :class:`Param`
15-
class, which
16-
takes arguments that are somewhat similar to the :class:`Set` class. For
17-
example, the following code snippet declares sets ``model.A`` and
18-
``model.B``, and then a parameter ``model.P`` that is indexed by
19-
``model.A`` and ``model.B``:
11+
The word "parameters" is used in many settings. In Pyomo, a :class:`Param`
12+
represents the fixed data of an optimization model. Unlike variables
13+
(:class:`Var`), which the solver determines, parameters are inputs that define
14+
the specific instance of the problem you are solving.
15+
16+
Common examples of parameters include costs, demands, capacities, or physical
17+
constants. While you could use standard Python variables to store these values,
18+
using Pyomo :class:`Param` components offers several advantages:
19+
20+
* **Index Management:** Params can be indexed by Pyomo :class:`Set` objects,
21+
ensuring consistency between your data and the model structure. In a
22+
:class:`ConcreteModel`, they can also be indexed by standard Python iterables
23+
like lists, tuples, or ranges.
24+
* **Validation:** You can define rules to ensure that the data provided (e.g.,
25+
from an external file) is valid before solving.
26+
* **Symbolic Representation:** In large or complex models, using Params allows
27+
Pyomo to maintain the structure of the model separately from the specific values.
28+
29+
.. note::
30+
31+
When working with a :class:`ConcreteModel`, many modelers choose to use
32+
standard Python variables, lists, or dictionaries to store their data
33+
instead of Pyomo :class:`Param` objects. This is a common and valid
34+
practice.
35+
36+
However, you must use a Pyomo :class:`Param` if:
37+
38+
* You are using an :class:`AbstractModel` (which requires components to be
39+
declared before data is loaded).
40+
* You need a **mutable** parameter to change values and re-solve the model
41+
without the overhead of rebuilding it from scratch.
42+
* You want to leverage Pyomo's built-in data validation and index-checking
43+
capabilities.
44+
45+
Declaration and Options
46+
-----------------------
47+
48+
Parameters are declared as instances of the :class:`Param` class. They can be
49+
scalar (single value) or indexed by one or more sets (Pyomo :class:`Set` or
50+
other iterables). For example:
2051

2152
.. testcode::
2253

2354
model.A = pyo.RangeSet(1,3)
24-
model.B = pyo.Set()
55+
model.B = pyo.Set(initialize=['dog', 'cat'])
56+
# Scalar parameter
57+
model.rho = pyo.Param(initialize=0.5)
58+
# Indexed parameter (by Set)
2559
model.P = pyo.Param(model.A, model.B)
60+
# Indexed parameter (by standard list)
61+
model.Q = pyo.Param(['a', 'b', 'c'], initialize={'a': 1, 'b': 2, 'c': 3})
2662

27-
In addition to sets that serve as indexes, :class:`Param` takes
28-
the following options:
63+
If there are indexes for a :class:`Param`, they are provided as the first
64+
positional arguments and do not have a keyword label. In addition to these
65+
optional indexes, :class:`Param` takes the following keyword arguments:
2966

30-
- ``default`` = The parameter value absent any other specification.
67+
- ``default`` = The parameter value used if no other value is specified for an index.
3168
- ``doc`` = A string describing the parameter.
32-
- ``initialize`` = A function (or Python object) that returns data used to
33-
initialize the parameter values.
34-
- ``mutable`` = Boolean value indicating if the Param values are allowed
35-
to change after the Param is initialized.
36-
- ``validate`` = A callback function that takes the model, proposed
37-
value, and indices of the proposed value; returning ``True`` if the value
38-
is valid. Returning ``False`` will generate an exception.
39-
- ``within`` = Set used for validation; it specifies the domain of
40-
valid parameter values.
41-
42-
These options perform in the same way as they do for :class:`Set`. For
43-
example, given ``model.A`` with values ``{1, 2, 3}``, then there are many
44-
ways to create a parameter that represents a square matrix with 9, 16, 25 on the
45-
main diagonal and zeros elsewhere, here are two ways to do it. First using a
46-
Python object to initialize:
69+
- ``initialize`` = A function, dictionary, or other Python object used to
70+
provide initial data.
71+
- ``mutable`` = Boolean indicating if values can be changed after construction
72+
(see below).
73+
- ``validate`` = A callback function to verify data integrity.
74+
- ``within`` = A set (e.g., ``NonNegativeReals``) used for domain validation.
75+
76+
Basic Initialization
77+
--------------------
78+
79+
There are many ways to provide data to a :class:`Param`. For example, given
80+
``model.A`` with values ``{1, 2, 3}``, here are two ways to create a diagonal
81+
matrix:
4782

4883
.. testcode::
4984

@@ -53,9 +88,7 @@ Python object to initialize:
5388
v[3,3] = 25
5489
model.S1 = pyo.Param(model.A, model.A, initialize=v, default=0)
5590

56-
And now using an initialization function that is automatically called
57-
once for each index tuple (remember that we are assuming that
58-
``model.A`` contains ``{1, 2, 3}``)
91+
You can also use an initialization function that Pyomo calls for each index:
5992

6093
.. testcode::
6194

@@ -66,21 +99,17 @@ once for each index tuple (remember that we are assuming that
6699
return 0.0
67100
model.S2 = pyo.Param(model.A, model.A, initialize=s_init)
68101

69-
In this example, the index set contained integers, but index sets need
70-
not be numeric. It is very common to use strings.
71-
72102
.. note::
73103

74-
Data specified in an input file will override the data specified by
75-
the ``initialize`` option.
104+
In an :class:`AbstractModel`, data specified in an external input file (e.g.,
105+
a ``.dat`` file) will override the data specified by the ``initialize``
106+
option.
76107

77-
Parameter values can be checked by a validation function. In the
78-
following example, the every value of the parameter ``T`` (indexed by
79-
``model.A``) is checked
80-
to be greater than 3.14159. If a value is provided that is less than
81-
that, the model instantiation will be terminated and an error message
82-
issued. The validation function should be written so as to return
83-
``True`` if the data is valid and ``False`` otherwise.
108+
Validation
109+
----------
110+
111+
Parameter values can be checked by a validation function. In the following
112+
example, we ensure every value of ``model.T`` is greater than 3.14159:
84113

85114
.. testcode::
86115

@@ -91,12 +120,97 @@ issued. The validation function should be written so as to return
91120

92121
model.T = pyo.Param(model.A, validate=t_validate, initialize=t_data)
93122

94-
This example will prodice the following error, indicating that the value
95-
provided for ``T[2]`` failed validation:
123+
This example will produce the following error:
96124

97125
.. testoutput::
98126

99127
Traceback (most recent call last):
100128
...
101129
ValueError: Invalid parameter value: T[2] = '3', value type=<class 'int'>.
102130
Value failed parameter validation rule
131+
132+
Performance vs. Flexibility: Mutable Parameters
133+
-----------------------------------------------
134+
135+
By default, Pyomo parameters are **immutable** (``mutable=False``). This choice
136+
is driven by performance:
137+
138+
* **Immutable (Default):** Pyomo "pre-computes" these values into the algebraic
139+
expressions during model construction. This results in faster model generation
140+
and significantly lower memory usage, especially for large models. Key
141+
advantages include:
142+
143+
* **Memory Efficiency:** For indexed parameters, Pyomo avoids creating
144+
individual component data objects, significantly reducing memory overhead.
145+
* **Expression Speed:** Values are injected as constants directly into the
146+
expression tree. This allows Pyomo to optimize expression tree walking.
147+
* **Simplification:** Pyomo can simplify constant sub-expressions during
148+
model construction (e.g., ``5 * model.p * model.q[i]`` is simplified to a
149+
single float if ``p`` and ``q`` are immutable), further accelerating
150+
subsequent processing.
151+
* **Mutable:** Pyomo maintains the parameter as a symbolic object within
152+
expressions. This allows you to change the value and re-solve without
153+
rebuilding the entire model, but it adds computational overhead.
154+
155+
It is important to note that even immutable :class:`Param` objects carry some
156+
overhead. For the fastest possible model instantiation in a
157+
:class:`ConcreteModel`, using native Python data structures (like dictionaries
158+
or lists) to provide values directly into expressions is usually faster than
159+
using :class:`Param` components. However, as noted earlier, :class:`Param`
160+
provides benefits like validation and the ability to update values if declared
161+
as mutable.
162+
163+
When to use Mutable
164+
~~~~~~~~~~~~~~~~~~~
165+
166+
**Use Immutable if:**
167+
* The data is static and never changes during the lifetime of the model.
168+
* You want to maximize performance and minimize memory usage for large models.
169+
170+
**Use Mutable if:**
171+
* You are running a loop (e.g., sensitivity analysis) where you change
172+
parameter values and re-solve.
173+
* You want to update values frequently without the "re-construction"
174+
bottleneck.
175+
* The parameter is part of a nonlinear expression that you need to update.
176+
* You want named constants to be preserved in the Pyomo expressions (e.g., for documentation of debugging purposes)
177+
178+
Comparison: Param vs. Var
179+
-------------------------
180+
181+
It is common to confuse mutable parameters with variables. The following table
182+
summarizes the key differences:
183+
184+
.. list-table::
185+
:header-rows: 1
186+
187+
* - Feature
188+
- Param (Immutable)
189+
- Param (Mutable)
190+
- Var (fixed)
191+
- Var (free)
192+
* - Can change after model construction?
193+
- No
194+
- Yes
195+
- Yes
196+
- Yes
197+
* - Rebuilds model on change?
198+
- Yes (requires new Param)
199+
- No
200+
- No
201+
- No
202+
* - Solver sees it as:
203+
- A constant number
204+
- A constant number
205+
- A constant number
206+
- An optimization variable
207+
208+
.. note::
209+
210+
**Should I use a mutable Param or a fixed Var?**
211+
While functionally similar, you should use a :class:`Param` for data that
212+
defines the problem instance (like costs or demands) and a :class:`Var` for
213+
the decisions the solver needs to make. Use `fix()` on a :class:`Var` when
214+
you want to temporarily hold a decision constant, and use a mutable
215+
:class:`Param` when you need to update input data for sensitivity analysis
216+
or iterative algorithms.

doc/OnlineDocs/howto/manipulating.rst

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,16 +247,46 @@ declared to be ``mutable`` (i.e., ``mutable=True``) with an
247247
index that contains ``idx``, then the value in ``NewVal`` can be assigned to
248248
it using
249249

250-
>>> instance.Theta[idx] = NewVal
250+
>>> instance.Theta[idx] = NewVal
251+
252+
or, more explicitly using the ``set_value()`` method:
253+
254+
>>> instance.Theta[idx].set_value(NewVal)
251255

252256
For a singleton parameter named ``sigma`` (i.e., if it is not
253257
indexed), the assignment can be made using
254258

255259
>>> instance.sigma = NewVal
256260

261+
or
262+
263+
>>> instance.sigma.set_value(NewVal)
264+
265+
Common Pitfalls: Updating Immutable Parameters
266+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
267+
268+
A common mistake is trying to update a parameter that was not declared as
269+
mutable. For example:
270+
271+
.. code-block:: python
272+
273+
model.p = pyo.Param(initialize=10) # mutable=False by default
274+
model.p = 5 # Raises TypeError
275+
276+
This will raise a ``TypeError`` because Pyomo has already "baked" the value
277+
``10`` into the model's expressions. To allow updates, you **must** set
278+
``mutable=True`` during declaration:
279+
280+
.. code-block:: python
281+
282+
model.p = pyo.Param(initialize=10, mutable=True)
283+
model.p.set_value(5) # This works!
284+
257285
.. note::
258286

259-
If the ``Param`` is not declared to be mutable, an error will occur if an assignment to it is attempted.
287+
While direct assignment (e.g., ``model.p = 5``) works for mutable parameters,
288+
using ``set_value()`` is often clearer as it explicitly signals that you
289+
are updating a Pyomo component rather than just a Python attribute.
260290

261291
For more information about access to Pyomo parameters, see the section
262292
in this document on ``Param`` access :ref:`ParamAccess`. Note that for

0 commit comments

Comments
 (0)