1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343
|
% Document History:
%
%-----------------------------------------------------------------------
\documentclass[a4paper,oneside]{arlimsTPPM}
%-------------------------------------------------------------------------
%--- Settings for the ARLIMS document class ---
%\setcounter{page}{8} % The starting page
%--- This part of the preamble can be fiddled with according to our needs ---
% Some packages that we like and want to use:
\usepackage{cite}
\usepackage{url}
\usepackage{graphicx}
\usepackage{color}
\usepackage[T1]{fontenc}
\usepackage[scaled]{beramono}
% For typesetting listings.
\usepackage{listings}
\lstset{% Set some listing parameters.
numbers=left,
numberstyle=\sffamily\scriptsize,
numberblanklines=false,
basicstyle=\ttfamily\footnotesize,
showstringspaces=false,
backgroundcolor=\color[rgb]{0.9,0.9,0.9},
%frame=tb,
%xleftmargin=4.3ex,
language=Python
}
\usepackage{hyperref}
% The path(s) to the graphics files:
\graphicspath{{eps/}{pdf/}}
% correct bad hyphenation here
%\hyphenation{cor-res-pon-ding net-works}
% This makes line breaks look prettier
\sloppy
% Conventions for typesetting vectors and matrices.
\renewcommand{\vec}[1]{\boldsymbol{#1}}
\newcommand{\mat}[1]{\boldsymbol{#1}}
% If \boldsymbol doesn't work, try \pmb (poor man's bold, which
% "multistrikes" every letter with small offsets.
%--- Preamble definitions for actual content ---
\title{An Introduction to pulp for Python Programmers}
\author{Stuart Mitchell}
\institute{
\begin{minipage}[t]{.45\textwidth}\centering
Light Metals Research Centre\\
University of Auckland\\
Auckland, New Zealand\\
\textnormal{\texttt{\small s.mitchell@auckland.ac.nz}}
\end{minipage}}
%-------------------------------------------------------------------------
\begin{document}
% Put a proper title on the first page:
\maketitle
%-------------------------------------------------------------------------
\begin{abstract}
Pulp-or (referred to as pulp for the rest of this paper) is a linear programming framework in Python. Pulp is licensed under a modified BSD license. The aim of pulp is to allow an Operations Research (OR) practitioner or programmer to express Linear Programming (LP), and Integer Programming (IP) models in python in a way similar to the conventional mathematical notation. Pulp
will also solve these problems using a variety of free and non-free LP solvers. Pulp models an LP in a natural and pythonic manner.
This paper is aimed at the python programmer who may wish to use pulp in their code. As such this paper contains a short introduction to LP models and their uses.
% ARLIMS style
\paragraph{Keywords:} Linear programming; Operations Research; pulp-or.
\end{abstract}
%-------------------------------------------------------------------------
\section{Introduction}
Operations Research is known by the catch phrase ``The science of better''. From the website \cite{scienceofbetter} we find this brief description
\begin{quote}
In a nutshell, operations research (O.R.) is the discipline of applying advanced analytical methods to help make better decisions.
\end{quote}
The particular area of operations research where pulp is useful is the development and modelling of Linear Programming (LP) and Integer Programming (IP) problems. Mathematically, an LP problem is to find a point in a n-dimensional linearly constrained region that maximises a given linear objective function. IP is an LP where the solution must contain discrete variables which take an integer value at the solution, a common special case of an integer variable is a binary variable which must be either 0 or 1 at the solution.
In general terms, an LP can describe a problem were decisions must be made (for example, the quantity of each ingredient in a can of cat food, detailed in section \ref{sec:whiskas}). These decisions are constrained by the properties of the problem that they model (the total weight of ingredients must be 100 grams and dietary requirements must be met). The quality of a solution is determined by some sort of cost (the total dollar cost to produce the can) and you seek to minimise or maximise the cost subject to the constraints.
\section{A Brief introduction to Linear Programming}
\label{sect:LP}
In this section we will introduce the reader to the structure of an LP problem and show the syntax used to formulate these models in pulp. The following is a example LP motivated by a real-world case-study. In general, problems of this sort are called `diet' or `blending' problems. An extensive set of case studies (including those below) can be found in \cite{pulpwiki}.
\subsection{The Whiskas Cat Food Problem (whiskas.py)}
\label{sec:whiskas}
\begin{figure}[h]
\centering
\includegraphics{images/whiskas_label.jpg}
\caption{A Whiskas cat food label.}
\end{figure}
Whiskas cat food, shown above, is manufactured by Uncle Ben's. Uncle Ben's want to produce their cat food products as cheaply as possible while ensuring they meet the stated nutritional analysis requirements shown on the cans. Thus they want to vary the quantities of each ingredient used (the main ingredients being chicken, beef, mutton, rice, wheat and gel) while still meeting their nutritional standards.
\begin{figure}[h]
\centering
\includegraphics[scale=0.5]{images/whiskas_ingredients.jpg}\includegraphics[scale=0.5]{images/whiskas_nutrition.jpg}
\caption{Detail of ingredients and nutritional requirements.}
\end{figure}
\begin{figure}[h]
\centering
\includegraphics[scale=0.6]{images/whiskas_blend.jpg}
\caption{The quantity of each ingredient must be determined.}
\end{figure}
The costs of the chicken, beef, and mutton are \$0.013, \$0.008 and \$0.010 respectively, while the costs of the rice, wheat and gel are \$0.002, \$0.005 and \$0.001 respectively. (All costs are per gram.) For this exercise we will ignore the vitamin and mineral ingredients. (Any costs for these are likely to be very small anyway.)
Each ingredient contributes to the total weight of protein, fat, fibre and salt in the final product which must be 100g (very convenient). The contributions (in grams) per gram of ingredient are given in the table below.
\begin{table}
\centering
\begin{tabular}{|l|l|l|l|l|}\hline
& Protein & Fat & Fibre & Salt\\\hline
Chicken & 0.100 & 0.080 & 0.001 & 0.002\\
Beef & 0.200 & 0.100 & 0.005 & 0.005\\
Rice & 0.000 & 0.010 & 0.100 & 0.008\\
Wheat bran & 0.040 & 0.010 & 0.150 & 0.000\\\hline
\end{tabular}
\caption{Table of ingredient compositions.}
\end{table}
A Linear Program consists of three main parts. These are the variable definitions, the objective function and the constraints.
\subsubsection{Variable definition}
In this problem the variables will be defined as the quantity (in grams) of each ingredient to include in the can. These variables will be continuous and will be able to take any non-negative value. Mathematically, we define set $I$ as the set of ingredients then create an $x$ variable indexed by $I$
\begin{eqnarray*}
I &=& \{chicken, beef, mutton, rice, wheat, gel\}\\
x_i &\ge& 0 \quad \quad i \in I.
\end{eqnarray*}
\lstinputlisting[linerange={4-12}]{code/whiskas.py}
\subsubsection{Objective Function}
The objective function is to minimise the cost of production for each can.
\[
\min \sum_i c_i x_i.
\]
Where:
\begin{itemize}
\item $c_i$ is the cost per gram of ingredient $i$.
\end{itemize}
\lstinputlisting[linerange={14-17}]{code/whiskas.py}
\subsubsection{Constraints}
Constraints serve to control the total mass of the ingredients and to model the protein, fat, salt and fibre requirements.
\begin{align*}
\sum_i x_i &= 100 \; \text{\hspace{19 mm}can mass} \\
\sum_i p_i x_i &\ge 8.0 \; \text{\hspace{20 mm}protein} \\
\sum_i f_i x_i &\ge 6.0 \; \text{\hspace{20 mm}fat} \\
\sum_i b_i x_i &\le 2.0 \; \text{\hspace{20 mm}fibre} \\
\sum_i s_i x_i &\le 0.4 \; \text{\hspace{20 mm}salt}.
\end{align*}
Where:
\begin{itemize}
\item $p_i$ is the protein per gram of ingredient $i$;
\item $f_i$ is the fat per gram of ingredient $i$;
\item $b_i$ is the fibre per gram of ingredient $i$;
\item $s_i$ is the salt per gram of ingredient $i$.
\end{itemize}
\lstinputlisting[linerange={18-29}]{code/whiskas.py}
The pulp model must now be solved with some third party optimisation software.
Pulp currently supports solving with coin-or \cite{coin-or}, glpk \cite{glpk},
CPLEX \cite{cplex} and Gurobi \cite{gurobi}.
The pulp download includes compiled versions of the coin-or solver for ubuntu and windows computers.
\lstinputlisting[linerange={31-37}]{code/whiskas.py}
\section{Other Example LP models}
Modelling the diet problem is not the only application of linear programming. Other examples include:
\begin{itemize}
\item the transportation problem,
\item the set partitioning problem,
\item the assignment problem,
\item the knapsack problem.
\end{itemize}
In this section we will give short examples of the first two problems modelled in pulp together with a brief description.
\subsection{The Transportation Problem (beerdistribution.py)}
A transportation problem involves that shipment of items from a set of sources
to a set of sinks (the sources and sinks must be disjoint sets), the problem seeks
to minimise the cost of shipping. In this case-study
crates of beer must be shipped from two breweries to five bars.
\begin{figure}[h]
\centering
\includegraphics[scale = 0.3]{images/beerdistribution.png}
\caption{The brewery and bar capacities and possible routes.}
\end{figure}
The problem is created.
\lstinputlisting[linerange={37-38}]{code/beerdistribution.py}
A list of possible routes for transportation is created:
\lstinputlisting[linerange={40-41}]{code/beerdistribution.py}
The variables created determine the amount shipped on each route. The variables
are defined as \lstinline{pulp.LpInteger} therefore solutions must not
ship fractional numbers of crates.
\lstinputlisting[linerange={43-47}]{code/beerdistribution.py}
The objective is to minimise the total shipping cost of the solution.
\lstinputlisting[linerange={48-50}]{code/beerdistribution.py}
These constraints ensure that amount shipped from each brewery is less
than the supply available. The names given to the constraints will be preserved
when an `.lp' file is created.
\lstinputlisting[linerange={52-55}]{code/beerdistribution.py}
These constraints ensure that amount shipped to each bar is greater
than the demand of the bar. These could also be equality constraints, depending
on how the problem is modelled.
\lstinputlisting[linerange={57-60}]{code/beerdistribution.py}
\subsection{The Set Partitioning Problem (wedding.py)}
A set partitioning problem determines how the items in one set (S) can be partitioned into smaller
subsets. All items in S must be contained in one and only one partition. Related problems are:
\begin{itemize}
\item set packing - all items must be contained in zero or one partitions;
\item set covering - all items must be contained in at least one partition.
\end{itemize}
In this case study a wedding planner must determine guest seating
allocations for
a wedding. To model this problem the tables are modelled as the partitions and the guests invited to the wedding
are modelled as the elements of S. The wedding planner wishes to maximise the total happiness of all of the tables.
A set partitioning problem may be modelled by explicitly enumerating each
possible subset. Though this approach does become intractable for large numbers of items (without using
column generation \cite{columngeneration}) it does have the advantage that the objective function co-efficients for
the partitions can be non-linear expressions (like happiness) and still allow this problem to be solved
using Linear Programming.
First we use \lstinline{pulp.allcombinations} to generate a list of all possible table seatings.
\lstinputlisting[linerange={20-22}]{code/wedding.py}
Then we create a binary variable that will be 1 if the table will be in the solution, or zero otherwise.
\lstinputlisting[linerange={24-28}]{code/wedding.py}
We create the \lstinline{LpProblem} and then make the objective function. Note that
happiness function used in this script would be difficult to model in any other way.
\lstinputlisting[linerange={30-32}]{code/wedding.py}
We specify the total number of tables allowed in the solution.
\lstinputlisting[linerange={34-35}]{code/wedding.py}
This set of constraints defines the set partitioning problem by guaranteeing that a guest is allocated to
exactly one table.
\lstinputlisting[linerange={38-41}]{code/wedding.py}
\section{Why pulp}
Modelling problems using LP and solving them with pulp can be a very useful approach for the python programmer.
This approach allows the programmer to focus on the modelling rather than the algorithms that find the solution.
The third party LP solvers that pulp interfaces with are all mature pieces of software that can be trusted to
produce the correct solution to the model created in pulp. The difference between these solvers, for a user of pulp,
lies mainly in the trade off between cost of the solver and its speed to find a solution.
LP and IP are well researched areas of mathematics, therefore once a problem is stated as an LP model it is possible
to guarantee some properties of the solutions given. For instance, if the solver delivers an optimal solution it
will be impossible for someone to create a solution that is better (as measured by the objective function).
Compared to other LP modelling tools including AMPL \cite{ampl}, GAMS \cite{gams}, mathprog \cite{glpk}, flopc++ \cite{flopc}, and pyomo \cite{pyomo} and poams; pulp offers a number of advantages. These include its:
\begin{itemize}
\item non-restrictive licensing;
\item ease of installation;
\item clear syntax;
\item interoperability with an number of solvers;
\item extensive documentation.
\end{itemize}
\subsection{Licensing}
The licensing of pulp under a permissive open-source license allows pulp to be used as a medium for teaching
operations research as students can download and use pulp for free. The license allows OR practitioners to
integrate pulp in commercial applications for their clients without disclosing how the problem was solved.
The license also allows advanced users to modify and improve pulp for their own purposes.
\subsection{Installation}
Pulp is very easy to install. The provision of a setup.py file and registration on pipy, allows the user
to use
\begin{verbatim}
$easy_install pulp-or
\end{verbatim}
to download and install pulp on their system. For windows and ubuntu users this binary package also includes
the coin-or \cite{coin-or} solver so pulp will be immediately functional. For users on other platforms a compatible
solver must be installed for a pulp model to be solved.
\subsection{Syntax}
Ideally an LP modelling framework will allow a one-to-one translation of symbols from mathematical notation. If the syntax follows the formulation the programmer can ensure that the model written is the model required. The OR practitioner can also quickly read and understand the a model as written pulp, without having much python background. The standard mathematical notation is very concise and therefore code that mimics it will also be clear and concise.
The use of a `Pythonic' construction of the pulp framework allows the programmer to use it easily in a variety of ways. Pulp can be directly imported into the local name-space (against python style) to allow simple LP models to be written by non-programmers, in fact most of the case studies on the wiki \cite{pulpwiki} use this style. Pulp also does not force the programmer to use any particular way to store parameter data. Parameter data can be stored as lists, dictionaries or custom classes.
\subsection{Interoperability}
The ability for pulp to call a number of free and non-free solvers is important as the OR practitioner often wishes to compare the solution of a problem with a variety of solvers. The user may wish to develop simple LP models in a free solver and then provide a solution to a client using a commercial solver. The use of non-free solvers may dramatically reduce the solution time of the model. Pulp allows the free interchange of solvers without much change in the program, only a parameter for the \lstinline{LpProblem.solve} function is changed.
\subsection{Documentation}
Pulp is supplied with a user guide with extensive examples (in fact it was converted from a course teaching LP modelling). This user guide and the permissive license were intended to make pulp useful to the largest possible audience.
\section{Conclusion}
In conclusion pulp nicely bridges the gap between the OR practitioner and the python programmer. This allows the OR practitioner to use python to quickly develop and solve LP and IP models while having access to all of the tools available in the python standard library. The python programmer can now embed LP models in complex programs written in python. So next time you find yourself hacking up an algorithm to solve some problem in your code please consider if it would be appropriate to model this problem as an LP or IP instead. If so download and use pulp.
\section*{Appendix - Example Code in full}
\subsection*{whiskas.py}
\lstinputlisting{code/whiskas.py}
\subsection*{beerdistribution.py}
\lstinputlisting{code/beerdistribution.py}
\subsection*{wedding.py}
\lstinputlisting{code/wedding.py}
%-------------------------------------------------------------------------
%--- back matter: bibliography ---
% These are in alphabetical by leading author order.
\newpage
\bibliographystyle{IEEEtran}
\bibliography{references}
%-------------------------------------------------------------------------
% We need this for the style to work properly, so please leave it in here:
\label{lastpagenum}
\end{document}
|