Getting Started
This tutorial provides a step-by-step guide to modeling and solving a bilevel mixed-integer programming (BiMIP) problem using PowerBiMIP. By the end of this guide, you’ll understand the basic workflow and be able to apply it to your own optimization problems.
Installation
Before we begin, please make sure you have successfully installed PowerBiMIP and its dependencies (MATLAB, YALMIP, and a MILP solver like Gurobi). If you haven’t, please follow the instructions on the Installation page.
A Simple Bilevel Programming Example
Let’s start with a classic textbook example of a BiMIP problem. This will help us illustrate the core components of a PowerBiMIP model. The full script for this example can be found in examples/BiMIP_benchmarks/BiMIP_toy_example1.m.
1. Mathematical Formulation
The problem is composed of two levels: an upper-level problem and a lower-level problem. The decision variables of the upper level affect the constraints of the lower level, and the solution of the lower level, in turn, influences the upper level’s objective function.
Upper-Level Problem: The leader’s goal is to minimize its objective function by choosing the variable
x.min_{x} -x - 10*z s.t. x >= 0 -25*x + 20*z <= 30 x + 2*z <= 10 2*x - z <= 15 2*x + 10*z >= 15
Lower-Level Problem: The follower’s problem is to minimize its objective by choosing variables
yandz, given the upper-level decisionx.min_{y,z} z + 1000 * sum(y) s.t. -25*x + 20*z <= 30 + y(1) x + 2*z <= 10 + y(2) 2*x - z <= 15 + y(3) 2*x + 10*z >= 15 - y(4) z >= 0 y >= 0
2. MATLAB Implementation with PowerBiMIP
Now, let’s translate this mathematical model into MATLAB code using PowerBiMIP.
Step 1: Initialization
It’s good practice to start with a clean slate. This command clears the MATLAB workspace, closes all figures, clears the command window, and resets YALMIP’s internal memory.
clear; close all; clc; yalmip('clear');
Step 2: Define Variables
We use YALMIP’s syntax to define the decision variables. We organize them into a model structure with specific fields: var_upper for upper-level variables and var_lower for lower-level variables.
model.var_upper.x = intvar(1,1,'full'); % Upper-level integer variable
model.var_lower.z = intvar(1,1,'full'); % Lower-level integer variable
model.var_lower.y = sdpvar(4,1,'full'); % Lower-level continuous variables
Step 3: Formulate the Model
Next, we define the constraints and objective functions for both levels. The constraints are created as standard YALMIP objects. The model structure must strictly follow these field names: cons_upper, cons_lower, obj_upper, and obj_lower.
Upper-Level Constraints & Objective:
% --- Upper-Level Constraints ---
model.cons_upper = [];
model.cons_upper = model.cons_upper + (model.var_upper.x >= 0);
model.cons_upper = model.cons_upper + (-25 * model.var_upper.x + 20 * model.var_lower.z <= 30);
model.cons_upper = model.cons_upper + (model.var_upper.x + 2 * model.var_lower.z <= 10);
model.cons_upper = model.cons_upper + (2 * model.var_upper.x - model.var_lower.z <= 15);
model.cons_upper = model.cons_upper + (2 * model.var_upper.x + 10 * model.var_lower.z >= 15);
% --- Upper-Level Objective ---
model.obj_upper = -model.var_upper.x - 10 * model.var_lower.z;
Lower-Level Constraints & Objective:
% --- Lower-Level Constraints ---
model.cons_lower = [];
model.cons_lower = model.cons_lower + (-25 * model.var_upper.x + 20 * model.var_lower.z <= 30 + model.var_lower.y(1,1) );
model.cons_lower = model.cons_lower + (model.var_upper.x + 2 * model.var_lower.z <= 10 + model.var_lower.y(2,1) );
model.cons_lower = model.cons_lower + (2 * model.var_upper.x - model.var_lower.z <= 15 + model.var_lower.y(3,1) );
model.cons_lower = model.cons_lower + (2 * model.var_upper.x + 10 * model.var_lower.z >= 15 - model.var_lower.y(4,1) );
model.cons_lower = model.cons_lower + (model.var_lower.z >= 0);
model.cons_lower = model.cons_lower + (model.var_lower.y >= 0);
% --- Lower-Level Objective ---
model.obj_lower = model.var_lower.z + 1e3 * sum(model.var_lower.y,'all');
Step 4: Configure and Run the Solver
Finally, we configure the solver settings using BiMIPsettings and then call the main solver function solve_BiMIP. Note that the input to solve_BiMIP is now just the model structure and the ops options.
perspective: Set to'optimistic'for this problem.method: We use the'exact_KKT'method, which is an exact algorithm.solver: We specify'gurobi'as our underlying MILP solver.verbose: A setting of2provides detailed solver output.
% Configure solver settings
ops = BiMIPsettings( ...
'perspective', 'optimistic', ...
'method', 'exact_KKT', ...
'solver', 'gurobi', ...
'verbose', 2 ...
);
% Call the solver
[Solution, BiMIP_record] = solve_BiMIP(model, ops);
3. Understanding the Results
After running the script, PowerBiMIP will return two main outputs:
Solution: A structure containing the optimal objective values.BiMIP_record: A structure that records detailed information about the solution process, such as solving time and algorithm iterations.
For this example, the optimal solution is x = 2, z = 2, resulting in an upper-level objective value of -22.
Next Steps
Congratulations! 🎉 You’ve successfully solved your first bilevel problem with PowerBiMIP.
From here, you can:
Explore other examples in the
examples/folder.Learn about Two-Stage Robust Optimization (TRO) features.
Apply PowerBiMIP to your own research problems.