Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Mathematical Programming (Julia)

Maintained by the JuliaQUBO organization
SECQUOIA  ·  PSR Energy

Open In Colab

Setup

Google Colab

Click the badge above to open this notebook in Colab. The notebook installs or activates dependencies in the setup cells below.

Local installation

Run the following from the repository root before opening this notebook locally:

julia --project=notebooks_jl -e 'using Pkg; Pkg.instantiate()'

See local-setup.md for installing Julia itself and for the full local workflow.

GLPK (required for ILP sections):

Colab Instructions

If not in a Colab notebook, continue to the next section.

  1. Work on a copy of this notebook: File > Save a copy in Drive.

  2. Make sure the runtime is set to Julia. If Colab opens a Python runtime, use Runtime > Change runtime type and select Julia.

  3. Execute the following setup cell to clone the repository when needed, activate the shared notebook project, and install dependencies. The first Colab run can take several minutes.

Notebook Cell

Activate Environment

Notebook Cell

Learning objectives

By the end of this notebook you will be able to:

  1. Formulate linear, integer, convex nonlinear, and nonconvex nonlinear programs from a word problem.

  2. Implement the same optimization model family in JuMP with continuous and integer decision variables.

  3. Select appropriate LP, MILP, NLP, and MINLP solvers and interpret their reported solutions.

  4. Explain how integrality and nonconvexity change the difficulty and reliability of optimization workflows.

Prerequisites

Mathematical background: Basic algebra, inequalities, objective functions, and familiarity with continuous and integer variables.
Prior notebooks: None; this is the first Julia notebook in the sequence.
Accounts required: None. Every solver this notebook uses is open source.
Julia version: Julia 1.10+ with the JuMP project environment used by this repository.

Introduction to Mathematical Programming

Modeling

The solution to optimization problems requires the development of a mathematical model. Here we will model an example given in the lecture and see how an integer program can be solved practically. This example will use as modeling language JuMP. This open-source Julia package provides flexible access to different solvers and a general modeling framework for linear and nonlinear integer programs. The examples solved here will make use of open-source solvers GLPK and CLP/CBC for linear and mixed-integer linear programming, IPOPT for interior point (non)linear programming, BONMIN for convex integer nonlinear programming, and COUENNE for nonconvex (global) integer nonlinear programming.

Problem statement

Suppose there is a company that produces two different products, A and B, which can be sold at different values, $5.5\$5.5 and $2.1\$2.1 per unit, respectively. The company has a single machine with electricity use capped at 17 kW/day. Producing each unit of A and B consumes 8kW/day8\text{kW}/\text{day} and 2kW/day2\text{kW}/\text{day}, respectively. Besides, the company can only produce at most 2 more units of B than A per day.

Linear Programming

This is a valid model, but it would be easier to solve if we had a mathematical representation. Assuming the units produced of A are x1x_1 and of B are x2x_2 we have

max⁡x1,x25.5x1+2.1x2s.t.x2≤x1+28x1+2x2≤17x1,x2≥0\begin{array}{rl} \displaystyle% \max_{x_1, x_2} & 5.5x_1 + 2.1x_2 \\ \textrm{s.t.} & x_2 \le x_1 + 2 \\ & 8x_1 + 2x_2 \le 17 \\ & x_1, x_2 \ge 0 \end{array}
Image produced in Jupyter
Loading...
solution_summary(; result = 1, verbose = false)
├ solver_name          : GLPK
├ Termination
│ ├ termination_status : OPTIMAL
│ ├ result_count       : 1
│ ├ raw_status         : Solution is optimal
│ └ 
objective_bound    : Inf
├ Solution (result = 1)
│ ├ primal_status        : FEASIBLE_POINT
│ ├ dual_status          : FEASIBLE_POINT
│ ├ objective_value      : 1.40800e+01
│ └ dual_objective_value : 1.40800e+01
└ Work counters
  └ solve_time (sec)   : 4.90904e-04
* x = [1.3, 3.3]

We observe that the optimal solution of this problem is x1=1.3x_1 = 1.3, x2=3.3x_2 = 3.3, leading to a profit of 14.08.

solution_summary(; result = 1, verbose = false)
├ solver_name          : COIN Branch-and-Cut (Cbc)
├ Termination
│ ├ termination_status : OPTIMAL
│ ├ result_count       : 1
│ ├ raw_status         : Cbc_status          = finished - check isProvenOptimal or isProvenInfeasible to see if solution found (or check value of best solution)
Cbc_secondaryStatus = unset (status_ will also be -1)

│ └ objective_bound    : 1.40800e+01
├ Solution (result = 1)
│ ├ primal_status        : FEASIBLE_POINT
│ ├ dual_status          : NO_SOLUTION
│ ├ objective_value      : 1.40800e+01
│ └ relative_gap         : 0.00000e+00
└ Work counters
  ├ solve_time (sec)   : 1.56808e-03
  └ node_count         : 0
* x = [1.3, 3.3]
Presolve 0 (-2) rows, 0 (-2) columns and 0 (-4) elements
Optimal - objective value 14.08
After Postsolve, objective 14.08, infeasibilities - dual 0 (0), primal 0 (0)
Optimal objective 14.08 - 0 iterations time 0.002, Presolve 0.00
Image produced in Jupyter

The solvers GLPK and CLP implement the simplex method (with many improvements) by default, but we can also use an interior point method through the solver IPOPT (interior point optimizer). IPOPT is able to solve not only linear but also nonlinear problems.


******************************************************************************
This program contains Ipopt, a library for large-scale nonlinear optimization.
 Ipopt is released as open source code under the Eclipse Public License (EPL).
         For more information visit https://github.com/coin-or/Ipopt
******************************************************************************

solution_summary(; result = 1, verbose = false)
├ solver_name          : Ipopt
├ Termination
│ ├ termination_status : LOCALLY_SOLVED
│ ├ result_count       : 1
│ └ raw_status         : Solve_Succeeded
├ Solution (result = 1)
│ ├ primal_status        : FEASIBLE_POINT
│ ├ dual_status          : FEASIBLE_POINT
│ ├ objective_value      : 1.40800e+01
│ └ dual_objective_value : 1.40800e+01
└ Work counters
  ├ solve_time (sec)   : 1.27070e-01
  └ barrier_iterations : 8
* x = [1.3000000135344931, 3.300000029213091]

We obtain the same result as previously, but notice that the interior point method reports a solution subject to a certain tolerance, given by its convergence properties when it can get infinitesimally close (but not directly at) the boundary of the feasible region.

From Linear Programming to Integer Programming

The LP above allows fractional decisions, which is useful for production rates but not for yes/no or count decisions. When the variables must be integers, the feasible region becomes a set of discrete points rather than a filled polygon. Solvers such as GLPK and CBC handle this by solving LP relaxations inside a branch-and-bound search. This usually makes the model harder, but it also lets the formulation represent decisions such as building 0, 1, or 2 units.

Integer Programming

Now let’s consider that only integer units of each product can be produced, namely

max⁡x1,x25.5x1+2.1x2s.t.x2≤x1+28x1+2x2≤17x1,x2≥0x1,x2∈Z\begin{array}{rl} \displaystyle% \max_{x_1, x_2} & 5.5x_1 + 2.1x_2 \\ \textrm{s.t.} & x_2 \leq x_1 + 2 \\ & 8x_1 + 2x_2 \leq 17 \\ & x_1, x_2 \geq 0 \\ & x_1, x_2 \in \mathbb{Z} \end{array}
Image produced in Jupyter
Loading...
solution_summary(; result = 1, verbose = false)
├ solver_name          : COIN Branch-and-Cut (Cbc)
├ Termination
│ ├ termination_status : OPTIMAL
│ ├ result_count       : 1
│ ├ raw_status         : Cbc_status          = finished - check isProvenOptimal or isProvenInfeasible to see if solution found (or check value of best solution)
Cbc_secondaryStatus = search completed with solution

│ └ objective_bound    : 1.18000e+01
├ Solution (result = 1)
│ ├ primal_status        : FEASIBLE_POINT
│ ├ dual_status          : NO_SOLUTION
│ ├ objective_value      : 1.18000e+01
│ └ relative_gap         : 0.00000e+00
└ Work counters
  ├ solve_time (sec)   : 8.11696e-03
  └ node_count         : 0solution_summary(; result = 1, verbose = false)
├ solver_name          : COIN Branch-and-Cut (Cbc)
├ Termination
│ ├ termination_status : OPTIMAL
│ ├ result_count       : 1
│ ├ raw_status         : Cbc_status          = finished - check isProvenOptimal or isProvenInfeasible to see if solution found (or check value of best solution)
Cbc_secondaryStatus = search completed with solution

│ └ objective_bound    : 1.18000e+01
├ Solution (result = 1)
│ ├ primal_status        : FEASIBLE_POINT
│ ├ dual_status          : NO_SOLUTION
│ ├ objective_value      : 1.18000e+01
│ └ relative_gap         : 0.00000e+00
└ Work counters
  ├ solve_time (sec)   : 8.11696e-03
  └ node_count         : 0
* x = [1.0, 3.0]

Here the solution becomes x1=1,x2=3x_1 = 1, x_2 = 3 with an objective of 11.8.

Image produced in Jupyter

Why enumeration stops scaling

The previous ILP is small enough that listing feasible integer points is informative. Enumeration is useful here because it exposes the shape of the discrete feasible set and checks the solver result by hand. It stops being practical quickly: adding variables or increasing bounds multiplies the number of candidates before the solver even evaluates the objective.

Enumeration

Enumeration is practical for this small problem: the plot shows only 8 feasible integer points. If both nonnegative integer variables instead had upper bounds of 4, there would be 52=255^2 = 25 candidate pairs before checking feasibility. With more variables, enumeration quickly becomes impractical. For nn binary variables (integer variables can be encoded in binary), the number of possible assignments is 2n2^n.

In many other applications, the possible solutions come from permutations of the integer variables (e.g., assignment problems), which grow as n!n! with the size of the input.

This combinatorial growth makes exhaustive enumeration impractical very quickly.

Image produced in Jupyter

From Integer Linear Programming to Integer Nonlinear Programming

Integer convex nonlinear programming

The following constraint: “the production of B minus 1, squared, can only be smaller than 2 minus the production of A” can be incorporated in the following convex integer nonlinear program,

max⁡x1,x25.5x1+2.1x2s.t.x2≤x1+28x1+2x2≤17(x2−1)2≤2−x1x1,x2≥0x1,x2∈Z\begin{array}{rl} \displaystyle% \max_{x_1, x_2} & 5.5x_1 + 2.1x_2 \\ \textrm{s.t.} & x_2 \leq x_1 + 2 \\ & 8x_1 + 2x_2 \leq 17 \\ & (x_2-1)^2 \leq 2 - x_1 \\ & x_1, x_2 \geq 0 \\ & x_1, x_2 \in \mathbb{Z} \end{array}
Image produced in Jupyter
Loading...
Bonmin 1.8.9 using Cbc 2.10.12 and Ipopt 3.14.19
bonmin: 

******************************************************************************
This program contains Ipopt, a library for large-scale nonlinear optimization.
 Ipopt is released as open source code under the Eclipse Public License (EPL).
         For more information visit https://github.com/coin-or/Ipopt
******************************************************************************

NLP0012I 
              Num      Status      Obj             It       time                 Location
NLP0014I             1         OPT -12.775        9 0
NLP0012I 
              Num      Status      Obj             It       time                 Location
NLP0014I             1      INFEAS 0.24999981       16 0.001493
NLP0014I             2         OPT -12.4125        5 0.000587
NLP0014I             3         OPT -9.7000002        6 0.000762
NLP0014I             4         OPT -9.7000001        6 0.000729
NLP0014I             5      INFEAS 0.24999981       16 0.002403
NLP0014I             6         OPT -9.7000001        6 0.000816
NLP0012I 
              Num      Status      Obj             It       time                 Location
NLP0014I             1         OPT -9.7        0 0
Cbc0004I Integer solution of -9.7 found after 6 iterations and 0 nodes (0.01 seconds)
Cbc0001I Search completed - best objective -9.699999999999999, took 6 iterations and 0 nodes (0.01 seconds)
Cbc0032I Strong branching done 2 times (33 iterations), fathomed 0 nodes and fixed 1 variables
Cbc0035I Maximum depth 0, 0 variables fixed on reduced cost

 	"Finished"
solution_summary(; result = 1, verbose = false)
├ solver_name          : AmplNLWriter
├ Termination
│ ├ termination_status : LOCALLY_SOLVED
│ ├ result_count       : 1
│ └ raw_status         : bonmin: Optimal
├ Solution (result = 1)
│ ├ primal_status        : FEASIBLE_POINT
│ ├ dual_status          : NO_SOLUTION
│ └ objective_value      : 9.70000e+00
└ Work counters
  └ solve_time (sec)   : 1.89079e-01
* x = [1.0, 2.0]

In this case the optimal solution becomes x1=1,x2=2x_1 = 1, x_2 = 2 with an objective of 9.7.

Image produced in Jupyter

Integer non-convex programming

The last constraint “the production of B minus 1 squared can only be greater than the production of A plus one half” can be incorporated in the following non-convex integer nonlinear program.

To see why, define g(x1,x2)=(x2−1)2−x1−12g(x_1, x_2) = (x_2 - 1)^2 - x_1 - \frac{1}{2}. This is a convex function, but the constraint g(x1,x2)≥0g(x_1, x_2) \geq 0 is a superlevel-set constraint. Equivalently, it removes the convex sublevel set g(x1,x2)<0g(x_1, x_2) < 0, leaving a feasible region that is generally non-convex.

max⁡x1,x25.5x1+2.1x2s.t.x2≤x1+28x1+2x2≤17(x2−1)2≤2−x1(x2−1)2≥12+x1x1,x2≥0x1,x2∈Z\begin{array}{rl} \displaystyle% \max_{x_1, x_2} & 5.5x_1 + 2.1x_2 \\ \textrm{s.t.} & x_2 \leq x_1 + 2 \\ & 8x_1 + 2x_2 \leq 17 \\ & (x_2-1)^2 \leq 2-x_1\\ & (x_2-1)^2 \geq \frac{1}{2} +x_1\\ & x_1, x_2 \geq 0 \\ & x_1, x_2 \in \mathbb{Z} \end{array}
Image produced in Jupyter
Loading...
Bonmin 1.8.9 using Cbc 2.10.12 and Ipopt 3.14.19
bonmin: 

******************************************************************************
This program contains Ipopt, a library for large-scale nonlinear optimization.
 Ipopt is released as open source code under the Eclipse Public License (EPL).
         For more information visit https://github.com/coin-or/Ipopt
******************************************************************************

bonmin: BonHeuristicDiveMIP.cpp:133: virtual int Bonmin::HeuristicDiveMIP::solution(double&, double*): Assertion `isNlpFeasible(minlp, primalTolerance)' failed.
Couenne 0.5.8 -- an Open-Source solver for Mixed Integer Nonlinear Optimization
Mailing list: couenne@list.coin-or.org
Instructions: http://www.coin-or.org/Couenne
couenne: 
ANALYSIS TEST: NLP0012I 
              Num      Status      Obj             It       time                 Location
NLP0014I             1         OPT -2.7500001        8 0.001775
Couenne: new cutoff value 0.0000000000e+00 (0.010366 seconds)
NLP0014I             2         OPT -0        0 0
Loaded instance "<TEMPORARY_PATH>/model.nl"
Constraints:            4
Variables:              2 (2 integer)
Auxiliaries:            5 (4 integer)

Coin0506I Presolve 6 (-2) rows, 3 (-4) columns and 14 (-8) elements
Clp0006I 0  Obj 0 Dual inf 7.599998 (2)
Clp0006I 4  Obj -6.95
Clp0000I Optimal - objective value -6.95
Clp0032I Optimal objective -6.95 - 4 iterations time 0.002, Presolve 0.00
Clp0000I Optimal - objective value -6.95
NLP Heuristic: Couenne: new cutoff value -4.2000000000e+00 (0.011225 seconds)
NLP0014I             3         OPT -4.2        0 0
no solution.
Clp0000I Optimal - objective value -6.95
Optimality Based BT: 0 improved bounds
Probing: 0 improved bounds
NLP Heuristic: no solution.
Cbc0013I At root node, 0 cuts changed objective from -6.95 to -4.2 in 2 passes
Cbc0014I Cut generator 0 (Couenne convexifier cuts) - 0 row cuts average 0.0 elements, 1 column cuts (1 active)
Cbc0004I Integer solution of -4.2 found after 2 iterations and 0 nodes (0.00 seconds)
Cbc0001I Search completed - best objective -4.2, took 2 iterations and 0 nodes (0.00 seconds)
Cbc0035I Maximum depth 0, 0 variables fixed on reduced cost

 	"Finished"

Linearization cuts added at root node:          8
Linearization cuts added in total:              8  (separation time: 9.7e-05s)
Total solve time:                        0.001286s (0.001285s in branch-and-bound)
Lower bound:                                 -4.2
Upper bound:                                 -4.2  (gap: 0.00%)
Branch-and-bound nodes:                         0
Performance of                           FBBT:	    5.1e-05s,        5 runs. fix:          0 shrnk:          0 ubd:        1.2 2ubd:          0 infeas:          0
Performance of                           OBBT:	   0.000101s,        1 runs. fix:          0 shrnk:          0 ubd:          0 2ubd:          0 infeas:          0
solution_summary(; result = 1, verbose = false)
├ solver_name          : AmplNLWriter
├ Termination
│ ├ termination_status : LOCALLY_SOLVED
│ ├ result_count       : 1
│ └ raw_status         : couenne: Optimal
├ Solution (result = 1)
│ ├ primal_status        : FEASIBLE_POINT
│ ├ dual_status          : NO_SOLUTION
│ └ objective_value      : 4.20000e+00
└ Work counters
  └ solve_time (sec)   : 3.05550e-02
* x = [0.0, 2.0]

In this case the optimal solution becomes x1=0,x2=2x_1 = 0, x_2 = 2 with an objective of 4.2.

Image produced in Jupyter

We are able to solve non-convex MINLP problems. However, the complexity of these problems leads to significant computational challenges that need to be tackled.

Practice checkpoints

Use these checkpoints during the workshop to test the main ideas before moving on.

Notebook Cell
minimizer = (0.0, 0.0); maximizer = (4.0, 0.0)
Notebook Cell
continuous = (2.4, 1.6); best_integer = (3, 2)
Notebook Cell
remaining_feasible_points = [(0, 0), (4, 0)]

Summary

In this notebook we:

  • Built optimization models for a production-planning example across LP, ILP, convex INLP, and nonconvex INLP forms.

  • Used JuMP to declare variables, objectives, and constraints, then called solvers suited to each model class.

  • Compared how relaxations, integer restrictions, nonlinear constraints, and nonconvexity affect solution quality and solver behavior.

Learning objectives met: You practiced formulating optimization models, implementing them in JuMP, choosing solver classes, and interpreting solver output across increasingly difficult model families.

Next steps: Proceed to Notebook 2: QUBO and Ising Models to learn how constrained binary models can be rewritten as unconstrained quadratic objectives.

Further reading:

Acknowledgments

This notebook was developed by: