From 5a6b420747a76b8ca3f188f9f3be85d1f17d023b Mon Sep 17 00:00:00 2001 From: Dave Woodruff Date: Wed, 25 Mar 2026 16:11:41 -0700 Subject: [PATCH] docs: improve documentation for mutable vs immutable Params Added sections for Performance vs. Flexibility, When to use Mutable, Comparison Table (Param vs. Var), and clearer information on updating parameters with set_value(). --- .../modeling/math_programming/parameters.rst | 179 +++++++++++++----- doc/OnlineDocs/howto/manipulating.rst | 34 +++- 2 files changed, 166 insertions(+), 47 deletions(-) diff --git a/doc/OnlineDocs/explanation/modeling/math_programming/parameters.rst b/doc/OnlineDocs/explanation/modeling/math_programming/parameters.rst index 90bcff4dc27..77f9bfb32db 100644 --- a/doc/OnlineDocs/explanation/modeling/math_programming/parameters.rst +++ b/doc/OnlineDocs/explanation/modeling/math_programming/parameters.rst @@ -8,42 +8,77 @@ Parameters >>> import pyomo.environ as pyo >>> model = pyo.ConcreteModel() -The word "parameters" is used in many settings. When discussing a Pyomo -model, we use the word to refer to data that must be provided in order -to find an optimal (or good) assignment of values to the decision -variables. Parameters are declared as instances of a :class:`Param` -class, which -takes arguments that are somewhat similar to the :class:`Set` class. For -example, the following code snippet declares sets ``model.A`` and -``model.B``, and then a parameter ``model.P`` that is indexed by -``model.A`` and ``model.B``: +The word "parameters" is used in many settings. In Pyomo, a :class:`Param` +represents the fixed data of an optimization model. Unlike variables +(:class:`Var`), which the solver determines, parameters are inputs that define + the specific instance of the problem you are solving. + +Common examples of parameters include costs, demands, capacities, or physical +constants. While you could use standard Python variables to store these values, +using Pyomo :class:`Param` components offers several advantages: + +* **Index Management:** Params can be indexed by Pyomo :class:`Set` objects, + ensuring consistency between your data and the model structure. In a + :class:`ConcreteModel`, they can also be indexed by standard Python iterables + like lists, tuples, or ranges. +* **Validation:** You can define rules to ensure that the data provided (e.g., + from an external file) is valid before solving. +* **Symbolic Representation:** In large or complex models, using Params allows + Pyomo to maintain the structure of the model separately from the specific values. + +.. note:: + + When working with a :class:`ConcreteModel`, many modelers choose to use + standard Python variables, lists, or dictionaries to store their data + instead of Pyomo :class:`Param` objects. This is a common and valid + practice. + + However, you must use a Pyomo :class:`Param` if: + + * You are using an :class:`AbstractModel` (which requires components to be + declared before data is loaded). + * You need a **mutable** parameter to change values and re-solve the model + without the overhead of rebuilding it from scratch. + * You want to leverage Pyomo's built-in data validation and index-checking + capabilities. + +Declaration and Options +----------------------- + +Parameters are declared as instances of the :class:`Param` class. They can be +scalar (single value) or indexed by one or more sets (Pyomo :class:`Set` or +other iterables). For example: .. testcode:: model.A = pyo.RangeSet(1,3) - model.B = pyo.Set() + model.B = pyo.Set(initialize=['dog', 'cat']) + # Scalar parameter + model.rho = pyo.Param(initialize=0.5) + # Indexed parameter (by Set) model.P = pyo.Param(model.A, model.B) + # Indexed parameter (by standard list) + model.Q = pyo.Param(['a', 'b', 'c'], initialize={'a': 1, 'b': 2, 'c': 3}) -In addition to sets that serve as indexes, :class:`Param` takes -the following options: +If there are indexes for a :class:`Param`, they are provided as the first +positional arguments and do not have a keyword label. In addition to these +optional indexes, :class:`Param` takes the following keyword arguments: -- ``default`` = The parameter value absent any other specification. +- ``default`` = The parameter value used if no other value is specified for an index. - ``doc`` = A string describing the parameter. -- ``initialize`` = A function (or Python object) that returns data used to - initialize the parameter values. -- ``mutable`` = Boolean value indicating if the Param values are allowed - to change after the Param is initialized. -- ``validate`` = A callback function that takes the model, proposed - value, and indices of the proposed value; returning ``True`` if the value - is valid. Returning ``False`` will generate an exception. -- ``within`` = Set used for validation; it specifies the domain of - valid parameter values. - -These options perform in the same way as they do for :class:`Set`. For -example, given ``model.A`` with values ``{1, 2, 3}``, then there are many -ways to create a parameter that represents a square matrix with 9, 16, 25 on the -main diagonal and zeros elsewhere, here are two ways to do it. First using a -Python object to initialize: +- ``initialize`` = A function, dictionary, or other Python object used to + provide initial data. +- ``mutable`` = Boolean indicating if values can be changed after construction + (see below). +- ``validate`` = A callback function to verify data integrity. +- ``within`` = A set (e.g., ``NonNegativeReals``) used for domain validation. + +Basic Initialization +-------------------- + +There are many ways to provide data to a :class:`Param`. For example, given +``model.A`` with values ``{1, 2, 3}``, here are two ways to create a diagonal +matrix: .. testcode:: @@ -53,9 +88,7 @@ Python object to initialize: v[3,3] = 25 model.S1 = pyo.Param(model.A, model.A, initialize=v, default=0) -And now using an initialization function that is automatically called -once for each index tuple (remember that we are assuming that -``model.A`` contains ``{1, 2, 3}``) +You can also use an initialization function that Pyomo calls for each index: .. testcode:: @@ -66,21 +99,17 @@ once for each index tuple (remember that we are assuming that return 0.0 model.S2 = pyo.Param(model.A, model.A, initialize=s_init) -In this example, the index set contained integers, but index sets need -not be numeric. It is very common to use strings. - .. note:: - Data specified in an input file will override the data specified by - the ``initialize`` option. + In an :class:`AbstractModel`, data specified in an external input file (e.g., + a ``.dat`` file) will override the data specified by the ``initialize`` + option. -Parameter values can be checked by a validation function. In the -following example, the every value of the parameter ``T`` (indexed by -``model.A``) is checked -to be greater than 3.14159. If a value is provided that is less than -that, the model instantiation will be terminated and an error message -issued. The validation function should be written so as to return -``True`` if the data is valid and ``False`` otherwise. +Validation +---------- + +Parameter values can be checked by a validation function. In the following +example, we ensure every value of ``model.T`` is greater than 3.14159: .. testcode:: @@ -91,8 +120,7 @@ issued. The validation function should be written so as to return model.T = pyo.Param(model.A, validate=t_validate, initialize=t_data) -This example will prodice the following error, indicating that the value -provided for ``T[2]`` failed validation: +This example will produce the following error: .. testoutput:: @@ -100,3 +128,64 @@ provided for ``T[2]`` failed validation: ... ValueError: Invalid parameter value: T[2] = '3', value type=. Value failed parameter validation rule + +Performance vs. Flexibility: Mutable Parameters +----------------------------------------------- + +By default, Pyomo parameters are **immutable** (``mutable=False``). This choice +is driven by performance: + +* **Immutable (Default):** Pyomo "pre-computes" these values into the algebraic + expressions during model construction. This results in faster model generation + and significantly lower memory usage, especially for large models. +* **Mutable:** Pyomo maintains the parameter as a symbolic object within + expressions. This allows you to change the value and re-solve without + rebuilding the entire model, but it adds computational overhead. + +It is important to note that even immutable :class:`Param` objects carry some +overhead. For the fastest possible model instantiation in a +:class:`ConcreteModel`, using native Python data structures (like dictionaries +or lists) to provide values directly into expressions is usually faster than +using :class:`Param` components. However, as noted earlier, :class:`Param` +provides benefits like validation and the ability to update values if declared +as mutable. + +When to use Mutable +~~~~~~~~~~~~~~~~~~~ + +**Use Immutable if:** + * The data is static and never changes during the execution of your script. + * You want to maximize performance and minimize memory usage for large models. + +**Use Mutable if:** + * You are running a loop (e.g., sensitivity analysis) where you change + parameter values and re-solve. + * You want to update values frequently without the "re-construction" + bottleneck. + * The parameter is part of a nonlinear expression that you need to update. + +Comparison: Param vs. Var +------------------------- + +It is common to confuse mutable parameters with variables. The following table +summarizes the key differences: + +.. list-table:: + :header-rows: 1 + + * - Feature + - Param (Immutable) + - Param (Mutable) + - Var + * - Can change after solve()? + - No + - Yes + - Yes (by solver) + * - Rebuilds model on change? + - Yes (requires new Param) + - No + - No + * - Solver sees it as: + - A constant number + - A constant number + - An optimization variable diff --git a/doc/OnlineDocs/howto/manipulating.rst b/doc/OnlineDocs/howto/manipulating.rst index 783fa4fe0e2..5fc8496ffc8 100644 --- a/doc/OnlineDocs/howto/manipulating.rst +++ b/doc/OnlineDocs/howto/manipulating.rst @@ -247,16 +247,46 @@ declared to be ``mutable`` (i.e., ``mutable=True``) with an index that contains ``idx``, then the value in ``NewVal`` can be assigned to it using - >>> instance.Theta[idx] = NewVal + >>> instance.Theta[idx] = NewVal + +or, more explicitly using the ``set_value()`` method: + + >>> instance.Theta[idx].set_value(NewVal) For a singleton parameter named ``sigma`` (i.e., if it is not indexed), the assignment can be made using >>> instance.sigma = NewVal +or + + >>> instance.sigma.set_value(NewVal) + +Common Pitfalls: Updating Immutable Parameters +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +A common mistake is trying to update a parameter that was not declared as +mutable. For example: + +.. code-block:: python + + model.p = pyo.Param(initialize=10) # mutable=False by default + model.p = 5 # Raises TypeError + +This will raise a ``TypeError`` because Pyomo has already "baked" the value +``10`` into the model's expressions. To allow updates, you **must** set +``mutable=True`` during declaration: + +.. code-block:: python + + model.p = pyo.Param(initialize=10, mutable=True) + model.p.set_value(5) # This works! + .. note:: - If the ``Param`` is not declared to be mutable, an error will occur if an assignment to it is attempted. + While direct assignment (e.g., ``model.p = 5``) works for mutable parameters, + using ``set_value()`` is often clearer as it explicitly signals that you + are updating a Pyomo component rather than just a Python attribute. For more information about access to Pyomo parameters, see the section in this document on ``Param`` access :ref:`ParamAccess`. Note that for