Skip to content

Commit

Permalink
add tutorial on pessimistic bilevel
Browse files Browse the repository at this point in the history
  • Loading branch information
hlefebvr committed Jan 23, 2025
1 parent 5fc7d1e commit 5fac880
Show file tree
Hide file tree
Showing 18 changed files with 412 additions and 219 deletions.
63 changes: 61 additions & 2 deletions docs/tutorials/bilevel-optimization/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,68 @@
Bilevel Optimization
====================

Bilevel optimization is a field of mathematical optimization in which two optimization problems are intertwined: the
upper-level and the lower-level problem. The upper-level problem minimizes a given objective function taking into account
the solution to the lower-level problem, which is parameterized by the upper-level’s decision. Such problems have many
applications in, e.g., economics where it is used to model non-cooperative games.

A classic model for a bilevel problem is as follows.

.. math::
:label: bilevel
\begin{align}
\text{''}\min_{x}\text{''} \quad & F(x,y) \\
\text{s.t.} \quad & G(x,y) \ge 0, \\
& x\in X, \\
& y\in S(x),
\end{align}
in which :math:`S(x)` denotes the solution set of the lower-level problem, which is parameterized by the upper-level decision
:math:`x`. The lower-level problem is defined as

.. math::
\begin{align}
\min_{y} \quad & f(x,y) \\
\text{s.t.} \quad & g(x,y) \ge 0, \\
& y\in Y.
\end{align}
Note that the quotes around the :math:`\min` operator in :math:numref:`bilevel` is here to highlight that the problem is
ill-defined in its current form. Indeed, in case multiple solutions to the lower-level problem exist, the upper-level problem
has to somehow "choose" one of them. To circumvent this, we typically consider the optimistic setting, where the lower-level
is assumed to pick the solution in favor of the upper-level problem, in order to break ties.

Optimistic bilevel problems can be modeled as:

.. math::
\begin{align}
\min_{x,y} \quad & F(x,y) \\
\text{s.t.} \quad & G(x,y) \ge 0, \\
& x\in X, \\
& y\in S(x),
\end{align}
Note that there exists other notions such as pessimistic bilevel problems. There, the lower-level problem is assumed to pick
the worst solution for the upper-level problem. This can be modeled as follows.

.. math::
\begin{align}
\min_{x} \quad & \max_{ y\in S(x) } \ F(x,y) \\
\text{s.t.} \quad & G(x,\bar y) \ge 0, \quad \text{for all } \bar y\in S(x) \\
& x\in X, \\
& S(x) \neq \emptyset.
\end{align}
Pessimisitc bilevel problems are less studied in the literature.

.. toctree::
:maxdepth: 3
:glob:

modeling/index
wrappers/index
modeling
mibs
pessimistic
66 changes: 66 additions & 0 deletions docs/tutorials/bilevel-optimization/mibs.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
Solving Mixed-Integer Bilevel Problems with coin-or/MibS
========================================================

MibS is an optimization solver for mixed-integer bilevel problems; for more information, please refer to the `MibS website <https://projects.coin-or.org/MibS>`_.
Idol seamlessly integrates with MibS to solve bilevel problems.

We will see that solving bilevel problems with MibS is very similar to solving any optimization problem in idol.

.. contents:: Table of Contents
:local:
:depth: 2

Setup
-----

We will assume that you have your bilevel problem already modeled in idol. In particular, we consider that you have
two variables:

1. :code:`high_point_relaxation` which is a :code:`Model` representing the high-point relaxation of your bilevel problem.

2. :code:`description` which is a :code:`Bilevel:Description` object representing the bilevel problem. If you do not know what this is or how to create it, please refer to the :ref:`previous tutorial <tutorial_optimistic_bilevel>`.

Using MibS
----------

To solve your bilevel problem, you can use the :code:`MibS` optimizer factory as follows:

.. code::
high_point_relaxation.use(Bilevel::MibS(description));
high_point_relaxation.optimize();
std::cout << save_primal(high_point_relaxation) << std::endl;
Notice how the MibS optimizer factory is attached to the high-point relaxation model and that the bilevel description
is passed as an argument.

The rest of the code is the same as with any other solver.

.. hint::

To use MibS, you need to have the MibS library installed on your system and idol linked to the executable.
You can download MibS from `here <https://projects.coin-or.org/MibS>`_.

Then, idol should be compiled with the options :code:`USE_MIBS=YES`, :code:`USE_CLP=YES`.

Using CPLEX for Feasibility Check
---------------------------------

Note that it is also possible to use MibS in combination with CPLEX for the feasibility check. This can be done as follows:

.. code::
const auto mibs = Bilevel::MibS(description)
.with_cplex_for_feasibility(true)
.with_time_limit(3600)
.with_logs(true);
high_point_relaxation.use(mibs);
high_point_relaxation.optimize();
std::cout << save_primal(high_point_relaxation) << std::endl;
Off course, MibS must have been installed with CPLEX for this to work.
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
.. _tutorial_optimistic_bilevel:

Modeling an Optimistic Bilevel Problem
======================================
Modeling a Bilevel Problem
==========================

In this tutorial, we will see how to model an optimistic bilevel problem in idol.
In this tutorial, we will see how to model a bilevel problem in idol.

To follow this tutorial, you should be familiar with bilevel optimization and modeling optimization problems in idol.
If this is not the case, we recommend you to read the tutorial on :ref:`MIP modeling <mip_modeling>`.
Expand All @@ -20,17 +20,17 @@ We consider the optimistic bilevel problem
.. math::
\begin{align}
\min_{x, y} \ & -x + -10 y \\
\text{s.t.} \ & x \in \mathbb Z_+ \\
\min_{x, y} \quad & -x + -10 y \\
\text{s.t.} \quad & x \in \mathbb Z_{\ge 0} \\
& y\in
\begin{array}[t]{rl}
\displaystyle \underset{y}{\text{arg min}} \ & y \\
\text{s.t.} \ & -25 x + 20 y \leq 30, \\
\displaystyle \underset{y}{\text{arg min}} \quad & y \\
\text{s.t.} \quad & -25 x + 20 y \leq 30, \\
& x + 2 y \leq 10, \\
& 2 x - y \leq 15, \\
& 2 x + 10 y \geq 15, \\
& y \geq 0, \\
& y \in \mathbb Z_+.
& y \in \mathbb Z_{\ge 0}.
\end{array}
\end{align}
Expand Down Expand Up @@ -74,66 +74,64 @@ Here is the code to model the HPR of the bilevel problem.
Env env;
Model high_point_relaxation(env);
auto x = high_point_relaxation.add_var(0, Inf, Integer, "x");
auto y = high_point_relaxation.add_var(0, Inf, Integer, "y");
auto x = high_point_relaxation.add_var(0, Inf, Integer, 0, "x");
auto y = high_point_relaxation.add_var(0, Inf, Integer, 0, "y");
high_point_relaxation.set_obj_expr(-x - 10 * y);
auto follower_c1 = high_point_relaxation.add_ctr(-25 * x + 20 * y <= 30);
auto follower_c1 = high_point_relaxation.add_ctr(-25*x + 20*y <= 30);
auto follower_c2 = high_point_relaxation.add_ctr(x + 2 * y <= 10);
auto follower_c3 = high_point_relaxation.add_ctr(2 * x - y <= 15);
auto follower_c4 = high_point_relaxation.add_ctr(2 * x + 10 * y >= 15);
Describing the Lower-Level Problem
----------------------------------

To describe the lower-level problem, we need to specify which variables and constraints are part of the lower-level problem.
This done by creating an object of type :code:`Bilevel::LowerLevelDescription` and calling the methods :code:`set_follower_var`
and :code:`set_follower_ctr`.
To describe the lower-level problem, we need to specify which variables and constraints are part of it.
This done by creating an object of type :code:`Bilevel::Description` and calling the methods :code:`make_follower`.

.. code::
Bilevel::LowerLevelDescription description(env);
description.set_follower_var(y);
description.set_follower_ctr(follower_c1);
description.set_follower_ctr(follower_c2);
description.set_follower_ctr(follower_c3);
description.set_follower_ctr(follower_c4);
Bilevel::Description description(env);
description.make_follower(y);
description.make_follower(follower_c1);
description.make_follower(follower_c2);
description.make_follower(follower_c3);
description.make_follower(follower_c4);
Note that this does nothing more but to create two new :code:`Annotation<Var|Ctr>` to indicate variables and constraints that are part of the lower-level problem.
These annotations are used by the bilevel solver to identify the lower-level problem.
In particular, all variables and constraints that are not annotated as follower variables or constraints are considered as leader variables and constraints
and have an annotation which is equal to :code:`MasterId`.
Note that this does nothing more but to create a new :code:`Annotation<unsigned int>` to indicate variables and constraints that are part of the lower-level problem.
These annotations are used by the bilevel solvers to identify the lower-level problem.
In particular, all variables and constraints that are not annotated as follower variables or constraints are considered as leader variables or constraints, respectively.
and have an annotation which is set to :code:`MasterId`.

Also note that it is possible to create and use your own annotation. For instance, the following code is equivalent to the previous one.


.. code::
Annotation<Var> follower_vars(env, MasterId, "follower_variables");
y.set(follower_vars, 0);
Annotation<unsigned int> follower(env, MasterId, "follower");
Annotation<Ctr> follower_ctrs(env, MasterId, "follower_constraints");
follower_c1.set(follower_ctrs, 0);
follower_c2.set(follower_ctrs, 0);
follower_c3.set(follower_ctrs, 0);
follower_c4.set(follower_ctrs, 0);
y.set(follower, 0);
follower_c1.set(follower, 0);
follower_c2.set(follower, 0);
follower_c3.set(follower, 0);
follower_c4.set(follower, 0);
Bilevel::LowerLevelDescription description(follower_vars, follower_ctrs);
Bilevel::Description description(follower);
Defining the Lower-Level Objective Function
-------------------------------------------

Finally, we need to define the lower-level objective function.
This is done by calling the method :code:`set_follower_obj_expr` on the object of type :code:`Bilevel::LowerLevelDescription`.
An :code:`AffExpr` object is passed as argument to this method.
This is done by calling the method :code:`set_follower_obj` on the object of type :code:`Bilevel::Description`.
A :code:`QuadExpr` object is passed as argument to this method.

.. code::
description.set_follower_obj_expr(y);
description.set_follower_obj(y);
Complete Example
----------------

A complete example is available :ref:`here <example_mibs>`.
A complete example is available :ref:`here <example_mibs>` and uses the MibS solver.
8 changes: 0 additions & 8 deletions docs/tutorials/bilevel-optimization/modeling/index.rst

This file was deleted.

100 changes: 100 additions & 0 deletions docs/tutorials/bilevel-optimization/pessimistic.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
.. _tutorial_pessimistic_bilevel:

From Pessimistic Bilevel Optimization to Optimistic Bilevel Optimization
========================================================================

Most of the litereature on bilevel optimization focuses on the optimistic setting, where the lower-level problem is assumed to
pick the solution in favor of the upper-level problem.
However, there exists other notions such as pessimistic bilevel problems.

A pessimistic problem reads as follows:

.. math::
:label: pessimistic
\begin{align}
\min_{x} \quad & \max_{ y\in S(x) } \ F(x,y) \\
\text{s.t.} \quad & x\in X, \\
& S(x) \neq \emptyset,
\end{align}
with :math:`S(x)` the solution set of

.. math::
\begin{align}
\min_{y} \quad & f(x,y) \\
\text{s.t.} \quad & g(x,y) \ge 0, \\
& y\in Y.
\end{align}
In this tutorial, we will show how a pessimistic bilevel problem can be automatically transformed into an optimistic bilevel problem.
This transformation is due to :cite:`Zeng2020`.

.. hint::

Here, :math:numref:`pessimistic` does not have coupling constraints for simplicity.
However, the transformation can be extended to bilevel problems with coupling constraints.

.. contents:: Table of Contents
:local:
:depth: 2

The Equivalent Optimistic Problem
---------------------------------

In :cite:`Zeng2020`, the authors show that the pessimistic bilevel problem :math:numref:`pessimistic` is equivalent to the following optimistic bilevel problem:

.. math::
:label: optimistic
\begin{align}
\min_{x,\bar y} \quad & F(x,y) \\
\text{s.t.} \quad & x\in X, \ \bar y\in Y, \\
& g(x,\bar y) \ge 0, \\
& y\in \tilde S(x, \bar y),
\end{align}
in which :math:`\tilde S(x, \bar y)` is the solution set of

.. math::
\begin{align}
\min_y \quad & -F(x,y) \\
\text{s.t.} \quad & g(x,y) \ge 0, \\
& y\in Y, \\
& f(x,y) \le f(x, \bar y).
\end{align}
Note that :math:numref:`optimistic` is an optimistic bilevel problem.

Implementation
--------------

Deriving the equivalent optimistic bilevel problem from a pessimistic bilevel problem can be done easily in idol.

To this end, let us assume that you have your bilevel problem already modeled in idol. In particular, let us consider that you have
two variables:

1. :code:`high_point_relaxation` which is a :code:`Model` representing the high-point relaxation of your bilevel problem.

2. :code:`description` which is a :code:`Bilevel:Description` object representing the bilevel problem. If you do not know what this is or how to create it, please refer to the :ref:`previous tutorial <tutorial_optimistic_bilevel>`.

Then, you can derive the equivalent optimistic bilevel problem as follows:

.. code::
auto [opt_model, opt_description] =
Bilevel::PessimisticAsOptimistic::make_model(high_point_relaxation, description);
Here, :code:`opt_model` is the high-point relaxation of :math:numref:`optimistic` and :code:`opt_description` is the bilevel description of :math:numref:`optimistic`.

The rest of the code is the same as with any other solver. For instance, you can solve the optimistic bilevel problem with MibS as follows:

.. code::
opt_model.use(Bilevel::MibS(opt_description));
opt_model.optimize();
std::cout << save_primal(opt_model) << std::endl;
8 changes: 0 additions & 8 deletions docs/tutorials/bilevel-optimization/wrappers/index.rst

This file was deleted.

Loading

0 comments on commit 5fac880

Please sign in to comment.