Optimisation with Pyomo

Two previous posts considered using SciPy and CVXPY to solve a simple optimisation problem.

Pyomo is a flexible Open Source optimisation modelling language for Python. It can be used to define, solve, and analyse a wide range of optimisation problems, including Linear Programming (LP) and Mixed-Integer Programming (MIP), nonlinear programming (NLP), and differential equations.

Install Pyomo

Use pip to install the pyomo package. I suggest doing this in a virtual environment.

pip install pyomo

You can also go all in and install it with all conditional dependencies, which might save some fiddling later on.

pip install 'pyomo[optional]'

Check that you are able to import pyomo into a Python session. For repeatability you should record all installed package versions at this point using pip freeze.

Before you can do anything meaningful with the pyomo package you’ll also need to install one or more solvers.

Install a Solver

Pyomo supports a selection of Open Source and commercial optimisers. I’ll be focusing on the former.

GLPK

GLPK (GNU Linear Programming Kit) is a powerful Open Source package for solving LP and MIP optimisation problems. GLPK comprises a library (a collection of C functions that can be integrated into other projects) and glpsol, a command-line executable that can be used to solve optimisation problems specified in a few different formats.

It’s easy to install GLPK via the system package manager.

sudo apt update
sudo apt install -y glpk-utils libglpk-dev glpk-doc

🚧 Unfortunately the GLPK solver will not work for the water tank problem though because it contains non-linear terms. We need an optimiser that’s able to handle non-linearity.

The Ipopt solver is a good alternative with a wider range of capabilities. It’s slightly more challenging to install. Ipopt is a project developed by COIN-OR (Computational Infrastructure for Operations Research). To use it with Pyomo you’ll need to first install a few other dependencies.

ASL

ASL (AMPL Solver Library) is a library developed to facilitate communication between solvers and the AMPL modelling language. It acts as a bridge, allowing solvers to interpret and solve optimisation problems defined in the AMPL format.

Install ASL using the third-party builder maintained by COIN-OR.

git clone git@github.com:coin-or-tools/ThirdParty-ASL.git
cd ThirdParty-ASL

Run the get.ASL script to download the ASL source code.

bash get.ASL

Some tools will be required, specifically a C compiler.

sudo apt install -y build-essential

Then configure, build and install.

./configure
make
sudo make install

Confirm that libcoinasl.so is installed under /usr/local/lib/.

HSL

HSL (Harwell Subroutine Library) is a collection of packages for large-scale scientific computation. It was developed at the Rutherford Appleton Laboratory. HSL is available under either academic or commercial licenses. A subset of HSL, the HSL Archive, is also available free for personal use.

Go to the Coin-HSL Archive and request a license. When your request is approved (should be immediate) you’ll receive a link to a download page. Follow the link and download the archive, which will be named something like coinhsl-archive-2023.11.17.tar.gz (possibly the date will differ).

Install HSL using the third-party builder maintained by COIN-OR.

git clone git@github.com:coin-or-tools/ThirdParty-HSL.git
cd ThirdParty-HSL

Unpack the HSL archive into that directory and create a symbolic link.

tar -zxvf coinhsl-archive-2023.11.17.tar.gz
ln -s coinhsl-archive-2023.11.17/ coinhsl

You’ll need a few libraries.

sudo apt install -y libblas-dev liblapack-dev libmetis-dev

Then configure, build and install.

./configure
make
sudo make install

Check to see that libcoinhsl.so is installed under /usr/local/lib/.

Ipopt

Finally we’re ready to install Ipopt.

git clone https://github.com/coin-or/Ipopt
cd Ipopt

But first a few more dependencies.

sudo apt install -y g++ pkgconf libmumps-seq-dev

Make sure that it can find the ASL and HSL libraries installed earlier. You might want to add this to your .bashrc.

export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH

Then configure, build and test.

./configure
make
make test

Assuming that all of the tests pass, you can now install.

sudo make install

Check that you can run the ipopt executable.

ipopt -v
Ipopt 3.14.17 (x86_64-pc-linux-gnu), ASL(20241111)

CPLEX

See installing CPLEX post.

Optimisation with Pyomo

I set up the reference problem for Pyomo in a Jupter notebook. Here are the key components:

  1. Import a selection of packages.
  2. Specify the problem via a set of constants.
  3. Create a Pyomo concrete model object, along with objects for each of the problem variables. Each problem variable is assigned a type and bounds.
  4. Set up constraints on the model object.
  5. Define the objective function and link to the model object.
  6. Instantiate a solver (specifying the ipopt solver).
  7. Run the solver on the model object.

📢 In contrast to the objective functions used in the CVXPY and SciPy, here I used the L2 rather than the L1 norm. The impact on the results is apparent in smoother changes in the inflow rate.

Ipopt

I unpacked the optimised problem variables into a data frame and generated the plots below. For reference, these results were obtained with Ipopt version 3.14.17 using the MA27 solver from the HSL library.

Optimised inflow and outflow rates as determined by Pyomo using the Ipopt optimiser.

MOSEK

For comparison with the Ipopt results above, here are the corresponding results from the MOSEK solver. These can also be compared with the MOSEK results obtained via the CVXPY package.

Optimised inflow and outflow rates as determined by Pyomo using the MOSEK optimiser.

As mentioned above, the decrease in inflow rate after the first phase is smoother due to the objective function using the L2 rather than L1 norm.

Summary

Optimiser Objective Time (s) Timestamp
CPLEX 6.714 0.035 2025-01-05 07:24:10
MOSEK 6.714 0.066 2025-01-05 06:17:29
IPOPT 6.714 0.092 2025-01-05 07:57:07

Compared with the results from the CVXPY package the optimised inflow rate is similar: high initial flow rate, then decreasing to a lower flow rate after the first demand cycle. The inflow here is, however, a smooth function of time. This is objectively a better solution since it corresponds to a lower value of the objective function. It’s also more aesthetically pleasing. Whereas with CVXPY the smoothing term in the objective function seems to have had no effect on the input flow, here it has achieved its goal. The optimized outflow rate closely matches the demand and the tank level is a periodic sawtooth as before.

Performance

The optimisation itself took around 0.055 seconds. For comparison purposes I extended the problem to 10 days and dropped the sampling interval to 10 minutes, for which the optimisation took 0.272 seconds. All else being equal, comparing these times with those from CVXPY suggests that Pyomo is significantly quicker.