PyROS Solver¶
PyROS (Pyomo Robust Optimization Solver) is a metasolver capability within Pyomo for solving nonconvex, twostage optimization models using adjustable robust optimization.
It was developed by Natalie M. Isenberg and Chrysanthos E. Gounaris of Carnegie Mellon University, in collaboration with John D. Siirola of Sandia National Labs. The developers gratefully acknowledge support from the U.S. Department of Energy’s Institute for the Design of Advanced Energy Systems (IDAES).
Methodology Overview¶
Below is an overview of the type of optimization models PyROS can accomodate.
 PyROS is suitable for optimization models of continuous variables that may feature nonlinearities (including nonconvexities) in both the variables and uncertain parameters.
 PyROS can handle equality constraints defining state variables, including implicit state variables that cannot be eliminated via reformulation.
 PyROS allows for twostage optimization problems that may feature both firststage and secondstage degrees of freedom.
The general form of a deterministic optimization problem that can be passed into PyROS is shown below:
where:
 \(x \in \mathcal{X}\) are the “design” variables (i.e., firststage degrees of freedom), where \(\mathcal{X} \subseteq \mathbb{R}^m\) is the feasible space defined by the model constraints that only reference these variables
 \(z \in \mathbb{R}^n\) are the “control” variables (i.e., secondstage degrees of freedom)
 \(y \in \mathbb{R}^a\) are the “state” variables
 \(q \in \mathbb{R}^w\) is the vector of parameters that we shall later consider to be uncertain, and \(q^0\) is the vector of nominal values associated with those.
 \(f_1\left(x\right)\) are the terms of the objective function that depend only on design variables
 \(f_2\left(x, z, y; q\right)\) are the terms of the objective function that depend on control and/or state variables
 \(g_i\left(x, z, y; q\right)\) is the \(i^\text{th}\) inequality constraint in set \(\mathcal{I}\) (see Note)
 \(h_j\left(x, z, y; q\right)\) is the \(j^\text{th}\) equality constraint in set \(\mathcal{J}\) (see Note)
Note
 Applicable bounds on variables \(z\) and/or \(y\) are assumed to have been incorporated in the set of inequality constraints \(\mathcal{I}\).
 A key requirement of PyROS is that each value of \(\left(x, z, q \right)\) maps to a unique value of \(y\), a property that is assumed to be properly enforced by the system of equality constraints \(\mathcal{J}\). If such unique mapping does not hold, then the selection of ‘state’ (i.e., not degree of freedom) variables \(y\) is incorrect, and one or more of the \(y\) variables should be appropriately redesignated to be part of either \(x\) or \(z\).
In order to cast the robust optimization counterpart formulation of the above model, we shall now assume that the uncertain parameters may attain any realization from within an uncertainty set \(\mathcal{Q} \subseteq \mathbb{R}^w\), such that \(q^0 \in \mathcal{Q}\). The set \(\mathcal{Q}\) is assumed to be closed and bounded, while it can be either continuous or discrete.
Based on the above notation, the form of the robust counterpart addressed in PyROS is shown below:
In order to solve problems of the above type, PyROS implements the Generalized Robust CuttingSet algorithm developed in [GRCSPaper].
When using PyROS, please consider citing the above paper.
PyROS Required Inputs¶
The required inputs to the PyROS solver are the following:
 The determinisitic optimization model
 List of firststage (“design”) variables
 List of secondstage (“control”) variables
 List of parameters to be considered uncertain
 The uncertainty set
 Subordinate local and global NLP optimization solvers
Below is a list of arguments that PyROS expects the user to provide when calling the solve
command.
Note how all but the model
argument must be specified as kwargs
.
 model
ConcreteModel
 A
ConcreteModel
object representing the deterministic model.  first_stage_variables
list(Var)
 A list of Pyomo
Var
objects representing the firststage degrees of freedom (design variables) inmodel
.  second_stage_variables
list(Var)
 A list of Pyomo
Var
objects representing secondstage degrees of freedom (control variables) inmodel
.  uncertain_params
list(Param)
 A list of Pyomo
Param
objects indeterministic_model
to be considered uncertain. These specifiedParam
objects must have the propertymutable=True
.  uncertainty_set
UncertaintySet
 A PyROS
UncertaintySet
object representing uncertainty in the space of those parameters listed in theuncertain_params
object.  local_solver
Solver
 A Pyomo
Solver
instance for a local NLP optimization solver.  global_solver
Solver
 A Pyomo
Solver
instance for a global NLP optimization solver.
Note
Any variables in the model not specified to be first or secondstage variables are automatically considered to be state variables.
PyROS Solver Interface¶

class
pyomo.contrib.pyros.
PyROS
[source]¶ PyROS (Pyomo Robust Optimization Solver) implementing a generalized robust cuttingset algorithm (GRCS) to solve twostage NLP optimization models under uncertainty.

solve
(model, first_stage_variables, second_stage_variables, uncertain_params, uncertainty_set, local_solver, global_solver, **kwds)[source]¶ Solve the model.
Parameters:  model (ConcreteModel) – A
ConcreteModel
object representing the deterministic model, cast as a minimization problem.  first_stage_variables (List[Var]) – The list of
Var
objects referenced inmodel
representing the design variables.  second_stage_variables (List[Var]) – The list of
Var
objects referenced inmodel
representing the control variables.  uncertain_params (List[Param]) – The list of
Param
objects referenced inmodel
representing the uncertain parameters. MUST bemutable
. Assumes entries are provided in consistent order with the entries of ‘nominal_uncertain_param_vals’ input.  uncertainty_set (UncertaintySet) –
UncertaintySet
object representing the uncertainty space that the final solutions will be robust against.  local_solver (Solver) –
Solver
object to utilize as the primary local NLP solver.  global_solver (Solver) –
Solver
object to utilize as the primary global NLP solver.
Keyword Arguments:  time_limit – Optional. Default = None. Total allotted time for the execution of the PyROS solver in seconds (includes time spent in subsolvers). ‘None’ is no time limit.
 keepfiles – Optional. Default = False. Whether or not to write files of subproblems for use in debugging. Must be paired with a writable directory supplied via
subproblem_file_directory
.  tee – Optional. Default = False. Sets the
tee
for all subsolvers utilized.  load_solution – Optional. Default = True. Whether or not to load the final solution of PyROS into the model object.
 objective_focus – Optional. Default =
ObjectiveType.nominal
. Choice of objective function to optimize in the master problems. Choices are:ObjectiveType.worst_case
,ObjectiveType.nominal
. See Note for details.  nominal_uncertain_param_vals – Optional. Default = deterministic model
Param
values. List of nominal values for all uncertain parameters. Assumes entries are provided in consistent order with the entries ofuncertain_params
input.  decision_rule_order – Optional. Default = 0. Order of decision rule functions for handling secondstage variable recourse. Choices are: ‘0’ for constant recourse (a.k.a. static approximation), ‘1’ for affine recourse (a.k.a. affine decision rules), ‘2’ for quadratic recourse.
 solve_master_globally – Optional. Default = False. ‘True’ for the master problems to be solved with the usersupplied global solver(s); or ‘False’ for the master problems to be solved with the usersupplied local solver(s).
 max_iter – Optional. Default = 1. Iteration limit for the GRCS algorithm. ‘1’ is no iteration limit.
 robust_feasibility_tolerance – Optional. Default = 1e4. Relative tolerance for assessing robust feasibility violation during separation phase.
 separation_priority_order – Optional. Default = {}. Dictionary mapping inequality constraint names to positive integer priorities for separation. Constraints not referenced in the dictionary assume a priority of 0 (lowest priority).
 progress_logger – Optional. Default = “pyomo.contrib.pyros”. The logger object to use for reporting.
 backup_local_solvers – Optional. Default = []. List of additional
Solver
objects to utilize as backup whenever primary local NLP solver fails to identify solution to a subproblem.  backup_global_solvers – Optional. Default = []. List of additional
Solver
objects to utilize as backup whenever primary global NLP solver fails to identify solution to a subproblem.  subproblem_file_directory – Optional. Path to a directory where subproblem files and logs will be written in the case that a subproblem fails to solve.
 bypass_local_separation – This is an advanced option. Default = False. ‘True’ to only use global solver(s) during separation; ‘False’ to use local solver(s) at intermediate separations, using global solver(s) only before termination to certify robust feasibility.
 p_robustness – This is an advanced option. Default = {}. Whether or not to add probustness constraints to the master problems. If the dictionary is empty (default), then probustness constraints are not added. See Note for how to specify arguments.
 model (ConcreteModel) – A

Note
Solving the master problems globally (via option solve_masters_globally=True
) is one of the requirements to guarantee robust optimality;
solving the master problems locally can only lead to a robust feasible solution.
Note
Selecting worstcase objective (via option objective_focus=ObjectiveType.worst_case
) is one of the requirements to guarantee robust optimality;
selecting nominal objective can only lead to a robust feasible solution,
albeit one that has optimized the sum of first and (nominal) secondstage objectives.
Note
To utilize option p_robustness
, a dictionary of the following form must be supplied via the kwarg
:
There must be a key (str
) called ‘rho’, which maps to a nonnegative value, where ‘1+rho’ defines a bound
for the ratio of the objective that any scenario may exhibit compared to the nominal objective.
PyROS Uncertainty Sets¶
PyROS contains preimplemented UncertaintySet
specializations for many types of commonly used uncertainty sets.
Additional capabilities for intersecting multiple PyROS UncertaintySet
objects so as to create custom sets are also provided
via the IntersectionSet
class. Custom userspecified sets can also be defined via the base UncertaintySet
class.
Mathematical representations of the sets are shown below, followed by the class descriptions.
Uncertainty Set Type  Set Representation 

BoxSet 
\(Q_X = \left\{q \in \mathbb{R}^n : q^\ell \leq q \leq q^u\right\} \\ q^\ell \in \mathbb{R}^n \\ q^u \in \mathbb{R}^n : \left\{q^\ell \leq q^u\right\}\) 
CardinalitySet 
\(Q_C = \left\{q \in \mathbb{R}^n : q = q^0 + (\hat{q} \circ \xi) \text{ for some } \xi \in \Xi_C\right\}\\ \Xi_C = \left\{\xi \in [0, 1]^n : \displaystyle\sum_{i=1}^{n} \xi_i \leq \Gamma\right\} \\ \Gamma \in [0, n] \\ \hat{q} \in \mathbb{R}^{n}_{+} \\ q^0 \in \mathbb{R}^n\) 
BudgetSet 
\(Q_B = \left\{q \in \mathbb{R}^n_+: \displaystyle\sum_{i \in B_\ell} q_i \leq b_\ell \ \forall \ell \in \left\{1,\ldots,L\right\} \right\} \\ b_\ell \in \mathbb{R}^{L}_+\) 
FactorModelSet 
\(Q_F = \left\{q \in \mathbb{R}^n: \displaystyle q = q^0 + \Psi \xi \text{ for some }\xi \in \Xi_F\right\} \\ \Xi_F = \left\{ \xi \in \left[1, 1\right]^F, \left\lvert \displaystyle \sum_{f=1}^{F} \xi_f\right\rvert \leq \beta F \right\} \\ \beta \in [0,1] \\ \Psi \in \mathbb{R}^{n \times F}_+ \\ q^0 \in \mathbb{R}^n\) 
PolyhedralSet 
\(Q_P = \left\{q \in \mathbb{R}^n: \displaystyle A q \leq b \right\} \\ A \in \mathbb{R}^{m \times n} \\ b \in \mathbb{R}^{m} \\ q^0 \in \mathbb{R}^n: {Aq^0 \leq b}\) 
AxisAlignedEllipsoidalSet 
\(Q_A = \left\{q \in \mathbb{R}^n: \displaystyle \sum\limits_{i=1 : \atop \left\{ \alpha_i > 0 \right\} } \left(\frac{q_i  q_i^0}{\alpha_i} \right)^2 \leq 1 , \quad q_i = q^0_i \quad \forall i : \left\{\alpha_i=0\right\}\right\} \\ \alpha \in \mathbb{R}^n_+, \\ q^0 \in \mathbb{R}^n\) 
EllipsoidalSet 
\(Q_E = \left\{q \in \mathbb{R}^n: \displaystyle q = q^0 + P^{1/2} \xi \text{ for some } \xi \in \Xi_E \right\} \\ \Xi_E = \left\{\xi \in \mathbb{R} : \xi^T\xi \leq s \right\} \\ P \in \mathbb{S}^{n\times n}_+ \\ s \in \mathbb{R}_+ \\ q^0 \in \mathbb{R}^n\) 
UncertaintySet 
\(Q_U = \left\{q \in \mathbb{R}^n: \displaystyle g_i(q) \leq 0 \quad \forall i \in \left\{1,\ldots,m \right\}\right\} \\ m \in \mathbb{N}_+ \\ g_i : \mathbb{R}^n \mapsto \mathbb{R} \, \forall i \in \left\{1,\ldots,m\right\}, \\ q^0 \in \mathbb{R}^n : \left\{g_i(q^0) \leq 0 \ \forall i \in \left\{1,\ldots,m\right\}\right\}\) 
DiscreteScenariosSet 
\(Q_D = \left\{q^s : s = 0,\ldots,D \right\} \\ D \in \mathbb{N} \\ q^s \in \mathbb{R}^n \forall s \in \left\{ 0,\ldots,D\right\}\) 
IntersectionSet 
\(Q_I = \left\{q \in \mathbb{R}^n: \displaystyle q \in \bigcap_{i \in \left\{1,\ldots,m\right\}} Q_i\right\} \\ Q_i \subset \mathbb{R}^n \quad \forall i \in \left\{1,\ldots,m\right\}\) 
Note
Each of the PyROS uncertainty set classes inherits from the UncertaintySet
base class.
PyROS Uncertainty Set Classes¶

class
pyomo.contrib.pyros.uncertainty_sets.
BoxSet
(bounds)[source]¶ Hyperrectangle (a.k.a. “Box”)

__init__
(bounds)[source]¶ BoxSet constructor
Parameters: bounds – A list of tuples providing lower and upper bounds (lb, ub) for each uncertain parameter, in the same order as the ‘uncertain_params’ required input that is to be supplied to the PyROS solve statement.

property
dim
¶ Dimension of the uncertainty set, i.e., number of parameters in “uncertain_params” list.

property
parameter_bounds
¶ Bounds on the realizations of the uncertain parameters, as inferred from the uncertainty set.

point_in_set
(point)¶ Calculates if supplied
point
is contained in the uncertainty set. Returns True or False.Parameters: point – The point being checked for membership in the set. The coordinates of the point should be supplied in the same order as the elements of uncertain_params
that is to be supplied to the PyROS solve statement. This point must match the dimension of the uncertain parameters of the set.


class
pyomo.contrib.pyros.uncertainty_sets.
CardinalitySet
(origin, positive_deviation, gamma)[source]¶ Cardinalityconstrained (a.k.a “Gamma”) uncertainty set

__init__
(origin, positive_deviation, gamma)[source]¶ CardinalitySet constructor
Parameters:  origin – The origin of the set (e.g., the nominal point).
 positive_deviation – Vector (
list
) of maximal deviations of each parameter.  gamma – Scalar to bound the total number of uncertain parameters that can maximally deviate from their respective ‘origin’. Setting ‘gamma = 0’ reduces the set to the ‘origin’ point. Setting ‘gamma’ to be equal to the number of parameters produces the hyperrectangle [origin, origin+positive_deviation]

property
dim
¶ Dimension of the uncertainty set, i.e., number of parameters in “uncertain_params” list.

property
parameter_bounds
¶ Bounds on the realizations of the uncertain parameters, as inferred from the uncertainty set.


class
pyomo.contrib.pyros.uncertainty_sets.
BudgetSet
(budget_membership_mat, rhs_vec)[source]¶ Budget uncertainty set

__init__
(budget_membership_mat, rhs_vec)[source]¶ BudgetSet constructor
Parameters:  budget_membership_mat – A matrix with 01 entries to designate which uncertain parameters participate in each budget constraint. Here, each row is associated with a separate budget constraint.
 rhs_vec – Vector (
list
) of righthand side values for the budget constraints.

property
dim
¶ Dimension of the uncertainty set, i.e., number of parameters in “uncertain_params” list.

property
parameter_bounds
¶ Bounds on the realizations of the uncertain parameters, as inferred from the uncertainty set.

point_in_set
(point)¶ Calculates if supplied
point
is contained in the uncertainty set. Returns True or False.Parameters: point – The point being checked for membership in the set. The coordinates of the point should be supplied in the same order as the elements of uncertain_params
that is to be supplied to the PyROS solve statement. This point must match the dimension of the uncertain parameters of the set.


class
pyomo.contrib.pyros.uncertainty_sets.
FactorModelSet
(origin, number_of_factors, psi_mat, beta)[source]¶ Factor model (a.k.a. “netalpha” model) uncertainty set

__init__
(origin, number_of_factors, psi_mat, beta)[source]¶ FactorModelSet constructor
Parameters:  origin – Vector (
list
) of uncertain parameter values around which deviations are restrained.  number_of_factors – Natural number representing the dimensionality of the space to which the set projects.
 psi – Matrix with nonnegative entries designating each uncertain parameter’s contribution to each factor. Here, each row is associated with a separate uncertain parameter and each column with a separate factor.
 beta – Number in [0,1] representing the fraction of the independent factors that can simultaneously attain their extreme values. Setting ‘beta = 0’ will enforce that as many factors will be above 0 as there will be below 0 (i.e., “zeronetalpha” model). Setting ‘beta = 1’ produces the hyperrectangle [origin  psi e, origin + psi e], where ‘e’ is the vector of ones.
 origin – Vector (

property
dim
¶ Dimension of the uncertainty set, i.e., number of parameters in “uncertain_params” list.

property
parameter_bounds
¶ Bounds on the realizations of the uncertain parameters, as inferred from the uncertainty set.


class
pyomo.contrib.pyros.uncertainty_sets.
PolyhedralSet
(lhs_coefficients_mat, rhs_vec)[source]¶ Polyhedral uncertainty set

__init__
(lhs_coefficients_mat, rhs_vec)[source]¶ PolyhedralSet constructor
Parameters:  lhs_coefficients_mat – Matrix of lefthand side coefficients for the linear inequality constraints defining the polyhedral set.
 rhs_vec – Vector (
list
) of righthand side values for the linear inequality constraints defining the polyhedral set.

property
dim
¶ Dimension of the uncertainty set, i.e., number of parameters in “uncertain_params” list.

property
parameter_bounds
¶ Bounds on the realizations of the uncertain parameters, as inferred from the uncertainty set. PolyhedralSet bounds are not computed at set construction because they cannot be algebraically determined and require access to an optimization solver.

point_in_set
(point)¶ Calculates if supplied
point
is contained in the uncertainty set. Returns True or False.Parameters: point – The point being checked for membership in the set. The coordinates of the point should be supplied in the same order as the elements of uncertain_params
that is to be supplied to the PyROS solve statement. This point must match the dimension of the uncertain parameters of the set.


class
pyomo.contrib.pyros.uncertainty_sets.
AxisAlignedEllipsoidalSet
(center, half_lengths)[source]¶ Axisaligned ellipsoidal uncertainty set

__init__
(center, half_lengths)[source]¶ AxisAlignedEllipsoidalSet constructor
Parameters:  center – Vector (
list
) of uncertain parameter values around which deviations are restrained.  half_lengths – Vector (
list
) of halflength values representing the maximal deviations for each uncertain parameter.
 center – Vector (

property
dim
¶ Dimension of the uncertainty set, i.e., number of parameters in “uncertain_params” list.

property
parameter_bounds
¶ Bounds on the realizations of the uncertain parameters, as inferred from the uncertainty set.

point_in_set
(point)¶ Calculates if supplied
point
is contained in the uncertainty set. Returns True or False.Parameters: point – The point being checked for membership in the set. The coordinates of the point should be supplied in the same order as the elements of uncertain_params
that is to be supplied to the PyROS solve statement. This point must match the dimension of the uncertain parameters of the set.


class
pyomo.contrib.pyros.uncertainty_sets.
EllipsoidalSet
(center, shape_matrix, scale=1)[source]¶ Ellipsoidal uncertainty set

__init__
(center, shape_matrix, scale=1)[source]¶ EllipsoidalSet constructor
Parameters:  center – Vector (
list
) of uncertain parameter values around which deviations are restrained.  shape_matrix – Positive semidefinite matrix, effectively a covariance matrix for
 determination. (constraint and bounds) –
 scale – Righthand side value for the ellipsoid.
 center – Vector (

property
dim
¶ Dimension of the uncertainty set, i.e., number of parameters in “uncertain_params” list.

property
parameter_bounds
¶ Bounds on the realizations of the uncertain parameters, as inferred from the uncertainty set.

point_in_set
(point)¶ Calculates if supplied
point
is contained in the uncertainty set. Returns True or False.Parameters: point – The point being checked for membership in the set. The coordinates of the point should be supplied in the same order as the elements of uncertain_params
that is to be supplied to the PyROS solve statement. This point must match the dimension of the uncertain parameters of the set.


class
pyomo.contrib.pyros.uncertainty_sets.
UncertaintySet
(**kwargs)[source]¶ Base class for custom userdefined uncertainty sets.

__init__
(**kwargs)[source]¶ Constructor for UncertaintySet base class
Parameters: kwargs – Use the kwargs for specifying data for the UncertaintySet object. This data should be used in defining constraints in the ‘set_as_constraint’ function.

abstract property
dim
¶ Dimension of the uncertainty set, i.e., number of parameters in “uncertain_params” list.

abstract property
parameter_bounds
¶ Bounds on the realizations of the uncertain parameters, as inferred from the uncertainty set.

point_in_set
(point)[source]¶ Calculates if supplied
point
is contained in the uncertainty set. Returns True or False.Parameters: point – The point being checked for membership in the set. The coordinates of the point should be supplied in the same order as the elements of uncertain_params
that is to be supplied to the PyROS solve statement. This point must match the dimension of the uncertain parameters of the set.


class
pyomo.contrib.pyros.uncertainty_sets.
DiscreteScenarioSet
(scenarios)[source]¶ Set of discrete scenarios (i.e., finite collection of realizations)

__init__
(scenarios)[source]¶ DiscreteScenarioSet constructor
Parameters: scenarios – Vector ( list
) of discrete scenarios where each scenario represents a realization of the uncertain parameters.

property
dim
¶ Dimension of the uncertainty set, i.e., number of parameters in “uncertain_params” list.

property
parameter_bounds
¶ Bounds on the realizations of the uncertain parameters, as inferred from the uncertainty set.


class
pyomo.contrib.pyros.uncertainty_sets.
IntersectionSet
(**kwargs)[source]¶ Set stemming from intersecting previously constructed sets of any type

__init__
(**kwargs)[source]¶ IntersectionSet constructor
Parameters: **kwargs – Keyword arguments for specifying all PyROS UncertaintySet objects to be intersected.

property
dim
¶ Dimension of the uncertainty set, i.e., number of parameters in “uncertain_params” list.

property
parameter_bounds
¶ Bounds on the realizations of the uncertain parameters, as inferred from the uncertainty set. IntersectedSet bounds are not computed at set construction because they cannot be algebraically determined and require access to an optimization solver.

PyROS Usage Example¶
We will use an example to illustrate the usage of PyROS. The problem we will use is called hydro and comes from the GAMS example problem database in The GAMS Model Library. The model was converted to Pyomo format via the GAMS Convert tool.
This model is a QCQP with 31 variables. Of these variables, 13 represent degrees of freedom, with the additional 18 being state variables. The model features 6 linear inequality constraints, 6 linear equality constraints, 6 nonlinear (quadratic) equalities, and a quadratic objective. We have augmented this model by converting one objective coefficient, two constraint coefficients, and one constraint righthand side into Param objects so that they can be considered uncertain later on.
Note
Per our analysis, the hydro problem satisfies the requirement that each value of \(\left(x, z, q \right)\) maps to a unique value of \(y\), which indicates a proper partition of variables between (first or secondstage) degrees of freedom and state variables.
Step 0: Import Pyomo and the PyROS Module¶
In anticipation of using the PyROS solver and building the deterministic Pyomo model:
>>> # === Required import ===
>>> import pyomo.environ as pyo
>>> import pyomo.contrib.pyros as pyros
>>> # === Instantiate the PyROS solver object ===
>>> pyros_solver = pyo.SolverFactory("pyros")
Step 1: Define the Deterministic Problem¶
The deterministic Pyomo model for hydro is shown below.
Note
Primitive data (Python literals) that have been hardcoded within a deterministic model cannot be later considered uncertain, unless they are first converted to Param
objects within the ConcreteModel
object.
Furthermore, any Param
object that is to be later considered uncertain must have the property mutable=True
.
Note
In case modifying the mutable
property inside the deterministic model object itself is not straightforward in your context,
you may consider adding the following statement after import pyomo.environ as pyo
but before defining the model object:
pyo.Param.DefaultMutable = True
. Note how this sets the default mutable
property in all Param
objects in the ensuing model instance to True
;
consequently, this solution will not work with Param
objects for which the mutable=False
property was explicitly enabled inside the model object.
>>> # === Construct the Pyomo model object ===
>>> m = pyo.ConcreteModel()
>>> m.name = "hydro"
>>> # === Define variables ===
>>> m.x1 = pyo.Var(within=pyo.Reals,bounds=(150,1500),initialize=150)
>>> m.x2 = pyo.Var(within=pyo.Reals,bounds=(150,1500),initialize=150)
>>> m.x3 = pyo.Var(within=pyo.Reals,bounds=(150,1500),initialize=150)
>>> m.x4 = pyo.Var(within=pyo.Reals,bounds=(150,1500),initialize=150)
>>> m.x5 = pyo.Var(within=pyo.Reals,bounds=(150,1500),initialize=150)
>>> m.x6 = pyo.Var(within=pyo.Reals,bounds=(150,1500),initialize=150)
>>> m.x7 = pyo.Var(within=pyo.Reals,bounds=(0,1000),initialize=0)
>>> m.x8 = pyo.Var(within=pyo.Reals,bounds=(0,1000),initialize=0)
>>> m.x9 = pyo.Var(within=pyo.Reals,bounds=(0,1000),initialize=0)
>>> m.x10 = pyo.Var(within=pyo.Reals,bounds=(0,1000),initialize=0)
>>> m.x11 = pyo.Var(within=pyo.Reals,bounds=(0,1000),initialize=0)
>>> m.x12 = pyo.Var(within=pyo.Reals,bounds=(0,1000),initialize=0)
>>> m.x13 = pyo.Var(within=pyo.Reals,bounds=(0,None),initialize=0)
>>> m.x14 = pyo.Var(within=pyo.Reals,bounds=(0,None),initialize=0)
>>> m.x15 = pyo.Var(within=pyo.Reals,bounds=(0,None),initialize=0)
>>> m.x16 = pyo.Var(within=pyo.Reals,bounds=(0,None),initialize=0)
>>> m.x17 = pyo.Var(within=pyo.Reals,bounds=(0,None),initialize=0)
>>> m.x18 = pyo.Var(within=pyo.Reals,bounds=(0,None),initialize=0)
>>> m.x19 = pyo.Var(within=pyo.Reals,bounds=(0,None),initialize=0)
>>> m.x20 = pyo.Var(within=pyo.Reals,bounds=(0,None),initialize=0)
>>> m.x21 = pyo.Var(within=pyo.Reals,bounds=(0,None),initialize=0)
>>> m.x22 = pyo.Var(within=pyo.Reals,bounds=(0,None),initialize=0)
>>> m.x23 = pyo.Var(within=pyo.Reals,bounds=(0,None),initialize=0)
>>> m.x24 = pyo.Var(within=pyo.Reals,bounds=(0,None),initialize=0)
>>> m.x25 = pyo.Var(within=pyo.Reals,bounds=(100000,100000),initialize=100000)
>>> m.x26 = pyo.Var(within=pyo.Reals,bounds=(60000,120000),initialize=60000)
>>> m.x27 = pyo.Var(within=pyo.Reals,bounds=(60000,120000),initialize=60000)
>>> m.x28 = pyo.Var(within=pyo.Reals,bounds=(60000,120000),initialize=60000)
>>> m.x29 = pyo.Var(within=pyo.Reals,bounds=(60000,120000),initialize=60000)
>>> m.x30 = pyo.Var(within=pyo.Reals,bounds=(60000,120000),initialize=60000)
>>> m.x31 = pyo.Var(within=pyo.Reals,bounds=(60000,120000),initialize=60000)
>>> # === Define parameters ===
>>> m.set_of_params = pyo.Set(initialize=[0, 1, 2, 3])
>>> nominal_values = {0:82.8*0.0016, 1:4.97, 2:4.97, 3:1800}
>>> m.p = pyo.Param(m.set_of_params, initialize=nominal_values, mutable=True)
>>> # === Specify the objective function ===
>>> m.obj = pyo.Objective(expr=m.p[0]*m.x1**2 + 82.8*8*m.x1 + 82.8*0.0016*m.x2**2 +
... 82.8*82.8*8*m.x2 + 82.8*0.0016*m.x3**2 + 82.8*8*m.x3 +
... 82.8*0.0016*m.x4**2 + 82.8*8*m.x4 + 82.8*0.0016*m.x5**2 +
... 82.8*8*m.x5 + 82.8*0.0016*m.x6**2 + 82.8*8*m.x6 + 248400,
... sense=pyo.minimize)
>>> # === Specify the constraints ===
>>> m.c2 = pyo.Constraint(expr=m.x1  m.x7 + m.x13 + 1200<= 0)
>>> m.c3 = pyo.Constraint(expr=m.x2  m.x8 + m.x14 + 1500 <= 0)
>>> m.c4 = pyo.Constraint(expr=m.x3  m.x9 + m.x15 + 1100 <= 0)
>>> m.c5 = pyo.Constraint(expr=m.x4  m.x10 + m.x16 + m.p[3] <= 0)
>>> m.c6 = pyo.Constraint(expr=m.x5  m.x11 + m.x17 + 950 <= 0)
>>> m.c7 = pyo.Constraint(expr=m.x6  m.x12 + m.x18 + 1300 <= 0)
>>> m.c8 = pyo.Constraint(expr=12*m.x19  m.x25 + m.x26 == 24000)
>>> m.c9 = pyo.Constraint(expr=12*m.x20  m.x26 + m.x27 == 24000)
>>> m.c10 = pyo.Constraint(expr=12*m.x21  m.x27 + m.x28 == 24000)
>>> m.c11 = pyo.Constraint(expr=12*m.x22  m.x28 + m.x29 == 24000)
>>> m.c12 = pyo.Constraint(expr=12*m.x23  m.x29 + m.x30 == 24000)
>>> m.c13 = pyo.Constraint(expr=12*m.x24  m.x30 + m.x31 == 24000)
>>> m.c14 = pyo.Constraint(expr=8e5*m.x7**2 + m.x13 == 0)
>>> m.c15 = pyo.Constraint(expr=8e5*m.x8**2 + m.x14 == 0)
>>> m.c16 = pyo.Constraint(expr=8e5*m.x9**2 + m.x15 == 0)
>>> m.c17 = pyo.Constraint(expr=8e5*m.x10**2 + m.x16 == 0)
>>> m.c18 = pyo.Constraint(expr=8e5*m.x11**2 + m.x17 == 0)
>>> m.c19 = pyo.Constraint(expr=8e5*m.x12**2 + m.x18 == 0)
>>> m.c20 = pyo.Constraint(expr=4.97*m.x7 + m.x19 == 330)
>>> m.c21 = pyo.Constraint(expr=m.p[1]*m.x8 + m.x20 == 330)
>>> m.c22 = pyo.Constraint(expr=4.97*m.x9 + m.x21 == 330)
>>> m.c23 = pyo.Constraint(expr=4.97*m.x10 + m.x22 == 330)
>>> m.c24 = pyo.Constraint(expr=m.p[2]*m.x11 + m.x23 == 330)
>>> m.c25 = pyo.Constraint(expr=4.97*m.x12 + m.x24 == 330)
Step 2: Define the Uncertainty¶
First, we need to collect into a list those Param
objects of our model that represent potentially uncertain parameters. For purposes of our example, we shall assume uncertainty in the model parameters (m.p[0], m.p[1], m.p[2], m.p[3])
, for which we can conveniently utilize the m.p
object (itself an indexed Param
object).
>>> # === Specify which parameters are uncertain ===
>>> uncertain_parameters = [m.p] # We can pass IndexedParams this way to PyROS, or as an expanded list per index
Note
Any Param
object that is to be considered uncertain by PyROS must have the property mutable=True
.
PyROS will seek to identify solutions that remain feasible for any realization of these parameters included in an uncertainty set. To that end, we need to construct an UncertaintySet
object. In our example, let us utilize the BoxSet
constructor to specify an uncertainty set of simple hyperrectangular geometry. For this, we will assume each parameter value is uncertain within a percentage of its nominal value. Constructing this specific UncertaintySet
object can be done as follows.
>>> # === Define the pertinent data ===
>>> relative_deviation = 0.15
>>> bounds = [(nominal_values[i]  relative_deviation*nominal_values[i],
... nominal_values[i] + relative_deviation*nominal_values[i])
... for i in range(4)]
>>> # === Construct the desirable uncertainty set ===
>>> box_uncertainty_set = pyros.BoxSet(bounds=bounds)
Step 3: Solve with PyROS¶
PyROS requires the user to supply one local and one global NLP solver to be used for solving subproblems. For convenience, we shall have PyROS invoke BARON as both the local and the global NLP solver.
>>> # === Designate local and global NLP solvers ===
>>> local_solver = pyo.SolverFactory('baron')
>>> global_solver = pyo.SolverFactory('baron')
Note
Additional solvers to be used as backup can be designated during the solve
statement via the config options backup_local_solvers
and backup_global_solvers
presented above.
The final step in solving a model with PyROS is to designate the remaining required inputs, namely first_stage_variables
and second_stage_variables
. Below, we present two separate cases.
PyROS Termination Conditions¶
PyROS will return one of six termination conditions upon completion. These termination conditions are tabulated below.
Termination Condition  Description 
pyrosTerminationCondition.robust_optimal 
The final solution is robust optimal 
pyrosTerminationCondition.robust_feasible 
The final solution is robust feasible 
pyrosTerminationCondition.robust_infeasible 
The posed problem is robust infeasible 
pyrosTerminationCondition.max_iter 
Maximum number of GRCS iteration reached 
pyrosTerminationCondition.time_out 
Maximum number of time reached 
pyrosTerminationCondition.subsolver_error 
Unacceptable return status(es) from a usersupplied subsolver 
A SingleStage Problem¶
If we choose to designate all variables as either design or state variables, without any control variables (i.e., all degrees of freedom are firststage), we can use PyROS to solve the singlestage problem as shown below. In particular, let us instruct PyROS that variables m.x1
through m.x6
, m.x19
through m.x24
, and m.x31
correspond to firststage degrees of freedom.
>>> # === Designate which variables correspond to first and secondstage degrees of freedom ===
>>> first_stage_variables =[m.x1, m.x2, m.x3, m.x4, m.x5, m.x6,
... m.x19, m.x20, m.x21, m.x22, m.x23, m.x24, m.x31]
>>> second_stage_variables = []
>>> # The remaining variables are implicitly designated to be state variables
>>> # === Call PyROS to solve the robust optimization problem ===
>>> results_1 = pyros_solver.solve(model = m,
... first_stage_variables = first_stage_variables,
... second_stage_variables = second_stage_variables,
... uncertain_params = uncertain_parameters,
... uncertainty_set = box_uncertainty_set,
... local_solver = local_solver,
... global_solver= global_solver,
... options = {
... "objective_focus": pyros.ObjectiveType.worst_case,
... "solve_master_globally": True,
... "load_solution":False
... })
===========================================================================================
PyROS: Pyomo Robust Optimization Solver ...
===========================================================================================
...
INFO: Robust optimal solution identified. Exiting PyROS.
>>> # === Query results ===
>>> time = results_1.time
>>> iterations = results_1.iterations
>>> termination_condition = results_1.pyros_termination_condition
>>> objective = results_1.final_objective_value
>>> # === Print some results ===
>>> single_stage_final_objective = round(objective,1)
>>> print("Final objective value: %s" % single_stage_final_objective)
Final objective value: 48367380.0
>>> print("PyROS termination condition: %s" % termination_condition)
PyROS termination condition: pyrosTerminationCondition.robust_optimal
PyROS Results Object¶
The results object returned by PyROS allows you to query the following information from the solve call:
total iterations of the algorithm iterations
, CPU time time
, the GRCS algorithm termination condition pyros_termination_condition
,
and the final objective function value final_objective_value
. If the option load_solution
= True
(default), the variables in the model will be
loaded to the solution determined by PyROS and can be obtained by querying the model variables. Note that in the results obtained above, we set load_solution
= False
.
This is to ensure that the next set of runs shown here can utilize the original deterministic model, as the initial point can affect the performance of subsolvers.
Note
The reported final_objective_value
and final model variable values depend on the selection of the option objective_focus
.
The final_objective_value
is the sum of firststage and secondstage objective functions.
If objective_focus = ObjectiveType.nominal
, secondstage objective and variables are evaluated at the nominal realization of the uncertain parameters, \(q^0\).
If objective_focus = ObjectiveType.worst_case
, secondstage objective and variables are evaluated at the worstcase realization of the uncertain parameters, \(q^{k^\ast}\) where \(k^\ast = argmax_{k \in \mathcal{K}} f_2(x,z^k,y^k,q^k)\) .
An example of how to query these values on the previously obtained results is shown in the code above.
A TwoStage Problem¶
For this next set of runs, we will assume that some of the previously designated firststage degrees of freedom are in fact secondstage ones. PyROS handles secondstage degrees of freedom via the use of decision rules, which is controlled with the config option decision_rule_order
presented above. Here, we shall select affine decision rules by setting decision_rule_order
to the value of 1.
>>> # === Define the variable partitioning
>>> first_stage_variables =[m.x5, m.x6, m.x19, m.x22, m.x23, m.x24, m.x31]
>>> second_stage_variables = [m.x1, m.x2, m.x3, m.x4, m.x20, m.x21]
>>> # The remaining variables are implicitly designated to be state variables
>>> # === Call PyROS to solve the robust optimization problem ===
>>> results_2 = pyros_solver.solve(model = m,
... first_stage_variables = first_stage_variables,
... second_stage_variables = second_stage_variables,
... uncertain_params = uncertain_parameters,
... uncertainty_set = box_uncertainty_set,
... local_solver = local_solver,
... global_solver = global_solver,
... options = {
... "objective_focus": pyros.ObjectiveType.worst_case,
... "solve_master_globally": True,
... "decision_rule_order": 1
... })
===========================================================================================
PyROS: Pyomo Robust Optimization Solver ...
...
INFO: Robust optimal solution identified. Exiting PyROS.
>>> # === Compare final objective to the singestage solution
>>> two_stage_final_objective = round(pyo.value(results_2.final_objective_value),1)
>>> percent_difference = 100 * (two_stage_final_objective  single_stage_final_objective)/(single_stage_final_objective)
>>> print("Percent objective change relative to constant decision rules objective: %.2f %%" % percent_difference)
Percent objective change relative to constant decision rules objective: 24...
In this example, when we compare the final objective value in the case of constant decision rules (no secondstage recourse) and affine decision rules, we see there is a ~25% decrease in total objective value.
The Price of Robustness¶
Using appropriately constructed hierarchies, PyROS allows for the facile comparison of robust optimal objectives across sets to determine the “price of robustness.”
For the set we considered here, the BoxSet
, we can create such a hierarchy via an array of relative_deviation
parameters to define the size of these uncertainty sets.
We can then loop through this array and call PyROS within a loop to identify robust solutions in light of each of the specified BoxSet
objects.
>>> # This takes a long time to run and therefore is not a doctest
>>> # === An array of maximum relative deviations from the nominal uncertain parameter values to utilize in constructing box sets
>>> relative_deviation_list = [0.00, 0.10, 0.20, 0.30, 0.40]
>>> # === Final robust optimal objectives
>>> robust_optimal_objectives = []
>>> for relative_deviation in relative_deviation_list:
... bounds = [(nominal_values[i]  relative_deviation*nominal_values[i],
... nominal_values[i] + relative_deviation*nominal_values[i])
... for i in range(4)]
... box_uncertainty_set = pyros.BoxSet(bounds = bounds)
... results = pyros_solver.solve(model = m,
... first_stage_variables = first_stage_variables,
... second_stage_variables = second_stage_variables,
... uncertain_params = uncertain_parameters,
... uncertainty_set = box_uncertainty_set,
... local_solver = local_solver,
... global_solver = global_solver,
... options = {
... "objective_focus": pyros.ObjectiveType.worst_case,
... "solve_master_globally": True,
... "decision_rule_order": 1
... })
... if results.pyros_termination_condition != pyros.pyrosTerminationCondition.robust_optimal:
... print("This instance didn't solve to robust optimality.")
... robust_optimal_objective.append("")
... else:
... robust_optimal_objectives.append(str(results.final_objective_value))
For this example, we obtain the following price of robustness results:
Uncertainty Set Size (+/) ^{o}  Robust Optimal Objective  % Increase ^{x} 
0.00  35,837,659.18  0.00 % 
0.10  36,135,191.59  0.82 % 
0.20  36,437,979.81  1.64 % 
0.30  43,478,190.92  17.57 % 
0.40  robust_infeasible 
\(\text{}\) 
Note how, in the case of the last uncertainty set, we were able to utilize PyROS to show the robust infeasibility of the problem.
^{o} Relative Deviation from Nominal Realization
^{x} Relative to Deterministic Optimal Objective
This clearly illustrates the impact that the uncertainty set size can have on the robust optimal objective values. Price of robustness studies like this are easily implemented using PyROS.
Warning
PyROS is still under a beta release. Please provide feedback and/or report any problems by opening an issue on the Pyomo GitHub page.