Multiple Scenarios#
When solving an optimization model, it is often useful to understand the sensitivity of the computed solution to changes in the inputs. How would profits be affected if the price of a particular raw material increased significantly? Would I still be able to satisfy customer orders if one of my machines broke down? The most general form of this problem would fall into the domain of stochastic or robust optimization, but those fields bring significant complexity with them. The Gurobi Optimizer includes scenario analysis features that have a much more modest goal: to allow the user to specify a set of scenarios, and to compute optimal solutions for all of these scenarios as quickly as possible. These solutions often provide significant insight into how the solution would change as inputs vary.
Definition of a Multi-Scenario Model#
Before diving into the details of working with multiple scenarios, we
first need to explain exactly what we mean by the term. Let us start by
claiming that it only makes sense to consider a set of models as being
different scenarios for the same underlying model if they have a lot in
common. They should definitely share the same set of variables. They
should also have similar sets of constraints and similar objectives. In
our approach, scenarios are described as a set of changes from a single
base
model. More specifically, scenarios can only modify model
features that are present in the base model. We should add that other
modifications, including addition and deletion of variables or
constraints, can be achieved through the clever use of various
tricks. For now, though, it is best
to think of scenarios as being small variations on the same base.
What variations do we allow from this base model? Scenarios can differ in the following attributes:
Linear objective function coefficients.
Variable lower and upper bounds.
Constraint right-hand side values.
A single scenario can have multiple changes from the base, so for example you could change an objective coefficient, two variable bounds, and a right-hand side value in the same scenario.
After you have defined a set of scenarios (the specific mechanics for
doing so will be described shortly),
the next step is to find solutions for all of the scenarios. A single
call to the standard Gurobi optimize
method is all that is needed.
This will of course be much more expensive than finding an optimal
solution for a single model, but our goal is for it to be faster and
more convenient than formulating and solving separate models for each
scenario.
Specifying Multiple Scenarios#
Your first step in building a multi-scenario model is to modify the
NumScenarios attribute to indicate how many scenarios you would
like to consider. Once you have changed this attribute, you can describe the
different scenarios by changing various scenario-related attributes (listed
below). When you later call optimize
on a multi-scenario model (a model
where NumScenarios is greater than 0), the solver will try to find
optimal solutions for all specified scenarios. Note that it will not try to
find a solution for the base model.
Variations in the different scenarios are expressed through a set of four attributes:
The first three are variable attributes, and the last is a linear constraint attribute. You can give each scenario a name through the ScenNName attribute (a model attribute).
You use the ScenarioNumber parameter to modify scenario
attributes for a specific scenario. Scenarios are numbered 0
through
NumScenarios-1
. To give an example, to create a model where binary
variable \(x\) is fixed to 0
and 1
in two scenarios, you
would:
Set the NumScenarios attribute to 2, to indicate that your model has two scenarios.
Set the ScenarioNumber parameter to 0, to indicate that you would first like to modify scenario attributes for scenario 0.
Set the ScenNUB attribute for variable \(x\) to 0 (to fix the binary variable to zero in this scenario).
Set the ScenarioNumber parameter to 1, to move on to scenario 1.
Set the ScenNLB attribute for variable \(x\) to 1 (to fix the binary variable to one in this scenario).
You query scenario attributes in a similar manner: set the ScenarioNumber parameter to choose the scenario you would like to query, and then use the appropriate attribute query routine to obtain the desired attribute values (consult our Attribute Examples for examples).
Note that unmodified scenario attributes take a special value of
GRB.UNDEFINED
. If you modified a scenario attribute and would like
to revert that modification, you can set the attribute back to
GRB.UNDEFINED
.
You can change the number of scenarios in your model as many times as you like (by modifying the NumScenarios attribute). When you increase the count, new empty scenarios are created (an empty scenario is a scenario with no changes from the base model). When you decrease the count, existing scenarios are discarded. When you set the count to zero, the model is no longer treated as a multi-scenario model.
We have extended the LP and MPS file formats, so writing a model with multiple scenarios to a file will capture those scenarios. Similarly, if you read a model file that contains multiple scenarios, then NumScenarios and the various scenario attributes will capture the scenarios stored in the file. See the file format section for details.
Logging#
When solving a multi-scenario model, logging is somewhat different from standard MIP logging. You should consult this section for details.
Retrieving Solutions for Multiple Scenarios#
Your first step in retrieving the solutions computed by an optimize
call
on a multi-scenario model is to query the Status attribute. A
status of OPTIMAL indicates that optimal solutions
were found (subject to tolerances) for all scenarios that have feasible
solutions, and that the other scenarios were determined to be infeasible. If
all scenarios were found to be infeasible, the status will be
INFEASIBLE. If any scenario is unbounded, the status
will be UNBOUNDED. An early termination status code
(e.g., TIME_LIMIT) indicates that the outcomes may
vary across the different scenarios, and you will have to look at individual
scenarios for more information.
Results for individual scenarios can be found in three attributes:
ScenNObjVal: The objective value for the solution for scenario number \(n\).
ScenNObjBound: The best known bound on the optimal objective value for scenario number \(n\).
ScenNX: The solution vector for scenario number \(n\).
The ScenNObjVal and ScenNObjBound attributes are model attributes, while ScenNX is a variable attribute. Again, use the ScenarioNumber parameter to select the scenario you’d like to query.
If your optimize
call terminated early, you should use the
ScenNObjBound attribute to interpret the results. This attribute
provides a bound on the optimal objective value for the selected scenario
(much like ObjBound provides a bound for a single model). For
example, if ScenNObjVal is 100 for a scenario and
ScenNObjBound is 90 (assuming minimization), then you have a
solution with a 10% optimality gap for that scenario.
You can also query the ObjVal and ObjBound attributes for a multi-scenario model. The former gives the best objective value for any solution found in any scenario. The latter provides a lower bound on the objective value for any solution that was not found.
Note that ScenNObjVal and ScenNObjBound are computed using the objective function for the corresponding scenario, which you may have changed from the base model.
If you query the ScenNX attribute and no feasible solution has been
found for that scenario, you will get a DATA_NOT_AVAILABLE
error.
Tips and Tricks#
Through clever use of the features provided in the multi-scenario interface, it is actually possible to do a lot more than it may first appear.
Adding or Deleting Variables or Constraints#
The multi-scenario interface provides no way to add or remove variables
or constraints in a scenario, but the same effect can be achieved by
changing variable bounds and constraint right-hand side values. To
remove a variable in a scenario, simply change its lower and upper
bounds to zero. To add a variable, set its bounds to zero in the base
model and change them to their true values in the scenario. To remove a
less-than constraint, change the right-hand-value in the scenario to
GRB.INFINITY
. To add one, set its right-hand side to
GRB.INFINITY
in the base model and change it to its true value in
the scenario.
Changing the sense of a constraint can also be done using similar
tricks. For example, you can transform an equality constraint in the
base model into an inequality in a scenario by splitting the equality
into a pair of inequalities. The right-hand side values for both
inequalities in the base model would be equal to the true value in the
equality. The right-hand side value on one of the two inequalities can
then be relaxed to GRB.INFINITY
in the scenario.
You can also change the type of a variable. For example, to transform an integer variable in the base model into a continuous variable in a scenario, you can add both variables in the base model, along with a split equality constraint that sets them equal to each other. That equality constraint could then be relaxed in the scenario (using the techniques just described).
This isn’t meant to be an exhaustive list of all of the ways that you can use supported multi-scenario features to achieve seemingly unsupported outcomes. The set of building blocks that we have provided can be assembled in a variety of different ways.
If all scenarios in your multi-scenario model are infeasible, your
optimize
call will produce an INFEASIBLE (or
INF_OR_UNBD) status code. While you can’t compute an
IIS on a multi-scenario model, you can extract individual scenarios as Gurobi
model objects using the singleScenarioModel
method (see below) and then
compute an IIS on each scenario individually.
Solving The Base Model#
As noted earlier, an optimize
call on a multi-scenario model will
not solve the base model. If you’d like to solve that model too, include
an empty scenario among your scenarios.
Extracting One Scenario#
If you’d like to extract one scenario from a multi-scenario model, you
can use the singleScenarioModel
method (in
C
,
C++
,
Java
,
.NET
, and
Python
).
Performance Considerations#
While it may appear to be important to minimize the number of scenarios in your model, note that some scenarios are trivial to solve and thus have no impact on overall solution cost. The main thing to keep in mind is that, if (1) the solution for one scenario is feasible for another scenario, and (2) the bounds and right-hand side values for the first scenario are never tighter than the bounds for the other scenario, then the optimal solution for the first scenario is also optimal for the other scenario. This means that some scenarios won’t increase solution cost significantly.
Limitations and Additional Considerations#
We should note a few additional considerations for solving multi-scenario models.
Nearly any model can serve as the base model in a multi-scenario model. The base model can be continuous or discrete, and it can contain quadratic constraints, general constraints, SOS constraints, semi-continuous variables, etc. One important exception is multiple objectives; a multi-scenario model can only have a single objective.
The IsMIP attribute will always be 1 for a multi-scenario model, even if the model is otherwise continuous.
Multi-scenario models can have lazy constraints, specified either through a user callback or through the Lazy attribute. Those that are provided through a callback must be valid across all scenarios. Those that are specified through the Lazy attribute can have different right-hand side values in different scenarios.
You can also add your own cutting planes to multi-scenario models through a user callback. Cuts must be valid across all scenarios.