diff --git a/docs/examples/two-stage-robust-facility-location-problem.rst b/docs/examples/two-stage-robust-facility-location-problem.rst index edbbcada..508ee29d 100644 --- a/docs/examples/two-stage-robust-facility-location-problem.rst +++ b/docs/examples/two-stage-robust-facility-location-problem.rst @@ -18,7 +18,7 @@ Each customer :math:`j\in V_2` has a demand :math:`d_j`. The unitary cost for serving customer :math:`j\in V_2` from facility :math:`i\in V_1` is :math:`t_{ij}`. The uncertainty in customer demands is controlled by a parameter :math:`\Gamma`. -In this robust variant, we consider that the demands are uncertain and can be expressed as :math:`d_j(\xi) = d_j(1 + p\xi_j)`` +In this robust variant, we consider that the demands are uncertain and can be expressed as :math:`d_j(\xi) = d_j(1 + p\xi_j)` with :math:`p` being the maximum increase in demand and :math:`\xi` being an unknown vector taken in the uncertainty set .. math:: diff --git a/docs/tutorials/robust-optimization/column-and-constraint-generation/write-ccg.rst b/docs/tutorials/robust-optimization/column-and-constraint-generation/write-ccg.rst index 5718d08e..8c5529b7 100644 --- a/docs/tutorials/robust-optimization/column-and-constraint-generation/write-ccg.rst +++ b/docs/tutorials/robust-optimization/column-and-constraint-generation/write-ccg.rst @@ -1,2 +1,49 @@ +.. _tutorial_ccg: + Writing a Column-and-Constraint-Generation Algorithm ==================================================== + +In this tutorial, we will see how to write a column-and-constraint-generation to solve a two-stage robust problem. + +To this end, we will assume that you have your two-stage robust problem modeled already in idol. In particular, +we consider that you have + +1. :code:`(idol::Model) model` which is the deterministic model of the problem in which the uncertain data are seen as parameters. + +2. :code:`(idol::Model) uncertainty_set` which is the uncertainty set of the robust problem. + +3. :code:`(idol::Robust::StageDescription) stages` which stores the assignments of variables and constraints to each stage. + +If you do not know what these are, please refer to the tutorial on :ref:`how to model a two-stage robust problem `. + +Then, you can solve this two-stage robust problem using a column-and-constraint-generation algorithm as follows: + +.. code:: + + model.use( + Robust::ColumnAndConstraintGeneration(stages, uncertainty_set) + .with_master_optimizer(Gurobi()) + .with_separator(Robust::CCGSeparators::Bilevel()) + .with_logs(true) + ); + + model.optimize(); + + std::cout << save_primal(model) << std::endl; + +Notice that the optimizer is attached to the deterministic model and that both the uncertainty set and the stages are passed as arguments. +An optimizer to solve the master problem is necessary and will be created when necessary. Here, we use the Gurobi optimizer. + +Most importantly, it is also necessary to specify a separator. The separator is a sub-routine of the CCG algorithm +which solves the separation problem, i.e., it finds the worst-case scenario for a given solution of the master problem. +In this example, we use the Bilevel separator which calls the MibS bilevel optimization solver at each iteration. + +Finally, the method :code:`optimize` is called to solve the problem and the solution is printed. + +.. admonition:: + + Here, we have assumed that the problem does not satisfy the complete recourse assumption, i.e., it is not known if + :math:`\forall x\in X, \forall\xi\in\Xi, \exists y\in Y(x,\xi)`. If, to the contrary, this assumption holds, + it can be communicated to the solver by calling the method :code:`with_complete_recourse(true)`. By doing this, + the CCG algorithms will not solve the feasibility separation problem which is most likely to result in a faster convergence. + diff --git a/docs/tutorials/robust-optimization/modeling/two-stage-robust.rst b/docs/tutorials/robust-optimization/modeling/two-stage-robust.rst index cf4fa7c4..ffd96f78 100644 --- a/docs/tutorials/robust-optimization/modeling/two-stage-robust.rst +++ b/docs/tutorials/robust-optimization/modeling/two-stage-robust.rst @@ -1,3 +1,216 @@ -Modeling a Two-Stage Robust Problem [TODO] -========================================== +.. _modeling_two_stage_robust_problem: +Modeling a Two-Stage Robust Problem +=================================== + +In this tutorial, we will see how to model a two-stage robust problem in idol. + +To follow this tutorial, you should be familiar with two-stage robust optimization and modeling optimization problems in idol. +If this is not the case, we recommend you to read the tutorial on :ref:`MIP modeling `. + +.. contents:: Table of Contents + :local: + :depth: 2 + +A Two-Stage Robust Facility Location Problem +-------------------------------------------- + +We consider a two-stage robust facility location problem (FLP) where demands are uncertain. + +Given a set of potential facility locations :math:`V_1` and a set of customers :math:`V_2`, the goal is to select a subset of facility locations +to activate in order to serve all customers' demand, while minimizing the total cost. +This version introduces uncertainty in the customers' demands. + +Note that there is also an example for the :ref:`deterministic version of the FLP using Column Generation `. + +Each facility :math:`i\in V_1` has an opening cost :math:`f_i` and a maximum capacity :math:`q_i`. +Each customer :math:`j\in V_2` has a demand :math:`d_j`. +The unitary cost for serving customer :math:`j\in V_2` from facility :math:`i\in V_1` is :math:`{ij}`. +The uncertainty in customer demands is controlled by a parameter :math:`\Gamma`. + +In this robust variant, we consider that the demands are uncertain and can be expressed as :math:`d_j(\xi) = d_j(1 + p\xi_j)` +with :math:`p` being the maximum increase in demand and :math:`\xi` being an unknown vector taken in the uncertainty set + +.. math:: + + \Xi := \left\{ \xi\in[ 0, 1 ]^{|V_2|} : \sum_{j\in V_2} \xi_j \le \Gamma \right\}. + +We model the two-stage robust FLP as + +.. math:: + + \min_{x\in \{0,1\}^{|V_1|}} \ \left\{ \sum_{i\in V_1} f_i x_i + \max_{\xi\in \Xi} \ \min_{y\in Y(x,\xi)} \ \sum_{i\in V_1} \sum_{j\in V_2} {ij} y_{ij} \right\} + +where :math:`Y(x,\xi)` is the set of feasible solutions for the second stage problem, given the first stage solution :math:`x` and the realization :math:`\xi` of the uncertain demand vector. +It is defined as the set of vectors :math:`y\in \mathbb{R}^{|V_1|\times|V_2|}` that satisfy the following constraints + +.. math:: + + \begin{align*} + & \sum_{i\in V_1} y_{ij} = d_j(\xi) && j\in V_2, \\ + & \sum_{j\in V_2} y_{ij} \le q_i x_i && i\in V_1, \\ + & y_{ij} \ge 0 && i\in V_1, j\in V_2. + \end{align*} + +In what follows, we will assume that we have a variable :code:`instance` of type :code:`idol::Problems::FLP::Instance` +that contains the data of the problem. Most typically, you will want to read the instance from a file. This is done as follows. + +.. code:: + + // Read instance + const auto instance = Problems::FLP::read_instance_1991_Cornuejols_eal("robusccg.data.txt"); + +Additionally, we create an optimization environment :code:`env` and some parameters. + +.. code:: + + Env env; + + const double Gamma = 3; + const double percentage_increase = .2; + +Modeling Steps +-------------- + +To model a two-stage robust problem, one needs to follow the following steps: + +1. Define an uncertainty set :math:`\Xi`. +2. Define the deterministic model in which :math:`\xi` is a parameter. +3. Assign a stage to each variable and constraint. + +Let's now see how to model the two-stage robust FLP in idol. + +Defining the Uncertainty Set +----------------------------- + +Modeling the uncertainty set :math:`\Xi` is done in the same way as modeling any classical optimization problem. +For instance, one can do as follows. + +.. code:: + + const unsigned int n_customers = instance.n_customers(); + + Model uncertainty_set(env); + + auto xi = uncertainty_set.add_vars(Dim<1>(n_customers), 0., 1, Binary, "xi"); + uncertainty_set.add_ctr(idol_Sum(j, Range(n_customers), xi[j]) <= Gamma); + +Defining the Deterministic Model +-------------------------------- + +The deterministic model underlying the two-stage robust FLP is the same as the classical FLP, except that the demand is a parameter. + +Recall that a variable, e.g., :code:`xi[0]`, can be turned into a parameter by prepending it with :code:`!`. + +Hence, +we can define the deterministic model as follows. + +.. code:: + + const unsigned int n_facilities = instance.n_facilities(); + + Model model(env); + + const auto x = model.add_vars(Dim<1>(n_facilities), 0., 1., Binary, "x"); + const auto y = model.add_vars(Dim<2>(n_facilities, n_customers), 0., Inf, Continuous, "y"); + + // Capacity constraints + for (unsigned int i = 0 ; i < n_facilities ; ++i) { + model.add_ctr(idol_Sum(j, Range(n_customers), y[i][j]) <= instance.capacity(i) * x[i]); + } + + // Demand satisfaction constraints + for (unsigned int j = 0 ; j < n_customers ; ++j) { + // IMPORTANT: here we use the parameter "!xi[j]" instead of the variable "xi[j]" + model.add_ctr(idol_Sum(i, Range(n_facilities), y[i][j]) == instance.demand(j) * (1 + percentage_increase * !xi[j])); + } + + // Objective function + model.seobj_expr(idol_Sum(i, Range(n_facilities), + instance.fixed_cost(i) * x[i] + + idol_Sum(j, Range(n_customers), + instance.per_unitransportation_cost(i, j) * y[i][j] + ) + ) + ); + +Assigning Stages +---------------- + +The last step is to assign a stage to each variable and constraint. Here, variables :math:`x` are first-stage variables +and variables :math:`y` are second-stage variables, i.e., they depend on the realization of the uncertain demand. +Similarly, all constraints are second-stage constraints since they are part of the second-stage feasible region. + +Assigning stages is done by creating a new object of type :code:`idol::Robust::StageDescription`. +Under the hood, this object does nothing more than defining new annotations for variables and constraints storing +the assigned stage of each variable and constraint. It is created as follows. + +.. code:: + + Robust::StageDescription stages(t_model.env()); + +By default, all variables and constraints are assigned to the first stage. +To assign a variable or constraint to the second stage, one can use the method :code:`set_stage` of the object :code:`stages`. +For instance, one can do as follows. + +.. code:: + + for (const auto& var : model.vars()) { + if (var.name().front() != 'x') { + stages.set_stage(var, 2); + } + } + +Similarly, since all constraints are second-stage constraints, one can do as follows. + +.. code:: + + for (const auto& ctr : model.ctrs()) { + stages.set_stage(ctr, 2); + } + +.. admonition:: About stage annotations + + Note that it is also possible to define your own annotations to assign variables and constraints to stages. + This is a rather advanced feeature and it is your responsability to ensure that the annotations are consistent with the model. + + The annotations are based on the following conventions: all first-stage variables and constraints have the annotation evaluating to :code:`MasterId`. + All second-stage variables and constraints have the annotation evaluating to :code:`0`. + + For instance, the following code is equivalent to the previous one. + + .. code:: + + Annotation stage_vars(model, "stage_vars", MasterId); // By default, all variables are first-stage variables + Annotation stage_ctrs(model, "stage_ctrs", MasterId); // By default, all constraints are first-stage constraints + + for (const auto& var : model.vars()) { + if (var.name().front() != 'x') { + var.set(stage_vars, 0); // Assign variable to the second stage + } + } + + for (const auto& ctr : model.ctrs()) { + ctr.set(stage_ctrs, 0); // Assign constraint to the second stage + } + + idol::Robust::StageDescription stages(stage_vars, stage_ctrs); + + By doing so, a call to :code:`stages.stage(var)` will return "1" for all first-stage variables and "2" for all second-stage variables. + The underlying annotation can be obtained using + + .. code:: + + Annotation stage_vars = stages.stage_vars() + + Finally, also note the method :code:`stages.stage_index(var)` that will return "0" for all first-stage variables and "1" for all second-stage variables. + + +That's it! We have now modeled a two-stage robust FLP in idol. Note that you will now need +to attach an optimizer to the model to solve it. +To this end, be sure to check the tutorials on optimizers for two-stage robust problems, e.g., :ref:`the column-and-constraint generation tutorial `. + +Complete Example +---------------- + +A complete example is given :ref:`here ` \ No newline at end of file