implemented diff from http://code.google.com/p/pulp-or/issues/detail?id=44 adding features to CPLEX_PY

This commit is contained in:
Stuart Mitchell
2013-01-07 21:34:07 +13:00
commit 803c3d37e8
114 changed files with 18693 additions and 0 deletions

2
AUTHORS Normal file
View File

@@ -0,0 +1,2 @@
Roy, J.S
Mitchell, Stuart A

77
HISTORY Normal file
View File

@@ -0,0 +1,77 @@
# PuLP, Copyright J.S. Roy (js@jeannot.org), 2002-2005
# Copyright S.A.Mitchell (s.mitchell@auckland.ac.nz), 2007-
# See the LICENSE file for copyright information.
# @(#) $Jeannot: HISTORY,v 1.8 2005/05/05 09:23:51 js Exp $
1.4.9, 2011-03-30
Added support for cplex runtime licenses
Made PULP_CBC_CMD the default LP solver for linux
Included 32 and 64 bit versions of cbc
1.4.8, 2011-03-30
Overdue fix for zero coeff issue
bugfix for default cat for LpVariable.dicts
moved tests to a different file
1.4.6, 2010-01-25
Bugfix
1.4.4, 2010-01-24
CBC 2.4 cmd line solver added
CoinMP dll 1.4.0 uses trunk version
CoinMP library object now accessed through COINMP_DLL.lib
Config files now include a %(here)s syntax to identify paths
Included solvers now moved to solvers directory
1.4.2, 2009-12-31
Fixes before coin Announcement
1.4.0, 2009-10-16
Added Elastic Constraints
Added Fractional Constraints (tests yet to be added)
Added limited resolve for gurobi
Changed version numbers for coin import
Fixed COIN_CMD
Changed code to be compatible with python 2.4
1.3.08, 2009-08-08
Bugfix COINMP_DLL
1.3.07, 2009/06/26
Changes for pypi
1.3.01, 2009/06/25
Made constraints ordered dictionaries
Small changes to parameters of COIN_DLL solver
Removed string exceptions
added the constraints from other LpProblems to LpProblem.extend()
1.3.00, 2009/06/21
Added GUROBI Solver
1.23, 2009/05/25
Removed old style MEM solvers
Cleaned up CPLEX_DLL interface
Added Sequential solve function
1.22, 2009/04/03
Added Cplex IntegerOptimalTolerence setMemoryEmphsis, and clarified linux
installation instructions
1.21.02, 2008/07/29
Added epagap, and logfile in CPLEX_DLL
1.21, 2008/07/28
Added Combination and Permutation functions
Bugfix for configsolvers
Updated setup.py to start to compile everything
1.20, 2008/06/08
Certified for inclusion in Coin-Or
Spilt the solver and constant definitions into separate files
Unit Tests make more explicit
Included external definitions for CoinMP.dll
No Makefile
1.11, 2008/03/01
Contributed by Stuart Mitchell s.mitchell@auckland.ac.nz.
Contains dll solvers that are accessed with the ctypes library.
Can use the CoinMP.dll solver from the coin-or project.
Added column-wise modelling and resolve capabilities.
Wiki added with plenty of examples aphi038@ec.auckland.ac.nz
1.9, 2007/08/06:
Stuart Mitchell
Added support for cplex 10.1.0 using ctypes library
Added support for the CoinMP.dll using ctypes library
Added distutils setup
Added a configuaration file pulp.cfg
1.1, 2005/05/03:
Fix an strange interpretation of unbounded integer variables by COIN and CPLEX
LP return codes are simplified.
C interface modules for GLPK, COIN and CPLEX
Windows compatibility
1.0, 2004/02/29: First release

127
INSTALL Normal file
View File

@@ -0,0 +1,127 @@
Installation
------------
Note that to install PuLP you must first have a working python installation as
described in `installing python`_.
PuLP requires Python >= 2.5. Though it can be made to work with Python 2.4
The latest version of PuLP can be freely obtained from coin-or_.
Please note that this version of PuLP has not been tested with operating systems
other than Microsoft Windows and Ubuntu Linux.
Easy install and pypi installation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
By far the easiest way to install pulp is through the use of EasyInstall_ and
CheeseShop_.
* Install EasyInstall
* In windows (please make sure easy_install is on your path)::
c:\Python26\Scripts\> easy_install -U pulp
* In Linux::
$ sudo easy_install -U pulp
$ sudo pulptest #needed to get the default solver to work
* Then follow the instructions below to test your installation
To access the examples and pulp source code use the instructions below
to install from source
Windows installation from source
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Install python (`installing python`_)
* Download the `PuLP zipfile`_
* Extract the zipfile to a suitable location (such as the desktop - the folder will be no longer required after installation)
* Open a command prompt by clicking "Run" in the Start Menu, and type 'cmd' in the window and push enter.
* Navigate to the extracted folder with the setup file in it. [Do this by typing 'cd foldername' at the prompt, where 'cd' stands for current directory and the 'foldername' is the name of the folder to open in the path already listed to the left of the prompt. To return back to a root drive, type 'cd C:\']
* Type 'setup.py install' at the command prompt. This will install all the PuLP functions into Python's site-packages directory.
The PuLP function library is now able to be imported from any python command line. Go to IDLE or PyDev and type
>>> from pulp import *
to load in the functions. (You need to re-import the functions each time after
you close the GUI) PuLP is written in a programming language called Python, and
to use PuLP you must write Python code to describe your optimization problem.
Linux Installation
~~~~~~~~~~~~~~~~~~
* Extract the `PuLP zipfile`_ folder to a suitable location (such as your home directory - the folder will be no longer required after installation)
* Open a command line navigate to the extracted zipfile with the setup file in it. [Do this by typing 'cd foldername' at the prompt]
* Type the following at the command prompt. This will install all the PuLP functions into Python's callable modules.
.. code-block:: sh
$ sudo python setup.py install
* install a solver for pulp to use either
* use the included 64 or 32-bit binaries cbc-32 and cbc-64
* install glpk_ debain based distributions may use the following
.. code-block:: sh
$ sudo apt-get install glpk
* install gurobi_ (free academic licenses)
* install cplex_ (and pay $$)
* or compile coinMP_ and pulp from source using buildout and copy the files
.. code-block:: sh
$ python bootstrap.py
$ bin/buildout -c solvers.cfg
$ cp parts/lib/* src/pulp/
$ sudo setup.py install
.. _glpk: http://www.gnu.org/software/glpk/
.. _CoinMP: http://projects.coin-or.org/CoinMP
.. _cplex: http://cplex.com
.. _gurobi: http://gurobi.com
Testing your PuLP installation
------------------------------
To test that that you pulp installation is working correctly please type the
following into a python interpreter and note that the output should be similar.
The output below is what you would expect if you have not installed any other
solvers and the CoinMP_ solver bundled with pulp works.
>>> import pulp
>>> pulp.pulpTestAll()
Solver pulp.pulp.COIN_MEM unavailable.
Solver pulp.pulp.COIN_CMD unavailable.
Testing continuous LP solution
Testing maximize continuous LP solution
Testing unbounded continuous LP solution
Testing MIP solution
Testing MIP relaxation
Testing feasibility problem (no objective)
Testing an infeasible problem
Testing an integer infeasible problem (Error to be fixed)
Testing column based modelling
Testing column based modelling with empty constraints
Testing dual variables and slacks reporting
Testing resolve of problem
Testing Sequential Solves
Testing fractional constraints
Testing elastic constraints (no change)
Testing elastic constraints (freebound)
Testing elastic constraints (penalty unchanged)
Testing elastic constraints (penalty unbounded)
* Solver pulp.pulp.COINMP_DLL passed.
Solver pulp.pulp.GLPK_MEM unavailable.
Solver pulp.pulp.GLPK_CMD unavailable.
Solver pulp.pulp.XPRESS unavailable.
.. _`installing python`: http://www.diveintopython.org/installing_python/index.html
.. _coin-or: https://projects.coin-or.org/PuLP
.. _EasyInstall: http://pypi.python.org/pypi/setuptools
.. _CheeseShop: http://pypi.python.org
.. _`PuLP zipfile`: http://www.coin-or.org/download/source/PuLP/

22
LICENSE Normal file
View File

@@ -0,0 +1,22 @@
Copyright (c) 2002-2005, Jean-Sebastien Roy (js@jeannot.org)
Modifications Copyright (c) 2007- Stuart Anthony Mitchell (s.mitchell@auckland.ac.nz)
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

20
MANIFEST.in Normal file
View File

@@ -0,0 +1,20 @@
include AUTHORS
include INSTALL
include LICENSE
include MANIFEST.in
include README
include VERSION
include HISTORY
include setup.py
include ez_setup.py
include bootstrap.py
include buildout.cfg
include docs.cfg
include solvers.cfg
include examples/*.py
include src/pulp/*.cfg.linux
include src/pulp/*.cfg.win
include src/pulp/*.cfg.buildout
include src/pulp/*.py
include src/pulp/solverdir/*
#include pulp-or/doc/*.pdf

70
README Normal file
View File

@@ -0,0 +1,70 @@
# Copyright J.S. Roy (js@jeannot.org), 2003-2005
# Copyright Stuart A. Mitchell (stu@stuartmitchell.com)
# See the LICENSE file for copyright information.
PuLP is an LP modeler written in python. PuLP can generate MPS or LP files
and call GLPK[1], COIN CLP/CBC[2], CPLEX[3], and GUROBI[4] to solve linear
problems.
See the examples directory for examples.
PuLP requires Python >= 2.5.
The examples require at least a solver in your PATH or a shared library file.
Documentation is found on https://www.coin-or.org/PuLP/.
A comprehensive wiki can be found at https://www.coin-or.org/PuLP/
Use LpVariable() to create new variables. To create a variable 0 <= x <= 3
>>> x = LpVariable("x", 0, 3)
To create a variable 0 <= y <= 1
>>> y = LpVariable("y", 0, 1)
Use LpProblem() to create new problems. Create "myProblem"
>>> prob = LpProblem("myProblem", LpMinimize)
Combine variables to create expressions and constraints and add them to the
problem.
>>> prob += x + y <= 2
If you add an expression (not a constraint), it will
become the objective.
>>> prob += -4*x + y
Choose a solver and solve the problem. ex:
>>> status = prob.solve(GLPK(msg = 0))
Display the status of the solution
>>> LpStatus[status]
'Optimal'
You can get the value of the variables using value(). ex:
>>> value(x)
2.0
Exported Classes:
- LpProblem -- Container class for a Linear programming problem
- LpVariable -- Variables that are added to constraints in the LP
- LpConstraint -- A constraint of the general form
a1x1+a2x2 ...anxn (<=, =, >=) b
- LpConstraintVar -- Used to construct a column of the model in column-wise
modelling
Exported Functions:
- value() -- Finds the value of a variable or expression
- lpSum() -- given a list of the form [a1*x1, a2x2, ..., anxn] will construct
a linear expression to be used as a constraint or variable
- lpDot() --given two lists of the form [a1, a2, ..., an] and
[ x1, x2, ..., xn] will construct a linear epression to be used
as a constraint or variable
Comments, bug reports, patches and suggestions are welcome.
pulp-or-discuss@googlegroups.com
References:
[1] http://www.gnu.org/software/glpk/glpk.html
[2] http://www.coin-or.org/
[3] http://www.cplex.com/
[4] http://www.gurobi.com/

34
ROADMAP Normal file
View File

@@ -0,0 +1,34 @@
Roadmap for Pulp-or
===================
Version 1.0
-----------
Original Pulp version written and conceived by Jean-Sebastien Roy js@jeannot.org
Version 1.1
-----------
Contributed by Stuart Mitchell s.mitchell@auckland.ac.nz.
Contains dll solvers that are accessed with the ctypes library.
Can use the CoinMP.dll solver from the coin-or project.
Added column-wise modelling and resolve capabilities.
Wiki added with plenty of examples aphi038@ec.auckland.ac.nz
Version 1.20
------------
Certified for inclusion in Coin-Or
Spilt the solver and constant definitions into separate files
Unit Tests make more explicit
Included external definitions for CoinMP.dll
No Makefile
Version 1.3
------------
Added GUROBI solver
Removed the older MEM solvers
Future
------
Discuss language syntax looking towards removing C alike constructs and make
better use of Python namespace conventions
Integrate branch and cut within Pulp
Integrate with other python OR projects eg Pyomo wehart@sandia.gov and POAMS leo@sie.arizona.edu

1
VERSION Normal file
View File

@@ -0,0 +1 @@
1.5.4

113
bootstrap.py Normal file
View File

@@ -0,0 +1,113 @@
##############################################################################
#
# Copyright (c) 2006 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Bootstrap a buildout-based project
Simply run this script in a directory containing a buildout.cfg.
The script accepts buildout command-line options, so you can
use the -c option to specify an alternate configuration file.
$Id$
"""
import os, shutil, sys, tempfile, urllib2
from optparse import OptionParser
tmpeggs = tempfile.mkdtemp()
is_jython = sys.platform.startswith('java')
# parsing arguments
parser = OptionParser()
parser.add_option("-v", "--version", dest="version",
help="use a specific zc.buildout version")
parser.add_option("-d", "--distribute",
action="store_true", dest="distribute", default=False,
help="Use Disribute rather than Setuptools.")
options, args = parser.parse_args()
if options.version is not None:
VERSION = '==%s' % options.version
else:
VERSION = ''
USE_DISTRIBUTE = options.distribute
args = args + ['bootstrap']
to_reload = False
try:
import pkg_resources
if not hasattr(pkg_resources, '_distribute'):
to_reload = True
raise ImportError
except ImportError:
ez = {}
if USE_DISTRIBUTE:
exec urllib2.urlopen('http://python-distribute.org/distribute_setup.py'
).read() in ez
ez['use_setuptools'](to_dir=tmpeggs, download_delay=0, no_fake=True)
else:
exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py'
).read() in ez
ez['use_setuptools'](to_dir=tmpeggs, download_delay=0)
if to_reload:
reload(pkg_resources)
else:
import pkg_resources
if sys.platform == 'win32':
def quote(c):
if ' ' in c:
return '"%s"' % c # work around spawn lamosity on windows
else:
return c
else:
def quote (c):
return c
cmd = 'from setuptools.command.easy_install import main; main()'
ws = pkg_resources.working_set
if USE_DISTRIBUTE:
requirement = 'distribute'
else:
requirement = 'setuptools'
if is_jython:
import subprocess
assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd',
quote(tmpeggs), 'zc.buildout' + VERSION],
env=dict(os.environ,
PYTHONPATH=
ws.find(pkg_resources.Requirement.parse(requirement)).location
),
).wait() == 0
else:
assert os.spawnle(
os.P_WAIT, sys.executable, quote (sys.executable),
'-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout' + VERSION,
dict(os.environ,
PYTHONPATH=
ws.find(pkg_resources.Requirement.parse(requirement)).location
),
) == 0
ws.add_entry(tmpeggs)
ws.require('zc.buildout' + VERSION)
import zc.buildout.buildout
zc.buildout.buildout.main(args)
shutil.rmtree(tmpeggs)

14
buildout.cfg Normal file
View File

@@ -0,0 +1,14 @@
[buildout]
develop = .
parts = pythonpulp scripts
[pythonpulp]
recipe = zc.recipe.egg
interpreter = pythonpulp
eggs = pulp
[scripts]
recipe = zc.recipe.egg:scripts
eggs = pulp

2369
doc/KPyCon2009/IEEEtran.bst Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -0,0 +1,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}

898
doc/KPyCon2009/arlims.cls Normal file
View File

@@ -0,0 +1,898 @@
%%
%% This is file `arlims.cls',
%% It is based on the original `article.cls' from a tetex 3.0
%% distribution (Ubuntu tetex-base 3.0-19 package).
%%
%% Additions to it were made by (probably) various authors at the
%% Massey University Institute of Information and Mathematical
%% Sciences.
%%
%% For further information contact Heath James
%% <h.a.james@massey.ac.nz>, Guy Kloss <g.kloss@massey.ac.nz>, Paul
%% Cowpertwait <p.s.cowpertwait@massey.ac.nz> or anybody you expect
%% suitable for this purpose ...
%%
%% Copyright 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004
%% The LaTeX3 Project and any individual authors listed elsewhere
%% in this file.
%%
%% This file was generated from file(s) of the LaTeX base system.
%% Numerous changes were made to be fit for RLIMS.
%% --------------------------------------------------------------
%%
%% Changes:
%%
%% * numerous changes previously to fit article.sty for the purpose
%%
%% version 1.5:
%%
%% * fixed the instructions
%% * added better default options to the template
%% * changed the year to 2008
%%
%% version 1.4:
%%
%% * fitted to a newer version of article.sty
%% * cleanups in layout and formatting
%% * better compliance to LaTeX2e
%% * re-fitted \institute, \instituteB, ...C, ...D
%% it doesn't work 100% nicely, but it works)
%% * removed dependency on natbib.sty (so cite.sty, or else can be
%% used as liked)
%% * changed paper size to a4paper by default
%% * removed dependencies to graphicx.sty and subfigure.sty
%% (users like to choose and do that themselves)
%%
%% --------------------------------------------------------------
%%
%% It may be distributed and/or modified under the
%% conditions of the LaTeX Project Public License, either version 1.3
%% of this license or (at your option) any later version.
%% The latest version of this license is in
%% http://www.latex-project.org/lppl.txt
%% and version 1.3 or later is part of all distributions of LaTeX
%% version 2003/12/01 or later.
%%
%% This file has the LPPL maintenance status "maintained".
%%
%% This file may only be distributed together with a copy of the LaTeX
%% base system. You may however distribute the LaTeX base system without
%% such generated files.
%%
%% The list of all files belonging to the LaTeX base distribution is
%% given in the file `manifest.txt'. See also `legal.txt' for additional
%% information.
%%
%% The list of derived (unpacked) files belonging to the distribution
%% and covered by LPPL is defined by the unpacking scripts (with
%% extension .ins) which are part of the distribution.
%% \CharacterTable
%% {Upper-case \A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z
%% Lower-case \a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z
%% Digits \0\1\2"s\4\5\6\7\8\9
%% Exclamation \! Double quote \" Hash (number) \#
%% Dollar \$ Percent \% Ampersand \&
%% Acute accent \' Left paren \( Right paren \)
%% Asterisk \* Plus \+ Comma \,
%% Minus \- Point \. Solidus \/
%% Colon \: Semicolon \; Less than \<
%% Equals \= Greater than \> Question mark \?
%% Commercial at \@ Left bracket \[ Backslash \\
%% Right bracket \] Circumflex \^ Underscore \_
%% Grave accent \` Left brace \{ Vertical bar \|
%% Right brace \} Tilde \~}
\NeedsTeXFormat{LaTeX2e}[1995/12/01]
\ProvidesClass{arlims}
[2008/04/23 v1.5
Massey Research Letters Article Class]
\newcommand\@ptsize{}
\newif\if@restonecol
\newif\if@titlepage
\@titlepagefalse
\if@compatibility\else
\DeclareOption{a4paper}
{\setlength\paperheight {297mm}%
\setlength\paperwidth {210mm}}
\DeclareOption{a5paper}
{\setlength\paperheight {210mm}%
\setlength\paperwidth {148mm}}
\DeclareOption{b5paper}
{\setlength\paperheight {250mm}%
\setlength\paperwidth {176mm}}
\DeclareOption{letterpaper}
{\setlength\paperheight {11in}%
\setlength\paperwidth {8.5in}}
\DeclareOption{legalpaper}
{\setlength\paperheight {14in}%
\setlength\paperwidth {8.5in}}
\DeclareOption{executivepaper}
{\setlength\paperheight {10.5in}%
\setlength\paperwidth {7.25in}}
\DeclareOption{landscape}
{\setlength\@tempdima {\paperheight}%
\setlength\paperheight {\paperwidth}%
\setlength\paperwidth {\@tempdima}}
\fi
\if@compatibility
\renewcommand\@ptsize{0}
\else
\DeclareOption{10pt}{\renewcommand\@ptsize{0}}
\fi
\DeclareOption{11pt}{\renewcommand\@ptsize{1}}
\DeclareOption{12pt}{\renewcommand\@ptsize{2}}
\if@compatibility\else
\DeclareOption{oneside}{\@twosidefalse \@mparswitchfalse}
\fi
\DeclareOption{twoside}{\@twosidetrue \@mparswitchtrue}
\DeclareOption{draft}{\setlength\overfullrule{5pt}}
\if@compatibility\else
\DeclareOption{final}{\setlength\overfullrule{0pt}}
\fi
\DeclareOption{titlepage}{\@titlepagetrue}
\if@compatibility\else
\DeclareOption{notitlepage}{\@titlepagefalse}
\fi
\if@compatibility\else
\DeclareOption{onecolumn}{\@twocolumnfalse}
\fi
\DeclareOption{twocolumn}{\@twocolumntrue}
\DeclareOption{leqno}{\input{leqno.clo}}
\DeclareOption{fleqn}{\input{fleqn.clo}}
\DeclareOption{openbib}{%
\AtEndOfPackage{%
\renewcommand\@openbib@code{%
\advance\leftmargin\bibindent
\itemindent -\bibindent
\listparindent \itemindent
\parsep \z@
}%
\renewcommand\newblock{\par}}%
}
\ExecuteOptions{a4paper,10pt,oneside,onecolumn,final}
\ProcessOptions
\input{size1\@ptsize.clo}
\setlength\lineskip{1\p@}
\setlength\normallineskip{1\p@}
\renewcommand\baselinestretch{}
\setlength\parskip{0\p@ \@plus \p@}
\@lowpenalty 51
\@medpenalty 151
\@highpenalty 301
\setcounter{topnumber}{2}
\renewcommand\topfraction{.7}
\setcounter{bottomnumber}{1}
\renewcommand\bottomfraction{.3}
\setcounter{totalnumber}{3}
\renewcommand\textfraction{.2}
\renewcommand\floatpagefraction{.5}
\setcounter{dbltopnumber}{2}
\renewcommand\dbltopfraction{.7}
\renewcommand\dblfloatpagefraction{.5}
\if@twoside
\def\ps@headings{%
\let\@oddfoot\@empty\let\@evenfoot\@empty
\def\@evenhead{\thepage\hfil\slshape\leftmark}%
\def\@oddhead{{\slshape\rightmark}\hfil\thepage}%
\let\@mkboth\markboth
\def\sectionmark##1{%
\markboth {\MakeUppercase{%
\ifnum \c@secnumdepth >\z@
\thesection\quad
\fi
##1}}{}}%
\def\subsectionmark##1{%
\markright {%
\ifnum \c@secnumdepth >\@ne
\thesubsection\quad
\fi
##1}}}
\else
\def\ps@headings{%
\let\@oddfoot\@empty
\def\@oddhead{{\slshape\rightmark}\hfil\thepage}%
\let\@mkboth\markboth
\def\sectionmark##1{%
\markright {\MakeUppercase{%
\ifnum \c@secnumdepth >\m@ne
\thesection\quad
\fi
##1}}}}
\fi
\def\ps@myheadings{%
\let\@oddfoot\@empty\let\@evenfoot\@empty
\def\@evenhead{\thepage\hfil\slshape\leftmark}%
\def\@oddhead{{\slshape\rightmark}\hfil\thepage}%
\let\@mkboth\@gobbletwo
\let\sectionmark\@gobble
\let\subsectionmark\@gobble
}
%---------- old definition of title page -------------------------------
\newif\ifGrxlb\ifGrxlb
\if@titlepage
\newcommand\maketitle{\begin{titlepage}%
\let\footnotesize\small
\let\footnoterule\relax
\let \footnote \thanks
\null\vfil
\vskip 60\p@
\begin{center}%
{\LARGE \@title \par}%
\vskip 3em%
{\large
\lineskip .75em%
\begin{tabular}[t]{c}%
\@author
\end{tabular}\par}%
\vskip 1.5em%
{\large \@date \par}% % Set date in \large size.
\end{center}\par
\@thanks
\vfil\null
\end{titlepage}%
\setcounter{footnote}{0}%
\global\let\thanks\relax
\global\let\maketitle\relax
\global\let\@thanks\@empty
\global\let\@author\@empty
\global\let\@date\@empty
\global\let\@title\@empty
\global\let\title\relax
\global\let\author\relax
\global\let\date\relax
\global\let\and\relax
}
\else
\newcommand\maketitle{\par
\begingroup
\renewcommand\thefootnote{\@fnsymbol\c@footnote}%
\def\@makefnmark{\rlap{\@textsuperscript{\normalfont\@thefnmark}}}%
\long\def\@makefntext##1{\parindent 1em\noindent
\hb@xt@1.8em{%
\hss\@textsuperscript{\normalfont\@thefnmark}}##1}%
\if@twocolumn
\ifnum \col@number=\@ne
\@maketitle
\else
\twocolumn[\@maketitle]%
\fi
\else
\newpage
\global\@topnum\z@ % Prevents figures from going at top of page.
\@maketitle
\fi
\thispagestyle{plain}\@thanks
\endgroup
\setcounter{footnote}{0}%
\global\let\thanks\relax
\global\let\maketitle\relax
\global\let\@maketitle\relax
\global\let\@thanks\@empty
\global\let\@author\@empty
\global\let\@date\@empty
\global\let\@title\@empty
\global\let\title\relax
\global\let\author\relax
\global\let\date\relax
\global\let\and\relax
}
\def\@maketitle{%
\newpage
\null
\vskip 2em%
\begin{center}%
\let \footnote \thanks
{\LARGE \@title \par}%
\vskip 1.5em%
{\large
\lineskip .5em%
\begin{tabular}[t]{c}%
\@author
\end{tabular}\par}%
\vskip 1em%
{\large \@date}%
\end{center}%
\par
\vskip 1.5em}
\fi
\fi
%---------------- end of old definition of title page ---------------------
\setcounter{secnumdepth}{3}
\newcounter {part}
\newcounter {section}
\newcounter {subsection}[section]
\newcounter {subsubsection}[subsection]
\newcounter {paragraph}[subsubsection]
\newcounter {subparagraph}[paragraph]
\renewcommand \thepart {\@Roman\c@part}
\renewcommand \thesection {\@arabic\c@section}
\renewcommand\thesubsection {\thesection.\@arabic\c@subsection}
\renewcommand\thesubsubsection{\thesubsection .\@arabic\c@subsubsection}
\renewcommand\theparagraph {\thesubsubsection.\@arabic\c@paragraph}
\renewcommand\thesubparagraph {\theparagraph.\@arabic\c@subparagraph}
\newcommand\part{%
\if@noskipsec \leavevmode \fi
\par
\addvspace{4ex}%
\@afterindentfalse
\secdef\@part\@spart}
\def\@part[#1]#2{%
\ifnum \c@secnumdepth >\m@ne
\refstepcounter{part}%
\addcontentsline{toc}{part}{\thepart\hspace{1em}#1}%
\else
\addcontentsline{toc}{part}{#1}%
\fi
{\parindent \z@ \raggedright
\interlinepenalty \@M
\normalfont
\ifnum \c@secnumdepth >\m@ne
\Large\bfseries \partname\nobreakspace\thepart
\par\nobreak
\fi
\huge \bfseries #2%
\markboth{}{}\par}%
\nobreak
\vskip 3ex
\@afterheading}
\def\@spart#1{%
{\parindent \z@ \raggedright
\interlinepenalty \@M
\normalfont
\huge \bfseries #1\par}%
\nobreak
\vskip 3ex
\@afterheading}
\newcommand\section{\@startsection {section}{1}{\z@}%
{-3.5ex \@plus -1ex \@minus -.2ex}%
{2.3ex \@plus.2ex}%
{\normalfont\Large\bfseries}}
\newcommand\subsection{\@startsection{subsection}{2}{\z@}%
{-3.25ex\@plus -1ex \@minus -.2ex}%
{1.5ex \@plus .2ex}%
{\normalfont\large\bfseries}}
\newcommand\subsubsection{\@startsection{subsubsection}{3}{\z@}%
{-3.25ex\@plus -1ex \@minus -.2ex}%
{1.5ex \@plus .2ex}%
{\normalfont\normalsize\bfseries}}
\newcommand\paragraph{\@startsection{paragraph}{4}{\z@}%
{3.25ex \@plus1ex \@minus.2ex}%
{-1em}%
{\normalfont\normalsize\bfseries}}
\newcommand\subparagraph{\@startsection{subparagraph}{5}{\parindent}%
{3.25ex \@plus1ex \@minus .2ex}%
{-1em}%
{\normalfont\normalsize\bfseries}}
\if@twocolumn
\setlength\leftmargini {2em}
\else
\setlength\leftmargini {2.5em}
\fi
\leftmargin \leftmargini
\setlength\leftmarginii {2.2em}
\setlength\leftmarginiii {1.87em}
\setlength\leftmarginiv {1.7em}
\if@twocolumn
\setlength\leftmarginv {.5em}
\setlength\leftmarginvi {.5em}
\else
\setlength\leftmarginv {1em}
\setlength\leftmarginvi {1em}
\fi
\setlength \labelsep {.5em}
\setlength \labelwidth{\leftmargini}
\addtolength\labelwidth{-\labelsep}
\@beginparpenalty -\@lowpenalty
\@endparpenalty -\@lowpenalty
\@itempenalty -\@lowpenalty
\renewcommand\theenumi{\@arabic\c@enumi}
\renewcommand\theenumii{\@alph\c@enumii}
\renewcommand\theenumiii{\@roman\c@enumiii}
\renewcommand\theenumiv{\@Alph\c@enumiv}
\newcommand\labelenumi{\theenumi.}
\newcommand\labelenumii{(\theenumii)}
\newcommand\labelenumiii{\theenumiii.}
\newcommand\labelenumiv{\theenumiv.}
\renewcommand\p@enumii{\theenumi}
\renewcommand\p@enumiii{\theenumi(\theenumii)}
\renewcommand\p@enumiv{\p@enumiii\theenumiii}
\newcommand\labelitemi{\textbullet}
\newcommand\labelitemii{\normalfont\bfseries \textendash}
\newcommand\labelitemiii{\textasteriskcentered}
\newcommand\labelitemiv{\textperiodcentered}
\newenvironment{description}
{\list{}{\labelwidth\z@ \itemindent-\leftmargin
\let\makelabel\descriptionlabel}}
{\endlist}
\newcommand*\descriptionlabel[1]{\hspace\labelsep
\normalfont\bfseries #1}
\if@titlepage
\newenvironment{abstract}{%
\titlepage
\null\vfil
\@beginparpenalty\@lowpenalty
\begin{center}%
\bfseries \abstractname
\@endparpenalty\@M
\end{center}}%
{\par\vfil\null\endtitlepage}
\else
\newenvironment{abstract}{%
\if@twocolumn
\section*{\abstractname}%
\else
\small
\begin{center}%
{\bfseries \abstractname\vspace{-.5em}\vspace{\z@}}%
\end{center}%
\quotation
\fi}
{\if@twocolumn\else\endquotation\fi}
\fi
\newenvironment{verse}
{\let\\\@centercr
\list{}{\itemsep \z@
\itemindent -1.5em%
\listparindent\itemindent
\rightmargin \leftmargin
\advance\leftmargin 1.5em}%
\item\relax}
{\endlist}
\newenvironment{quotation}
{\list{}{\listparindent 1.5em%
\itemindent \listparindent
\rightmargin \leftmargin
\parsep \z@ \@plus\p@}%
\item\relax}
{\endlist}
\newenvironment{quote}
{\list{}{\rightmargin\leftmargin}%
\item\relax}
{\endlist}
\if@compatibility
\newenvironment{titlepage}
{%
\if@twocolumn
\@restonecoltrue\onecolumn
\else
\@restonecolfalse\newpage
\fi
\thispagestyle{empty}%
\setcounter{page}\z@
}%
{\if@restonecol\twocolumn \else \newpage \fi
}
\else
\newenvironment{titlepage}
{%
\if@twocolumn
\@restonecoltrue\onecolumn
\else
\@restonecolfalse\newpage
\fi
\thispagestyle{empty}%
\setcounter{page}\@ne
}%
{\if@restonecol\twocolumn \else \newpage \fi
\if@twoside\else
\setcounter{page}\@ne
\fi
}
\fi
\newcommand\appendix{\par
\setcounter{section}{0}%
\setcounter{subsection}{0}%
\gdef\thesection{\@Alph\c@section}}
\setlength\arraycolsep{5\p@}
\setlength\tabcolsep{6\p@}
\setlength\arrayrulewidth{.4\p@}
\setlength\doublerulesep{2\p@}
\setlength\tabbingsep{\labelsep}
\skip\@mpfootins = \skip\footins
\setlength\fboxsep{3\p@}
\setlength\fboxrule{.4\p@}
\renewcommand \theequation {\@arabic\c@equation}
\newcounter{figure}
\renewcommand \thefigure {\@arabic\c@figure}
\def\fps@figure{tbp}
\def\ftype@figure{1}
\def\ext@figure{lof}
\def\fnum@figure{\figurename\nobreakspace\thefigure}
\newenvironment{figure}
{\@float{figure}}
{\end@float}
\newenvironment{figure*}
{\@dblfloat{figure}}
{\end@dblfloat}
\newcounter{table}
\renewcommand\thetable{\@arabic\c@table}
\def\fps@table{tbp}
\def\ftype@table{2}
\def\ext@table{lot}
\def\fnum@table{\tablename\nobreakspace\thetable}
\newenvironment{table}
{\@float{table}}
{\end@float}
\newenvironment{table*}
{\@dblfloat{table}}
{\end@dblfloat}
\newlength\abovecaptionskip
\newlength\belowcaptionskip
\setlength\abovecaptionskip{10\p@}
\setlength\belowcaptionskip{0\p@}
\long\def\@makecaption#1#2{%
\vskip\abovecaptionskip
\sbox\@tempboxa{#1: #2}%
\ifdim \wd\@tempboxa >\hsize
#1: #2\par
\else
\global \@minipagefalse
\hb@xt@\hsize{\hfil\box\@tempboxa\hfil}%
\fi
\vskip\belowcaptionskip}
\DeclareOldFontCommand{\rm}{\normalfont\rmfamily}{\mathrm}
\DeclareOldFontCommand{\sf}{\normalfont\sffamily}{\mathsf}
\DeclareOldFontCommand{\tt}{\normalfont\ttfamily}{\mathtt}
\DeclareOldFontCommand{\bf}{\normalfont\bfseries}{\mathbf}
\DeclareOldFontCommand{\it}{\normalfont\itshape}{\mathit}
\DeclareOldFontCommand{\sl}{\normalfont\slshape}{\@nomath\sl}
\DeclareOldFontCommand{\sc}{\normalfont\scshape}{\@nomath\sc}
\DeclareRobustCommand*\cal{\@fontswitch\relax\mathcal}
\DeclareRobustCommand*\mit{\@fontswitch\relax\mathnormal}
\newcommand\@pnumwidth{1.55em}
\newcommand\@tocrmarg{2.55em}
\newcommand\@dotsep{4.5}
\setcounter{tocdepth}{3}
\newcommand\tableofcontents{%
\section*{\contentsname
\@mkboth{%
\MakeUppercase\contentsname}{\MakeUppercase\contentsname}}%
\@starttoc{toc}%
}
\newcommand*\l@part[2]{%
\ifnum \c@tocdepth >-2\relax
\addpenalty\@secpenalty
\addvspace{2.25em \@plus\p@}%
\setlength\@tempdima{3em}%
\begingroup
\parindent \z@ \rightskip \@pnumwidth
\parfillskip -\@pnumwidth
{\leavevmode
\large \bfseries #1\hfil \hb@xt@\@pnumwidth{\hss #2}}\par
\nobreak
\if@compatibility
\global\@nobreaktrue
\everypar{\global\@nobreakfalse\everypar{}}%
\fi
\endgroup
\fi}
\newcommand*\l@section[2]{%
\ifnum \c@tocdepth >\z@
\addpenalty\@secpenalty
\addvspace{1.0em \@plus\p@}%
\setlength\@tempdima{1.5em}%
\begingroup
\parindent \z@ \rightskip \@pnumwidth
\parfillskip -\@pnumwidth
\leavevmode \bfseries
\advance\leftskip\@tempdima
\hskip -\leftskip
#1\nobreak\hfil \nobreak\hb@xt@\@pnumwidth{\hss #2}\par
\endgroup
\fi}
\newcommand*\l@subsection{\@dottedtocline{2}{1.5em}{2.3em}}
\newcommand*\l@subsubsection{\@dottedtocline{3}{3.8em}{3.2em}}
\newcommand*\l@paragraph{\@dottedtocline{4}{7.0em}{4.1em}}
\newcommand*\l@subparagraph{\@dottedtocline{5}{10em}{5em}}
\newcommand\listoffigures{%
\section*{\listfigurename}%
\@mkboth{\MakeUppercase\listfigurename}%
{\MakeUppercase\listfigurename}%
\@starttoc{lof}%
}
\newcommand*\l@figure{\@dottedtocline{1}{1.5em}{2.3em}}
\newcommand\listoftables{%
\section*{\listtablename}%
\@mkboth{%
\MakeUppercase\listtablename}%
{\MakeUppercase\listtablename}%
\@starttoc{lot}%
}
\let\l@table\l@figure
\newdimen\bibindent
\setlength\bibindent{1.5em}
\newenvironment{thebibliography}[1]
{\section*{\refname}%
\@mkboth{\MakeUppercase\refname}{\MakeUppercase\refname}%
\list{\@biblabel{\@arabic\c@enumiv}}%
{\settowidth\labelwidth{\@biblabel{#1}}%
\leftmargin\labelwidth
\advance\leftmargin\labelsep
\@openbib@code
\usecounter{enumiv}%
\let\p@enumiv\@empty
\renewcommand\theenumiv{\@arabic\c@enumiv}}%
\sloppy
\clubpenalty4000
\@clubpenalty \clubpenalty
\widowpenalty4000%
\sfcode`\.\@m}
{\def\@noitemerr
{\@latex@warning{Empty `thebibliography' environment}}%
\endlist}
\newcommand\newblock{\hskip .11em\@plus.33em\@minus.07em}
\let\@openbib@code\@empty
\newenvironment{theindex}
{\if@twocolumn
\@restonecolfalse
\else
\@restonecoltrue
\fi
\twocolumn[\section*{\indexname}]%
\@mkboth{\MakeUppercase\indexname}%
{\MakeUppercase\indexname}%
\thispagestyle{plain}\parindent\z@
\parskip\z@ \@plus .3\p@\relax
\columnseprule \z@
\columnsep 35\p@
\let\item\@idxitem}
{\if@restonecol\onecolumn\else\clearpage\fi}
\newcommand\@idxitem{\par\hangindent 40\p@}
\newcommand\subitem{\@idxitem \hspace*{20\p@}}
\newcommand\subsubitem{\@idxitem \hspace*{30\p@}}
\newcommand\indexspace{\par \vskip 10\p@ \@plus5\p@ \@minus3\p@\relax}
\renewcommand\footnoterule{%
\kern-3\p@
\hrule\@width.4\columnwidth
\kern2.6\p@}
\newcommand\@makefntext[1]{%
\parindent 1em%
\noindent
\hb@xt@1.8em{\hss\@makefnmark}#1}
\newcommand\contentsname{Contents}
\newcommand\listfigurename{List of Figures}
\newcommand\listtablename{List of Tables}
\newcommand\refname{References}
\newcommand\indexname{Index}
\newcommand\figurename{Figure}
\newcommand\tablename{Table}
\newcommand\partname{Part}
\newcommand\appendixname{Appendix}
\newcommand\abstractname{}
\def\today{\ifcase\month\or
January\or February\or March\or April\or May\or June\or
July\or August\or September\or October\or November\or December\fi
\space\number\day, \number\year}
\setlength\columnsep{10\p@}
\setlength\columnseprule{0\p@}
\pagestyle{plain}
\pagenumbering{arabic}
% === ARLIMS adaptions following ============================================
\pagestyle{empty}
\setlength{\oddsidemargin}{1.5 cm}
\setlength{\evensidemargin}{-0.5 cm}
\setlength{\textheight}{22 cm}
\setlength{\textwidth}{15 cm}
\setlength{\topmargin}{0 cm}
% --- special indexing to cope with the list of articles
\newif\ifIndexMade
\def\makeartIndex{%
\protected@write1{}{\string\global\string\IndexMadetrue}
\newwrite\@artIndexfile
\immediate\openout\@artIndexfile=\jobname.adx
\def\artIndex{\@bsphack\begingroup\@sanitize\@wrartIndex}
\typeout{Writing artIndex file \jobname.adx}%
\let\makeartIndex\@empty
}
\def\@wrartIndex#1{%
\protected@write\@artIndexfile{}{%
\string\artIndexentry{\factTitle}%
{\thepage}%
{\string\last#1}%
{\factAuthor\factAuthorB\factAuthorC\factAuthorD}}%
\endgroup%
\@esphack%
}
\def\artIndex{\@bsphack\begingroup \@sanitize\@artIndex}
\def\@artIndex#1{\endgroup\@esphack}
\def\artIndexLast#1{%
\protected@write1{}{\string\gdef\string\last#1{\thepage}}%
}
% --- to set up the headers permanently
\newcommand{\markperm}[2]{%
\global\def\@evenhead{\arabic{page}\hfil{#1}}%
\global\def\@oddhead{{#2}\hfil\arabic{page}}
}
% (yes, I know I've hardwired arabic page numbering)
% --- to set up the headers with a once off first
\newcommand{\markonce}[3]{%
\global\def\@evenhead{\arabic{page}\hfil{#1}\markperm{#2}{#3}}%
\global\def\@oddhead{{#1}\hfil\arabic{page}\markperm{#2}{#3}}%
}
%% Put a line after the abstract %%
\newcommand{\drawline}{%
\vspace*{1mm}
\begin{center}
\rule{\textwidth}{0.2mm}
\end{center}
\vspace*{1mm}
}
\newcommand{\reff}[1]{(\ref{#1})}
% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
% Define the author and title commands. Bascially each just stuffs
% the argument into a storage command. I need to define the blank
% storage commands because I am using the "renewcomand", since I
% need to do this for each article.
\def\author{}
\def\institute{}
\def\factTitle{}
\def\factAuthor{}
\def\factAuthorB{}
\def\factAuthorC{}
\def\factAuthorD{}
\def\factInstitute{}
\def\factInstituteB{}
\def\factInstituteC{}
\def\factInstituteD{}
\def\footInst{}
\def\footInstB{}
\def\footInstC{}
\def\footInstD{}
\def\footAuthorInst{}
\def\footAuthorInstB{}
\def\footAuthorInstC{}
\def\footAuthorInstD{}
\renewcommand{\author}[2][]{
\renewcommand{\footAuthorInst}{#1}
\renewcommand{\factAuthor}{#2}
}
\newcommand{\authorB}[2][]{
\renewcommand{\footAuthorInstB}{#1}
\renewcommand{\factAuthorB}{#2}
}
\newcommand{\authorC}[2][]{
\renewcommand{\footAuthorInstC}{#1}
\renewcommand{\factAuthorC}{#2}
}
\newcommand{\authorD}[2][]{
\renewcommand{\footAuthorInstD}{#1}
\renewcommand{\factAuthorD}{#2}
}
\renewcommand{\institute}[2][]{
\renewcommand{\footInst}{#1}
\renewcommand{\factInstitute}{#2}
}
\newcommand{\instituteB}[2][*]{
\renewcommand{\footInstB}{#1}
\renewcommand{\factInstituteB}{#2}
}
\newcommand{\instituteC}[2][**]{
\renewcommand{\footInstC}{#1}
\renewcommand{\factInstituteC}{#2}
}
\newcommand{\instituteD}[2][***]{
\renewcommand{\footInstD}{#1}
\renewcommand{\factInstituteD}{#2}
}
\def\nameTag{}
\renewcommand{\title}[1]{
\renewcommand{\factTitle}{#1}
}
\newcommand{\iimsaddress}{
\begin{center}
\textit{Institute of Information \& Mathematical Sciences\\
Massey University at Albany, Auckland, New Zealand.}
\end{center}
}
\newcommand{\maketitle}[1][\factTitle]{
\begin{center}
\markonce{
\parbox{10cm}{\small
\textit{Res.\ Lett.\ Inf.\ Math.\ Sci.}, 2009, Vol.~\textbf{13}, pp.~\arabic{page}--\pageref{lastpagenum} \\[1mm]
Available online at http://iims.massey.ac.nz/research/letters/
}
}
{\factAuthor\factAuthorB\factAuthorC\factAuthorD}
{#1}
\vspace*{1cm}
\begin{minipage}{12cm}
\begin{center}
{\bfseries\Large\factTitle\par}
\vspace*{2mm}
\end{center}
\end{minipage}
\vspace*{3mm}
\textsc{%
\factAuthor $^{\footAuthorInst}$%
\factAuthorB $^{\footAuthorInstB}$%
\factAuthorC $^{\footAuthorInstC}$%
\factAuthorD $^{\footAuthorInstD}$%
}
\vspace*{1mm}
$^{\footInst}$\textit{\factInstitute}
$^{\footInstB}$\textit{\factInstituteB}
$^{\footInstC}$\textit{\factInstituteC}
$^{\footInstD}$\textit{\factInstituteD}
\artIndex{\nameTag}
\end{center}
}
% calculate the margins so that the text on opposite sides
% of the same sheet is lined up at the edges
% ok, its a fudge, but you can change the oddsidemargin
% to shift both sides in sync.
\oddsidemargin1cm
\evensidemargin\paperwidth
\addtolength{\evensidemargin}{-\oddsidemargin}
\addtolength{\evensidemargin}{-205mm}
% --- turn off a few things -------------------------------------------------
\renewcommand{\markboth}[2]{}
\renewcommand{\markright}[1]{}
\newcommand{\markheading}[1]{}
\usepackage{amsmath,amsfonts,dsfont}
\usepackage{amsthm}
\newif\ifCombinedRlims
% === end of ARLIMS adaptions ===============================================
\if@twoside
\else
\raggedbottom
\fi
\if@twocolumn
\twocolumn
\sloppy
\flushbottom
\else
\onecolumn
\fi
\endinput
%%
%% End of file `arlims.cls'.

View File

@@ -0,0 +1,898 @@
%%
%% This is file `arlims.cls',
%% It is based on the original `article.cls' from a tetex 3.0
%% distribution (Ubuntu tetex-base 3.0-19 package).
%%
%% Additions to it were made by (probably) various authors at the
%% Massey University Institute of Information and Mathematical
%% Sciences.
%%
%% For further information contact Heath James
%% <h.a.james@massey.ac.nz>, Guy Kloss <g.kloss@massey.ac.nz>, Paul
%% Cowpertwait <p.s.cowpertwait@massey.ac.nz> or anybody you expect
%% suitable for this purpose ...
%%
%% Copyright 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004
%% The LaTeX3 Project and any individual authors listed elsewhere
%% in this file.
%%
%% This file was generated from file(s) of the LaTeX base system.
%% Numerous changes were made to be fit for RLIMS.
%% --------------------------------------------------------------
%%
%% Changes:
%%
%% * numerous changes previously to fit article.sty for the purpose
%%
%% version 1.5:
%%
%% * fixed the instructions
%% * added better default options to the template
%% * changed the year to 2008
%%
%% version 1.4:
%%
%% * fitted to a newer version of article.sty
%% * cleanups in layout and formatting
%% * better compliance to LaTeX2e
%% * re-fitted \institute, \instituteB, ...C, ...D
%% it doesn't work 100% nicely, but it works)
%% * removed dependency on natbib.sty (so cite.sty, or else can be
%% used as liked)
%% * changed paper size to a4paper by default
%% * removed dependencies to graphicx.sty and subfigure.sty
%% (users like to choose and do that themselves)
%%
%% --------------------------------------------------------------
%%
%% It may be distributed and/or modified under the
%% conditions of the LaTeX Project Public License, either version 1.3
%% of this license or (at your option) any later version.
%% The latest version of this license is in
%% http://www.latex-project.org/lppl.txt
%% and version 1.3 or later is part of all distributions of LaTeX
%% version 2003/12/01 or later.
%%
%% This file has the LPPL maintenance status "maintained".
%%
%% This file may only be distributed together with a copy of the LaTeX
%% base system. You may however distribute the LaTeX base system without
%% such generated files.
%%
%% The list of all files belonging to the LaTeX base distribution is
%% given in the file `manifest.txt'. See also `legal.txt' for additional
%% information.
%%
%% The list of derived (unpacked) files belonging to the distribution
%% and covered by LPPL is defined by the unpacking scripts (with
%% extension .ins) which are part of the distribution.
%% \CharacterTable
%% {Upper-case \A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z
%% Lower-case \a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z
%% Digits \0\1\2"s\4\5\6\7\8\9
%% Exclamation \! Double quote \" Hash (number) \#
%% Dollar \$ Percent \% Ampersand \&
%% Acute accent \' Left paren \( Right paren \)
%% Asterisk \* Plus \+ Comma \,
%% Minus \- Point \. Solidus \/
%% Colon \: Semicolon \; Less than \<
%% Equals \= Greater than \> Question mark \?
%% Commercial at \@ Left bracket \[ Backslash \\
%% Right bracket \] Circumflex \^ Underscore \_
%% Grave accent \` Left brace \{ Vertical bar \|
%% Right brace \} Tilde \~}
\NeedsTeXFormat{LaTeX2e}[1995/12/01]
\ProvidesClass{arlims}
[2008/04/23 v1.5
Massey Research Letters Article Class]
\newcommand\@ptsize{}
\newif\if@restonecol
\newif\if@titlepage
\@titlepagefalse
\if@compatibility\else
\DeclareOption{a4paper}
{\setlength\paperheight {297mm}%
\setlength\paperwidth {210mm}}
\DeclareOption{a5paper}
{\setlength\paperheight {210mm}%
\setlength\paperwidth {148mm}}
\DeclareOption{b5paper}
{\setlength\paperheight {250mm}%
\setlength\paperwidth {176mm}}
\DeclareOption{letterpaper}
{\setlength\paperheight {11in}%
\setlength\paperwidth {8.5in}}
\DeclareOption{legalpaper}
{\setlength\paperheight {14in}%
\setlength\paperwidth {8.5in}}
\DeclareOption{executivepaper}
{\setlength\paperheight {10.5in}%
\setlength\paperwidth {7.25in}}
\DeclareOption{landscape}
{\setlength\@tempdima {\paperheight}%
\setlength\paperheight {\paperwidth}%
\setlength\paperwidth {\@tempdima}}
\fi
\if@compatibility
\renewcommand\@ptsize{0}
\else
\DeclareOption{10pt}{\renewcommand\@ptsize{0}}
\fi
\DeclareOption{11pt}{\renewcommand\@ptsize{1}}
\DeclareOption{12pt}{\renewcommand\@ptsize{2}}
\if@compatibility\else
\DeclareOption{oneside}{\@twosidefalse \@mparswitchfalse}
\fi
\DeclareOption{twoside}{\@twosidetrue \@mparswitchtrue}
\DeclareOption{draft}{\setlength\overfullrule{5pt}}
\if@compatibility\else
\DeclareOption{final}{\setlength\overfullrule{0pt}}
\fi
\DeclareOption{titlepage}{\@titlepagetrue}
\if@compatibility\else
\DeclareOption{notitlepage}{\@titlepagefalse}
\fi
\if@compatibility\else
\DeclareOption{onecolumn}{\@twocolumnfalse}
\fi
\DeclareOption{twocolumn}{\@twocolumntrue}
\DeclareOption{leqno}{\input{leqno.clo}}
\DeclareOption{fleqn}{\input{fleqn.clo}}
\DeclareOption{openbib}{%
\AtEndOfPackage{%
\renewcommand\@openbib@code{%
\advance\leftmargin\bibindent
\itemindent -\bibindent
\listparindent \itemindent
\parsep \z@
}%
\renewcommand\newblock{\par}}%
}
\ExecuteOptions{a4paper,12pt,oneside,onecolumn,final}
\ProcessOptions
\input{size1\@ptsize.clo}
\setlength\lineskip{1\p@}
\setlength\normallineskip{1\p@}
\renewcommand\baselinestretch{}
\setlength\parskip{0\p@ \@plus \p@}
\@lowpenalty 51
\@medpenalty 151
\@highpenalty 301
\setcounter{topnumber}{2}
\renewcommand\topfraction{.7}
\setcounter{bottomnumber}{1}
\renewcommand\bottomfraction{.3}
\setcounter{totalnumber}{3}
\renewcommand\textfraction{.2}
\renewcommand\floatpagefraction{.5}
\setcounter{dbltopnumber}{2}
\renewcommand\dbltopfraction{.7}
\renewcommand\dblfloatpagefraction{.5}
\if@twoside
\def\ps@headings{%
\let\@oddfoot\@empty\let\@evenfoot\@empty
\def\@evenhead{\thepage\hfil\slshape\leftmark}%
\def\@oddhead{{\slshape\rightmark}\hfil\thepage}%
\let\@mkboth\markboth
\def\sectionmark##1{%
\markboth {\MakeUppercase{%
\ifnum \c@secnumdepth >\z@
\thesection\quad
\fi
##1}}{}}%
\def\subsectionmark##1{%
\markright {%
\ifnum \c@secnumdepth >\@ne
\thesubsection\quad
\fi
##1}}}
\else
\def\ps@headings{%
\let\@oddfoot\@empty
\def\@oddhead{{\slshape\rightmark}\hfil\thepage}%
\let\@mkboth\markboth
\def\sectionmark##1{%
\markright {\MakeUppercase{%
\ifnum \c@secnumdepth >\m@ne
\thesection\quad
\fi
##1}}}}
\fi
\def\ps@myheadings{%
\let\@oddfoot\@empty\let\@evenfoot\@empty
\def\@evenhead{\thepage\hfil\slshape\leftmark}%
\def\@oddhead{{\slshape\rightmark}\hfil\thepage}%
\let\@mkboth\@gobbletwo
\let\sectionmark\@gobble
\let\subsectionmark\@gobble
}
%---------- old definition of title page -------------------------------
\newif\ifGrxlb\ifGrxlb
\if@titlepage
\newcommand\maketitle{\begin{titlepage}%
\let\footnotesize\small
\let\footnoterule\relax
\let \footnote \thanks
\null\vfil
\vskip 60\p@
\begin{center}%
{\LARGE \@title \par}%
\vskip 3em%
{\large
\lineskip .75em%
\begin{tabular}[t]{c}%
\@author
\end{tabular}\par}%
\vskip 1.5em%
{\large \@date \par}% % Set date in \large size.
\end{center}\par
\@thanks
\vfil\null
\end{titlepage}%
\setcounter{footnote}{0}%
\global\let\thanks\relax
\global\let\maketitle\relax
\global\let\@thanks\@empty
\global\let\@author\@empty
\global\let\@date\@empty
\global\let\@title\@empty
\global\let\title\relax
\global\let\author\relax
\global\let\date\relax
\global\let\and\relax
}
\else
\newcommand\maketitle{\par
\begingroup
\renewcommand\thefootnote{\@fnsymbol\c@footnote}%
\def\@makefnmark{\rlap{\@textsuperscript{\normalfont\@thefnmark}}}%
\long\def\@makefntext##1{\parindent 1em\noindent
\hb@xt@1.8em{%
\hss\@textsuperscript{\normalfont\@thefnmark}}##1}%
\if@twocolumn
\ifnum \col@number=\@ne
\@maketitle
\else
\twocolumn[\@maketitle]%
\fi
\else
\newpage
\global\@topnum\z@ % Prevents figures from going at top of page.
\@maketitle
\fi
\thispagestyle{plain}\@thanks
\endgroup
\setcounter{footnote}{0}%
\global\let\thanks\relax
\global\let\maketitle\relax
\global\let\@maketitle\relax
\global\let\@thanks\@empty
\global\let\@author\@empty
\global\let\@date\@empty
\global\let\@title\@empty
\global\let\title\relax
\global\let\author\relax
\global\let\date\relax
\global\let\and\relax
}
\def\@maketitle{%
\newpage
\null
\vskip 2em%
\begin{center}%
\let \footnote \thanks
{\LARGE \@title \par}%
\vskip 1.5em%
{\large
\lineskip .5em%
\begin{tabular}[t]{c}%
\@author
\end{tabular}\par}%
\vskip 1em%
{\large \@date}%
\end{center}%
\par
\vskip 1.5em}
\fi
\fi
%---------------- end of old definition of title page ---------------------
\setcounter{secnumdepth}{3}
\newcounter {part}
\newcounter {section}
\newcounter {subsection}[section]
\newcounter {subsubsection}[subsection]
\newcounter {paragraph}[subsubsection]
\newcounter {subparagraph}[paragraph]
\renewcommand \thepart {\@Roman\c@part}
\renewcommand \thesection {\@arabic\c@section}
\renewcommand\thesubsection {\thesection.\@arabic\c@subsection}
\renewcommand\thesubsubsection{\thesubsection .\@arabic\c@subsubsection}
\renewcommand\theparagraph {\thesubsubsection.\@arabic\c@paragraph}
\renewcommand\thesubparagraph {\theparagraph.\@arabic\c@subparagraph}
\newcommand\part{%
\if@noskipsec \leavevmode \fi
\par
\addvspace{4ex}%
\@afterindentfalse
\secdef\@part\@spart}
\def\@part[#1]#2{%
\ifnum \c@secnumdepth >\m@ne
\refstepcounter{part}%
\addcontentsline{toc}{part}{\thepart\hspace{1em}#1}%
\else
\addcontentsline{toc}{part}{#1}%
\fi
{\parindent \z@ \raggedright
\interlinepenalty \@M
\normalfont
\ifnum \c@secnumdepth >\m@ne
\Large\bfseries \partname\nobreakspace\thepart
\par\nobreak
\fi
\huge \bfseries #2%
\markboth{}{}\par}%
\nobreak
\vskip 3ex
\@afterheading}
\def\@spart#1{%
{\parindent \z@ \raggedright
\interlinepenalty \@M
\normalfont
\huge \bfseries #1\par}%
\nobreak
\vskip 3ex
\@afterheading}
\newcommand\section{\@startsection {section}{1}{\z@}%
{-3.5ex \@plus -1ex \@minus -.2ex}%
{2.3ex \@plus.2ex}%
{\normalfont\Large\bfseries}}
\newcommand\subsection{\@startsection{subsection}{2}{\z@}%
{-3.25ex\@plus -1ex \@minus -.2ex}%
{1.5ex \@plus .2ex}%
{\normalfont\large\bfseries}}
\newcommand\subsubsection{\@startsection{subsubsection}{3}{\z@}%
{-3.25ex\@plus -1ex \@minus -.2ex}%
{1.5ex \@plus .2ex}%
{\normalfont\normalsize\bfseries}}
\newcommand\paragraph{\@startsection{paragraph}{4}{\z@}%
{3.25ex \@plus1ex \@minus.2ex}%
{-1em}%
{\normalfont\normalsize\bfseries}}
\newcommand\subparagraph{\@startsection{subparagraph}{5}{\parindent}%
{3.25ex \@plus1ex \@minus .2ex}%
{-1em}%
{\normalfont\normalsize\bfseries}}
\if@twocolumn
\setlength\leftmargini {2em}
\else
\setlength\leftmargini {2.5em}
\fi
\leftmargin \leftmargini
\setlength\leftmarginii {2.2em}
\setlength\leftmarginiii {1.87em}
\setlength\leftmarginiv {1.7em}
\if@twocolumn
\setlength\leftmarginv {.5em}
\setlength\leftmarginvi {.5em}
\else
\setlength\leftmarginv {1em}
\setlength\leftmarginvi {1em}
\fi
\setlength \labelsep {.5em}
\setlength \labelwidth{\leftmargini}
\addtolength\labelwidth{-\labelsep}
\@beginparpenalty -\@lowpenalty
\@endparpenalty -\@lowpenalty
\@itempenalty -\@lowpenalty
\renewcommand\theenumi{\@arabic\c@enumi}
\renewcommand\theenumii{\@alph\c@enumii}
\renewcommand\theenumiii{\@roman\c@enumiii}
\renewcommand\theenumiv{\@Alph\c@enumiv}
\newcommand\labelenumi{\theenumi.}
\newcommand\labelenumii{(\theenumii)}
\newcommand\labelenumiii{\theenumiii.}
\newcommand\labelenumiv{\theenumiv.}
\renewcommand\p@enumii{\theenumi}
\renewcommand\p@enumiii{\theenumi(\theenumii)}
\renewcommand\p@enumiv{\p@enumiii\theenumiii}
\newcommand\labelitemi{\textbullet}
\newcommand\labelitemii{\normalfont\bfseries \textendash}
\newcommand\labelitemiii{\textasteriskcentered}
\newcommand\labelitemiv{\textperiodcentered}
\newenvironment{description}
{\list{}{\labelwidth\z@ \itemindent-\leftmargin
\let\makelabel\descriptionlabel}}
{\endlist}
\newcommand*\descriptionlabel[1]{\hspace\labelsep
\normalfont\bfseries #1}
\if@titlepage
\newenvironment{abstract}{%
\titlepage
\null\vfil
\@beginparpenalty\@lowpenalty
\begin{center}%
\bfseries \abstractname
\@endparpenalty\@M
\end{center}}%
{\par\vfil\null\endtitlepage}
\else
\newenvironment{abstract}{%
\if@twocolumn
\section*{\abstractname}%
\else
\small
\begin{center}%
{\bfseries \abstractname\vspace{-.5em}\vspace{\z@}}%
\end{center}%
\quotation
\fi}
{\if@twocolumn\else\endquotation\fi}
\fi
\newenvironment{verse}
{\let\\\@centercr
\list{}{\itemsep \z@
\itemindent -1.5em%
\listparindent\itemindent
\rightmargin \leftmargin
\advance\leftmargin 1.5em}%
\item\relax}
{\endlist}
\newenvironment{quotation}
{\list{}{\listparindent 1.5em%
\itemindent \listparindent
\rightmargin \leftmargin
\parsep \z@ \@plus\p@}%
\item\relax}
{\endlist}
\newenvironment{quote}
{\list{}{\rightmargin\leftmargin}%
\item\relax}
{\endlist}
\if@compatibility
\newenvironment{titlepage}
{%
\if@twocolumn
\@restonecoltrue\onecolumn
\else
\@restonecolfalse\newpage
\fi
\thispagestyle{empty}%
\setcounter{page}\z@
}%
{\if@restonecol\twocolumn \else \newpage \fi
}
\else
\newenvironment{titlepage}
{%
\if@twocolumn
\@restonecoltrue\onecolumn
\else
\@restonecolfalse\newpage
\fi
\thispagestyle{empty}%
\setcounter{page}\@ne
}%
{\if@restonecol\twocolumn \else \newpage \fi
\if@twoside\else
\setcounter{page}\@ne
\fi
}
\fi
\newcommand\appendix{\par
\setcounter{section}{0}%
\setcounter{subsection}{0}%
\gdef\thesection{\@Alph\c@section}}
\setlength\arraycolsep{5\p@}
\setlength\tabcolsep{6\p@}
\setlength\arrayrulewidth{.4\p@}
\setlength\doublerulesep{2\p@}
\setlength\tabbingsep{\labelsep}
\skip\@mpfootins = \skip\footins
\setlength\fboxsep{3\p@}
\setlength\fboxrule{.4\p@}
\renewcommand \theequation {\@arabic\c@equation}
\newcounter{figure}
\renewcommand \thefigure {\@arabic\c@figure}
\def\fps@figure{tbp}
\def\ftype@figure{1}
\def\ext@figure{lof}
\def\fnum@figure{\figurename\nobreakspace\thefigure}
\newenvironment{figure}
{\@float{figure}}
{\end@float}
\newenvironment{figure*}
{\@dblfloat{figure}}
{\end@dblfloat}
\newcounter{table}
\renewcommand\thetable{\@arabic\c@table}
\def\fps@table{tbp}
\def\ftype@table{2}
\def\ext@table{lot}
\def\fnum@table{\tablename\nobreakspace\thetable}
\newenvironment{table}
{\@float{table}}
{\end@float}
\newenvironment{table*}
{\@dblfloat{table}}
{\end@dblfloat}
\newlength\abovecaptionskip
\newlength\belowcaptionskip
\setlength\abovecaptionskip{10\p@}
\setlength\belowcaptionskip{0\p@}
\long\def\@makecaption#1#2{%
\vskip\abovecaptionskip
\sbox\@tempboxa{#1: #2}%
\ifdim \wd\@tempboxa >\hsize
#1: #2\par
\else
\global \@minipagefalse
\hb@xt@\hsize{\hfil\box\@tempboxa\hfil}%
\fi
\vskip\belowcaptionskip}
\DeclareOldFontCommand{\rm}{\normalfont\rmfamily}{\mathrm}
\DeclareOldFontCommand{\sf}{\normalfont\sffamily}{\mathsf}
\DeclareOldFontCommand{\tt}{\normalfont\ttfamily}{\mathtt}
\DeclareOldFontCommand{\bf}{\normalfont\bfseries}{\mathbf}
\DeclareOldFontCommand{\it}{\normalfont\itshape}{\mathit}
\DeclareOldFontCommand{\sl}{\normalfont\slshape}{\@nomath\sl}
\DeclareOldFontCommand{\sc}{\normalfont\scshape}{\@nomath\sc}
\DeclareRobustCommand*\cal{\@fontswitch\relax\mathcal}
\DeclareRobustCommand*\mit{\@fontswitch\relax\mathnormal}
\newcommand\@pnumwidth{1.55em}
\newcommand\@tocrmarg{2.55em}
\newcommand\@dotsep{4.5}
\setcounter{tocdepth}{3}
\newcommand\tableofcontents{%
\section*{\contentsname
\@mkboth{%
\MakeUppercase\contentsname}{\MakeUppercase\contentsname}}%
\@starttoc{toc}%
}
\newcommand*\l@part[2]{%
\ifnum \c@tocdepth >-2\relax
\addpenalty\@secpenalty
\addvspace{2.25em \@plus\p@}%
\setlength\@tempdima{3em}%
\begingroup
\parindent \z@ \rightskip \@pnumwidth
\parfillskip -\@pnumwidth
{\leavevmode
\large \bfseries #1\hfil \hb@xt@\@pnumwidth{\hss #2}}\par
\nobreak
\if@compatibility
\global\@nobreaktrue
\everypar{\global\@nobreakfalse\everypar{}}%
\fi
\endgroup
\fi}
\newcommand*\l@section[2]{%
\ifnum \c@tocdepth >\z@
\addpenalty\@secpenalty
\addvspace{1.0em \@plus\p@}%
\setlength\@tempdima{1.5em}%
\begingroup
\parindent \z@ \rightskip \@pnumwidth
\parfillskip -\@pnumwidth
\leavevmode \bfseries
\advance\leftskip\@tempdima
\hskip -\leftskip
#1\nobreak\hfil \nobreak\hb@xt@\@pnumwidth{\hss #2}\par
\endgroup
\fi}
\newcommand*\l@subsection{\@dottedtocline{2}{1.5em}{2.3em}}
\newcommand*\l@subsubsection{\@dottedtocline{3}{3.8em}{3.2em}}
\newcommand*\l@paragraph{\@dottedtocline{4}{7.0em}{4.1em}}
\newcommand*\l@subparagraph{\@dottedtocline{5}{10em}{5em}}
\newcommand\listoffigures{%
\section*{\listfigurename}%
\@mkboth{\MakeUppercase\listfigurename}%
{\MakeUppercase\listfigurename}%
\@starttoc{lof}%
}
\newcommand*\l@figure{\@dottedtocline{1}{1.5em}{2.3em}}
\newcommand\listoftables{%
\section*{\listtablename}%
\@mkboth{%
\MakeUppercase\listtablename}%
{\MakeUppercase\listtablename}%
\@starttoc{lot}%
}
\let\l@table\l@figure
\newdimen\bibindent
\setlength\bibindent{1.5em}
\newenvironment{thebibliography}[1]
{\section*{\refname}%
\@mkboth{\MakeUppercase\refname}{\MakeUppercase\refname}%
\list{\@biblabel{\@arabic\c@enumiv}}%
{\settowidth\labelwidth{\@biblabel{#1}}%
\leftmargin\labelwidth
\advance\leftmargin\labelsep
\@openbib@code
\usecounter{enumiv}%
\let\p@enumiv\@empty
\renewcommand\theenumiv{\@arabic\c@enumiv}}%
\sloppy
\clubpenalty4000
\@clubpenalty \clubpenalty
\widowpenalty4000%
\sfcode`\.\@m}
{\def\@noitemerr
{\@latex@warning{Empty `thebibliography' environment}}%
\endlist}
\newcommand\newblock{\hskip .11em\@plus.33em\@minus.07em}
\let\@openbib@code\@empty
\newenvironment{theindex}
{\if@twocolumn
\@restonecolfalse
\else
\@restonecoltrue
\fi
\twocolumn[\section*{\indexname}]%
\@mkboth{\MakeUppercase\indexname}%
{\MakeUppercase\indexname}%
\thispagestyle{plain}\parindent\z@
\parskip\z@ \@plus .3\p@\relax
\columnseprule \z@
\columnsep 35\p@
\let\item\@idxitem}
{\if@restonecol\onecolumn\else\clearpage\fi}
\newcommand\@idxitem{\par\hangindent 40\p@}
\newcommand\subitem{\@idxitem \hspace*{20\p@}}
\newcommand\subsubitem{\@idxitem \hspace*{30\p@}}
\newcommand\indexspace{\par \vskip 10\p@ \@plus5\p@ \@minus3\p@\relax}
\renewcommand\footnoterule{%
\kern-3\p@
\hrule\@width.4\columnwidth
\kern2.6\p@}
\newcommand\@makefntext[1]{%
\parindent 1em%
\noindent
\hb@xt@1.8em{\hss\@makefnmark}#1}
\newcommand\contentsname{Contents}
\newcommand\listfigurename{List of Figures}
\newcommand\listtablename{List of Tables}
\newcommand\refname{References}
\newcommand\indexname{Index}
\newcommand\figurename{Figure}
\newcommand\tablename{Table}
\newcommand\partname{Part}
\newcommand\appendixname{Appendix}
\newcommand\abstractname{}
\def\today{\ifcase\month\or
January\or February\or March\or April\or May\or June\or
July\or August\or September\or October\or November\or December\fi
\space\number\day, \number\year}
\setlength\columnsep{10\p@}
\setlength\columnseprule{0\p@}
\pagestyle{plain}
\pagenumbering{arabic}
% === ARLIMS adaptions following ============================================
\pagestyle{empty}
\setlength{\oddsidemargin}{1.5 cm}
\setlength{\evensidemargin}{-0.5 cm}
\setlength{\textheight}{22 cm}
\setlength{\textwidth}{15 cm}
\setlength{\topmargin}{0 cm}
% --- special indexing to cope with the list of articles
\newif\ifIndexMade
\def\makeartIndex{%
\protected@write1{}{\string\global\string\IndexMadetrue}
\newwrite\@artIndexfile
\immediate\openout\@artIndexfile=\jobname.adx
\def\artIndex{\@bsphack\begingroup\@sanitize\@wrartIndex}
\typeout{Writing artIndex file \jobname.adx}%
\let\makeartIndex\@empty
}
\def\@wrartIndex#1{%
\protected@write\@artIndexfile{}{%
\string\artIndexentry{\factTitle}%
{\thepage}%
{\string\last#1}%
{\factAuthor\factAuthorB\factAuthorC\factAuthorD}}%
\endgroup%
\@esphack%
}
\def\artIndex{\@bsphack\begingroup \@sanitize\@artIndex}
\def\@artIndex#1{\endgroup\@esphack}
\def\artIndexLast#1{%
\protected@write1{}{\string\gdef\string\last#1{\thepage}}%
}
% --- to set up the headers permanently
\newcommand{\markperm}[2]{%
\global\def\@evenhead{\arabic{page}\hfil{#1}}%
\global\def\@oddhead{{#2}\hfil\arabic{page}}
}
% (yes, I know I've hardwired arabic page numbering)
% --- to set up the headers with a once off first
\newcommand{\markonce}[3]{%
\global\def\@evenhead{\arabic{page}\hfil{#1}\markperm{#2}{#3}}%
\global\def\@oddhead{{#1}\hfil\arabic{page}\markperm{#2}{#3}}%
}
%% Put a line after the abstract %%
\newcommand{\drawline}{%
\vspace*{1mm}
\begin{center}
\rule{\textwidth}{0.2mm}
\end{center}
\vspace*{1mm}
}
\newcommand{\reff}[1]{(\ref{#1})}
% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
% Define the author and title commands. Bascially each just stuffs
% the argument into a storage command. I need to define the blank
% storage commands because I am using the "renewcomand", since I
% need to do this for each article.
\def\author{}
\def\institute{}
\def\factTitle{}
\def\factAuthor{}
\def\factAuthorB{}
\def\factAuthorC{}
\def\factAuthorD{}
\def\factInstitute{}
\def\factInstituteB{}
\def\factInstituteC{}
\def\factInstituteD{}
\def\footInst{}
\def\footInstB{}
\def\footInstC{}
\def\footInstD{}
\def\footAuthorInst{}
\def\footAuthorInstB{}
\def\footAuthorInstC{}
\def\footAuthorInstD{}
\renewcommand{\author}[2][]{
\renewcommand{\footAuthorInst}{#1}
\renewcommand{\factAuthor}{#2}
}
\newcommand{\authorB}[2][]{
\renewcommand{\footAuthorInstB}{#1}
\renewcommand{\factAuthorB}{#2}
}
\newcommand{\authorC}[2][]{
\renewcommand{\footAuthorInstC}{#1}
\renewcommand{\factAuthorC}{#2}
}
\newcommand{\authorD}[2][]{
\renewcommand{\footAuthorInstD}{#1}
\renewcommand{\factAuthorD}{#2}
}
\renewcommand{\institute}[2][]{
\renewcommand{\footInst}{#1}
\renewcommand{\factInstitute}{#2}
}
\newcommand{\instituteB}[2][*]{
\renewcommand{\footInstB}{#1}
\renewcommand{\factInstituteB}{#2}
}
\newcommand{\instituteC}[2][**]{
\renewcommand{\footInstC}{#1}
\renewcommand{\factInstituteC}{#2}
}
\newcommand{\instituteD}[2][***]{
\renewcommand{\footInstD}{#1}
\renewcommand{\factInstituteD}{#2}
}
\def\nameTag{}
\renewcommand{\title}[1]{
\renewcommand{\factTitle}{#1}
}
\newcommand{\iimsaddress}{
\begin{center}
\textit{Institute of Information \& Mathematical Sciences\\
Massey University at Albany, Auckland, New Zealand.}
\end{center}
}
\newcommand{\maketitle}[1][\factTitle]{
\begin{center}
\markonce{
\parbox{10cm}{\small
\textit{The Python Papers,} Vol.~3, No.~3 (2008)\\[1mm]
Available online at http://ojs.pythonpapers.org/index.php/tpp/issue/view/10
}
}
{\factAuthor\factAuthorB\factAuthorC\factAuthorD}
{#1}
\vspace*{1cm}
\begin{minipage}{12cm}
\begin{center}
{\bfseries\Large\factTitle\par}
\vspace*{2mm}
\end{center}
\end{minipage}
\vspace*{3mm}
\textsc{%
\factAuthor $^{\footAuthorInst}$%
\factAuthorB $^{\footAuthorInstB}$%
\factAuthorC $^{\footAuthorInstC}$%
\factAuthorD $^{\footAuthorInstD}$%
}
\vspace*{1mm}
$^{\footInst}$\textit{\factInstitute}
$^{\footInstB}$\textit{\factInstituteB}
$^{\footInstC}$\textit{\factInstituteC}
$^{\footInstD}$\textit{\factInstituteD}
\artIndex{\nameTag}
\end{center}
}
% calculate the margins so that the text on opposite sides
% of the same sheet is lined up at the edges
% ok, its a fudge, but you can change the oddsidemargin
% to shift both sides in sync.
\oddsidemargin1cm
\evensidemargin\paperwidth
\addtolength{\evensidemargin}{-\oddsidemargin}
\addtolength{\evensidemargin}{-205mm}
% --- turn off a few things -------------------------------------------------
\renewcommand{\markboth}[2]{}
\renewcommand{\markright}[1]{}
\newcommand{\markheading}[1]{}
\usepackage{amsmath,amsfonts,dsfont}
\usepackage{amsthm}
\newif\ifCombinedRlims
% === end of ARLIMS adaptions ===============================================
\if@twoside
\else
\raggedbottom
\fi
\if@twocolumn
\twocolumn
\sloppy
\flushbottom
\else
\onecolumn
\fi
\endinput
%%
%% End of file `arlims.cls'.

View File

@@ -0,0 +1,898 @@
%%
%% This is file `arlims.cls',
%% It is based on the original `article.cls' from a tetex 3.0
%% distribution (Ubuntu tetex-base 3.0-19 package).
%%
%% Additions to it were made by (probably) various authors at the
%% Massey University Institute of Information and Mathematical
%% Sciences.
%%
%% For further information contact Heath James
%% <h.a.james@massey.ac.nz>, Guy Kloss <g.kloss@massey.ac.nz>, Paul
%% Cowpertwait <p.s.cowpertwait@massey.ac.nz> or anybody you expect
%% suitable for this purpose ...
%%
%% Copyright 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004
%% The LaTeX3 Project and any individual authors listed elsewhere
%% in this file.
%%
%% This file was generated from file(s) of the LaTeX base system.
%% Numerous changes were made to be fit for RLIMS.
%% --------------------------------------------------------------
%%
%% Changes:
%%
%% * numerous changes previously to fit article.sty for the purpose
%%
%% version 1.5:
%%
%% * fixed the instructions
%% * added better default options to the template
%% * changed the year to 2008
%%
%% version 1.4:
%%
%% * fitted to a newer version of article.sty
%% * cleanups in layout and formatting
%% * better compliance to LaTeX2e
%% * re-fitted \institute, \instituteB, ...C, ...D
%% it doesn't work 100% nicely, but it works)
%% * removed dependency on natbib.sty (so cite.sty, or else can be
%% used as liked)
%% * changed paper size to a4paper by default
%% * removed dependencies to graphicx.sty and subfigure.sty
%% (users like to choose and do that themselves)
%%
%% --------------------------------------------------------------
%%
%% It may be distributed and/or modified under the
%% conditions of the LaTeX Project Public License, either version 1.3
%% of this license or (at your option) any later version.
%% The latest version of this license is in
%% http://www.latex-project.org/lppl.txt
%% and version 1.3 or later is part of all distributions of LaTeX
%% version 2003/12/01 or later.
%%
%% This file has the LPPL maintenance status "maintained".
%%
%% This file may only be distributed together with a copy of the LaTeX
%% base system. You may however distribute the LaTeX base system without
%% such generated files.
%%
%% The list of all files belonging to the LaTeX base distribution is
%% given in the file `manifest.txt'. See also `legal.txt' for additional
%% information.
%%
%% The list of derived (unpacked) files belonging to the distribution
%% and covered by LPPL is defined by the unpacking scripts (with
%% extension .ins) which are part of the distribution.
%% \CharacterTable
%% {Upper-case \A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z
%% Lower-case \a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z
%% Digits \0\1\2"s\4\5\6\7\8\9
%% Exclamation \! Double quote \" Hash (number) \#
%% Dollar \$ Percent \% Ampersand \&
%% Acute accent \' Left paren \( Right paren \)
%% Asterisk \* Plus \+ Comma \,
%% Minus \- Point \. Solidus \/
%% Colon \: Semicolon \; Less than \<
%% Equals \= Greater than \> Question mark \?
%% Commercial at \@ Left bracket \[ Backslash \\
%% Right bracket \] Circumflex \^ Underscore \_
%% Grave accent \` Left brace \{ Vertical bar \|
%% Right brace \} Tilde \~}
\NeedsTeXFormat{LaTeX2e}[1995/12/01]
\ProvidesClass{arlims}
[2008/04/23 v1.5
Massey Research Letters Article Class]
\newcommand\@ptsize{}
\newif\if@restonecol
\newif\if@titlepage
\@titlepagefalse
\if@compatibility\else
\DeclareOption{a4paper}
{\setlength\paperheight {297mm}%
\setlength\paperwidth {210mm}}
\DeclareOption{a5paper}
{\setlength\paperheight {210mm}%
\setlength\paperwidth {148mm}}
\DeclareOption{b5paper}
{\setlength\paperheight {250mm}%
\setlength\paperwidth {176mm}}
\DeclareOption{letterpaper}
{\setlength\paperheight {11in}%
\setlength\paperwidth {8.5in}}
\DeclareOption{legalpaper}
{\setlength\paperheight {14in}%
\setlength\paperwidth {8.5in}}
\DeclareOption{executivepaper}
{\setlength\paperheight {10.5in}%
\setlength\paperwidth {7.25in}}
\DeclareOption{landscape}
{\setlength\@tempdima {\paperheight}%
\setlength\paperheight {\paperwidth}%
\setlength\paperwidth {\@tempdima}}
\fi
\if@compatibility
\renewcommand\@ptsize{0}
\else
\DeclareOption{10pt}{\renewcommand\@ptsize{0}}
\fi
\DeclareOption{11pt}{\renewcommand\@ptsize{1}}
\DeclareOption{12pt}{\renewcommand\@ptsize{2}}
\if@compatibility\else
\DeclareOption{oneside}{\@twosidefalse \@mparswitchfalse}
\fi
\DeclareOption{twoside}{\@twosidetrue \@mparswitchtrue}
\DeclareOption{draft}{\setlength\overfullrule{5pt}}
\if@compatibility\else
\DeclareOption{final}{\setlength\overfullrule{0pt}}
\fi
\DeclareOption{titlepage}{\@titlepagetrue}
\if@compatibility\else
\DeclareOption{notitlepage}{\@titlepagefalse}
\fi
\if@compatibility\else
\DeclareOption{onecolumn}{\@twocolumnfalse}
\fi
\DeclareOption{twocolumn}{\@twocolumntrue}
\DeclareOption{leqno}{\input{leqno.clo}}
\DeclareOption{fleqn}{\input{fleqn.clo}}
\DeclareOption{openbib}{%
\AtEndOfPackage{%
\renewcommand\@openbib@code{%
\advance\leftmargin\bibindent
\itemindent -\bibindent
\listparindent \itemindent
\parsep \z@
}%
\renewcommand\newblock{\par}}%
}
\ExecuteOptions{a4paper,10pt,oneside,onecolumn,final}
\ProcessOptions
\input{size1\@ptsize.clo}
\setlength\lineskip{1\p@}
\setlength\normallineskip{1\p@}
\renewcommand\baselinestretch{}
\setlength\parskip{0\p@ \@plus \p@}
\@lowpenalty 51
\@medpenalty 151
\@highpenalty 301
\setcounter{topnumber}{2}
\renewcommand\topfraction{.7}
\setcounter{bottomnumber}{1}
\renewcommand\bottomfraction{.3}
\setcounter{totalnumber}{3}
\renewcommand\textfraction{.2}
\renewcommand\floatpagefraction{.5}
\setcounter{dbltopnumber}{2}
\renewcommand\dbltopfraction{.7}
\renewcommand\dblfloatpagefraction{.5}
\if@twoside
\def\ps@headings{%
\let\@oddfoot\@empty\let\@evenfoot\@empty
\def\@evenhead{\thepage\hfil\slshape\leftmark}%
\def\@oddhead{{\slshape\rightmark}\hfil\thepage}%
\let\@mkboth\markboth
\def\sectionmark##1{%
\markboth {\MakeUppercase{%
\ifnum \c@secnumdepth >\z@
\thesection\quad
\fi
##1}}{}}%
\def\subsectionmark##1{%
\markright {%
\ifnum \c@secnumdepth >\@ne
\thesubsection\quad
\fi
##1}}}
\else
\def\ps@headings{%
\let\@oddfoot\@empty
\def\@oddhead{{\slshape\rightmark}\hfil\thepage}%
\let\@mkboth\markboth
\def\sectionmark##1{%
\markright {\MakeUppercase{%
\ifnum \c@secnumdepth >\m@ne
\thesection\quad
\fi
##1}}}}
\fi
\def\ps@myheadings{%
\let\@oddfoot\@empty\let\@evenfoot\@empty
\def\@evenhead{\thepage\hfil\slshape\leftmark}%
\def\@oddhead{{\slshape\rightmark}\hfil\thepage}%
\let\@mkboth\@gobbletwo
\let\sectionmark\@gobble
\let\subsectionmark\@gobble
}
%---------- old definition of title page -------------------------------
\newif\ifGrxlb\ifGrxlb
\if@titlepage
\newcommand\maketitle{\begin{titlepage}%
\let\footnotesize\small
\let\footnoterule\relax
\let \footnote \thanks
\null\vfil
\vskip 60\p@
\begin{center}%
{\LARGE \@title \par}%
\vskip 3em%
{\large
\lineskip .75em%
\begin{tabular}[t]{c}%
\@author
\end{tabular}\par}%
\vskip 1.5em%
{\large \@date \par}% % Set date in \large size.
\end{center}\par
\@thanks
\vfil\null
\end{titlepage}%
\setcounter{footnote}{0}%
\global\let\thanks\relax
\global\let\maketitle\relax
\global\let\@thanks\@empty
\global\let\@author\@empty
\global\let\@date\@empty
\global\let\@title\@empty
\global\let\title\relax
\global\let\author\relax
\global\let\date\relax
\global\let\and\relax
}
\else
\newcommand\maketitle{\par
\begingroup
\renewcommand\thefootnote{\@fnsymbol\c@footnote}%
\def\@makefnmark{\rlap{\@textsuperscript{\normalfont\@thefnmark}}}%
\long\def\@makefntext##1{\parindent 1em\noindent
\hb@xt@1.8em{%
\hss\@textsuperscript{\normalfont\@thefnmark}}##1}%
\if@twocolumn
\ifnum \col@number=\@ne
\@maketitle
\else
\twocolumn[\@maketitle]%
\fi
\else
\newpage
\global\@topnum\z@ % Prevents figures from going at top of page.
\@maketitle
\fi
\thispagestyle{plain}\@thanks
\endgroup
\setcounter{footnote}{0}%
\global\let\thanks\relax
\global\let\maketitle\relax
\global\let\@maketitle\relax
\global\let\@thanks\@empty
\global\let\@author\@empty
\global\let\@date\@empty
\global\let\@title\@empty
\global\let\title\relax
\global\let\author\relax
\global\let\date\relax
\global\let\and\relax
}
\def\@maketitle{%
\newpage
\null
\vskip 2em%
\begin{center}%
\let \footnote \thanks
{\LARGE \@title \par}%
\vskip 1.5em%
{\large
\lineskip .5em%
\begin{tabular}[t]{c}%
\@author
\end{tabular}\par}%
\vskip 1em%
{\large \@date}%
\end{center}%
\par
\vskip 1.5em}
\fi
\fi
%---------------- end of old definition of title page ---------------------
\setcounter{secnumdepth}{3}
\newcounter {part}
\newcounter {section}
\newcounter {subsection}[section]
\newcounter {subsubsection}[subsection]
\newcounter {paragraph}[subsubsection]
\newcounter {subparagraph}[paragraph]
\renewcommand \thepart {\@Roman\c@part}
\renewcommand \thesection {\@arabic\c@section}
\renewcommand\thesubsection {\thesection.\@arabic\c@subsection}
\renewcommand\thesubsubsection{\thesubsection .\@arabic\c@subsubsection}
\renewcommand\theparagraph {\thesubsubsection.\@arabic\c@paragraph}
\renewcommand\thesubparagraph {\theparagraph.\@arabic\c@subparagraph}
\newcommand\part{%
\if@noskipsec \leavevmode \fi
\par
\addvspace{4ex}%
\@afterindentfalse
\secdef\@part\@spart}
\def\@part[#1]#2{%
\ifnum \c@secnumdepth >\m@ne
\refstepcounter{part}%
\addcontentsline{toc}{part}{\thepart\hspace{1em}#1}%
\else
\addcontentsline{toc}{part}{#1}%
\fi
{\parindent \z@ \raggedright
\interlinepenalty \@M
\normalfont
\ifnum \c@secnumdepth >\m@ne
\Large\bfseries \partname\nobreakspace\thepart
\par\nobreak
\fi
\huge \bfseries #2%
\markboth{}{}\par}%
\nobreak
\vskip 3ex
\@afterheading}
\def\@spart#1{%
{\parindent \z@ \raggedright
\interlinepenalty \@M
\normalfont
\huge \bfseries #1\par}%
\nobreak
\vskip 3ex
\@afterheading}
\newcommand\section{\@startsection {section}{1}{\z@}%
{-3.5ex \@plus -1ex \@minus -.2ex}%
{2.3ex \@plus.2ex}%
{\normalfont\Large\bfseries}}
\newcommand\subsection{\@startsection{subsection}{2}{\z@}%
{-3.25ex\@plus -1ex \@minus -.2ex}%
{1.5ex \@plus .2ex}%
{\normalfont\large\bfseries}}
\newcommand\subsubsection{\@startsection{subsubsection}{3}{\z@}%
{-3.25ex\@plus -1ex \@minus -.2ex}%
{1.5ex \@plus .2ex}%
{\normalfont\normalsize\bfseries}}
\newcommand\paragraph{\@startsection{paragraph}{4}{\z@}%
{3.25ex \@plus1ex \@minus.2ex}%
{-1em}%
{\normalfont\normalsize\bfseries}}
\newcommand\subparagraph{\@startsection{subparagraph}{5}{\parindent}%
{3.25ex \@plus1ex \@minus .2ex}%
{-1em}%
{\normalfont\normalsize\bfseries}}
\if@twocolumn
\setlength\leftmargini {2em}
\else
\setlength\leftmargini {2.5em}
\fi
\leftmargin \leftmargini
\setlength\leftmarginii {2.2em}
\setlength\leftmarginiii {1.87em}
\setlength\leftmarginiv {1.7em}
\if@twocolumn
\setlength\leftmarginv {.5em}
\setlength\leftmarginvi {.5em}
\else
\setlength\leftmarginv {1em}
\setlength\leftmarginvi {1em}
\fi
\setlength \labelsep {.5em}
\setlength \labelwidth{\leftmargini}
\addtolength\labelwidth{-\labelsep}
\@beginparpenalty -\@lowpenalty
\@endparpenalty -\@lowpenalty
\@itempenalty -\@lowpenalty
\renewcommand\theenumi{\@arabic\c@enumi}
\renewcommand\theenumii{\@alph\c@enumii}
\renewcommand\theenumiii{\@roman\c@enumiii}
\renewcommand\theenumiv{\@Alph\c@enumiv}
\newcommand\labelenumi{\theenumi.}
\newcommand\labelenumii{(\theenumii)}
\newcommand\labelenumiii{\theenumiii.}
\newcommand\labelenumiv{\theenumiv.}
\renewcommand\p@enumii{\theenumi}
\renewcommand\p@enumiii{\theenumi(\theenumii)}
\renewcommand\p@enumiv{\p@enumiii\theenumiii}
\newcommand\labelitemi{\textbullet}
\newcommand\labelitemii{\normalfont\bfseries \textendash}
\newcommand\labelitemiii{\textasteriskcentered}
\newcommand\labelitemiv{\textperiodcentered}
\newenvironment{description}
{\list{}{\labelwidth\z@ \itemindent-\leftmargin
\let\makelabel\descriptionlabel}}
{\endlist}
\newcommand*\descriptionlabel[1]{\hspace\labelsep
\normalfont\bfseries #1}
\if@titlepage
\newenvironment{abstract}{%
\titlepage
\null\vfil
\@beginparpenalty\@lowpenalty
\begin{center}%
\bfseries \abstractname
\@endparpenalty\@M
\end{center}}%
{\par\vfil\null\endtitlepage}
\else
\newenvironment{abstract}{%
\if@twocolumn
\section*{\abstractname}%
\else
\small
\begin{center}%
{\bfseries \abstractname\vspace{-.5em}\vspace{\z@}}%
\end{center}%
\quotation
\fi}
{\if@twocolumn\else\endquotation\fi}
\fi
\newenvironment{verse}
{\let\\\@centercr
\list{}{\itemsep \z@
\itemindent -1.5em%
\listparindent\itemindent
\rightmargin \leftmargin
\advance\leftmargin 1.5em}%
\item\relax}
{\endlist}
\newenvironment{quotation}
{\list{}{\listparindent 1.5em%
\itemindent \listparindent
\rightmargin \leftmargin
\parsep \z@ \@plus\p@}%
\item\relax}
{\endlist}
\newenvironment{quote}
{\list{}{\rightmargin\leftmargin}%
\item\relax}
{\endlist}
\if@compatibility
\newenvironment{titlepage}
{%
\if@twocolumn
\@restonecoltrue\onecolumn
\else
\@restonecolfalse\newpage
\fi
\thispagestyle{empty}%
\setcounter{page}\z@
}%
{\if@restonecol\twocolumn \else \newpage \fi
}
\else
\newenvironment{titlepage}
{%
\if@twocolumn
\@restonecoltrue\onecolumn
\else
\@restonecolfalse\newpage
\fi
\thispagestyle{empty}%
\setcounter{page}\@ne
}%
{\if@restonecol\twocolumn \else \newpage \fi
\if@twoside\else
\setcounter{page}\@ne
\fi
}
\fi
\newcommand\appendix{\par
\setcounter{section}{0}%
\setcounter{subsection}{0}%
\gdef\thesection{\@Alph\c@section}}
\setlength\arraycolsep{5\p@}
\setlength\tabcolsep{6\p@}
\setlength\arrayrulewidth{.4\p@}
\setlength\doublerulesep{2\p@}
\setlength\tabbingsep{\labelsep}
\skip\@mpfootins = \skip\footins
\setlength\fboxsep{3\p@}
\setlength\fboxrule{.4\p@}
\renewcommand \theequation {\@arabic\c@equation}
\newcounter{figure}
\renewcommand \thefigure {\@arabic\c@figure}
\def\fps@figure{tbp}
\def\ftype@figure{1}
\def\ext@figure{lof}
\def\fnum@figure{\figurename\nobreakspace\thefigure}
\newenvironment{figure}
{\@float{figure}}
{\end@float}
\newenvironment{figure*}
{\@dblfloat{figure}}
{\end@dblfloat}
\newcounter{table}
\renewcommand\thetable{\@arabic\c@table}
\def\fps@table{tbp}
\def\ftype@table{2}
\def\ext@table{lot}
\def\fnum@table{\tablename\nobreakspace\thetable}
\newenvironment{table}
{\@float{table}}
{\end@float}
\newenvironment{table*}
{\@dblfloat{table}}
{\end@dblfloat}
\newlength\abovecaptionskip
\newlength\belowcaptionskip
\setlength\abovecaptionskip{10\p@}
\setlength\belowcaptionskip{0\p@}
\long\def\@makecaption#1#2{%
\vskip\abovecaptionskip
\sbox\@tempboxa{#1: #2}%
\ifdim \wd\@tempboxa >\hsize
#1: #2\par
\else
\global \@minipagefalse
\hb@xt@\hsize{\hfil\box\@tempboxa\hfil}%
\fi
\vskip\belowcaptionskip}
\DeclareOldFontCommand{\rm}{\normalfont\rmfamily}{\mathrm}
\DeclareOldFontCommand{\sf}{\normalfont\sffamily}{\mathsf}
\DeclareOldFontCommand{\tt}{\normalfont\ttfamily}{\mathtt}
\DeclareOldFontCommand{\bf}{\normalfont\bfseries}{\mathbf}
\DeclareOldFontCommand{\it}{\normalfont\itshape}{\mathit}
\DeclareOldFontCommand{\sl}{\normalfont\slshape}{\@nomath\sl}
\DeclareOldFontCommand{\sc}{\normalfont\scshape}{\@nomath\sc}
\DeclareRobustCommand*\cal{\@fontswitch\relax\mathcal}
\DeclareRobustCommand*\mit{\@fontswitch\relax\mathnormal}
\newcommand\@pnumwidth{1.55em}
\newcommand\@tocrmarg{2.55em}
\newcommand\@dotsep{4.5}
\setcounter{tocdepth}{3}
\newcommand\tableofcontents{%
\section*{\contentsname
\@mkboth{%
\MakeUppercase\contentsname}{\MakeUppercase\contentsname}}%
\@starttoc{toc}%
}
\newcommand*\l@part[2]{%
\ifnum \c@tocdepth >-2\relax
\addpenalty\@secpenalty
\addvspace{2.25em \@plus\p@}%
\setlength\@tempdima{3em}%
\begingroup
\parindent \z@ \rightskip \@pnumwidth
\parfillskip -\@pnumwidth
{\leavevmode
\large \bfseries #1\hfil \hb@xt@\@pnumwidth{\hss #2}}\par
\nobreak
\if@compatibility
\global\@nobreaktrue
\everypar{\global\@nobreakfalse\everypar{}}%
\fi
\endgroup
\fi}
\newcommand*\l@section[2]{%
\ifnum \c@tocdepth >\z@
\addpenalty\@secpenalty
\addvspace{1.0em \@plus\p@}%
\setlength\@tempdima{1.5em}%
\begingroup
\parindent \z@ \rightskip \@pnumwidth
\parfillskip -\@pnumwidth
\leavevmode \bfseries
\advance\leftskip\@tempdima
\hskip -\leftskip
#1\nobreak\hfil \nobreak\hb@xt@\@pnumwidth{\hss #2}\par
\endgroup
\fi}
\newcommand*\l@subsection{\@dottedtocline{2}{1.5em}{2.3em}}
\newcommand*\l@subsubsection{\@dottedtocline{3}{3.8em}{3.2em}}
\newcommand*\l@paragraph{\@dottedtocline{4}{7.0em}{4.1em}}
\newcommand*\l@subparagraph{\@dottedtocline{5}{10em}{5em}}
\newcommand\listoffigures{%
\section*{\listfigurename}%
\@mkboth{\MakeUppercase\listfigurename}%
{\MakeUppercase\listfigurename}%
\@starttoc{lof}%
}
\newcommand*\l@figure{\@dottedtocline{1}{1.5em}{2.3em}}
\newcommand\listoftables{%
\section*{\listtablename}%
\@mkboth{%
\MakeUppercase\listtablename}%
{\MakeUppercase\listtablename}%
\@starttoc{lot}%
}
\let\l@table\l@figure
\newdimen\bibindent
\setlength\bibindent{1.5em}
\newenvironment{thebibliography}[1]
{\section*{\refname}%
\@mkboth{\MakeUppercase\refname}{\MakeUppercase\refname}%
\list{\@biblabel{\@arabic\c@enumiv}}%
{\settowidth\labelwidth{\@biblabel{#1}}%
\leftmargin\labelwidth
\advance\leftmargin\labelsep
\@openbib@code
\usecounter{enumiv}%
\let\p@enumiv\@empty
\renewcommand\theenumiv{\@arabic\c@enumiv}}%
\sloppy
\clubpenalty4000
\@clubpenalty \clubpenalty
\widowpenalty4000%
\sfcode`\.\@m}
{\def\@noitemerr
{\@latex@warning{Empty `thebibliography' environment}}%
\endlist}
\newcommand\newblock{\hskip .11em\@plus.33em\@minus.07em}
\let\@openbib@code\@empty
\newenvironment{theindex}
{\if@twocolumn
\@restonecolfalse
\else
\@restonecoltrue
\fi
\twocolumn[\section*{\indexname}]%
\@mkboth{\MakeUppercase\indexname}%
{\MakeUppercase\indexname}%
\thispagestyle{plain}\parindent\z@
\parskip\z@ \@plus .3\p@\relax
\columnseprule \z@
\columnsep 35\p@
\let\item\@idxitem}
{\if@restonecol\onecolumn\else\clearpage\fi}
\newcommand\@idxitem{\par\hangindent 40\p@}
\newcommand\subitem{\@idxitem \hspace*{20\p@}}
\newcommand\subsubitem{\@idxitem \hspace*{30\p@}}
\newcommand\indexspace{\par \vskip 10\p@ \@plus5\p@ \@minus3\p@\relax}
\renewcommand\footnoterule{%
\kern-3\p@
\hrule\@width.4\columnwidth
\kern2.6\p@}
\newcommand\@makefntext[1]{%
\parindent 1em%
\noindent
\hb@xt@1.8em{\hss\@makefnmark}#1}
\newcommand\contentsname{Contents}
\newcommand\listfigurename{List of Figures}
\newcommand\listtablename{List of Tables}
\newcommand\refname{References}
\newcommand\indexname{Index}
\newcommand\figurename{Figure}
\newcommand\tablename{Table}
\newcommand\partname{Part}
\newcommand\appendixname{Appendix}
\newcommand\abstractname{}
\def\today{\ifcase\month\or
January\or February\or March\or April\or May\or June\or
July\or August\or September\or October\or November\or December\fi
\space\number\day, \number\year}
\setlength\columnsep{10\p@}
\setlength\columnseprule{0\p@}
\pagestyle{plain}
\pagenumbering{arabic}
% === ARLIMS adaptions following ============================================
\pagestyle{empty}
\setlength{\oddsidemargin}{1.5 cm}
\setlength{\evensidemargin}{-0.5 cm}
\setlength{\textheight}{22 cm}
\setlength{\textwidth}{15 cm}
\setlength{\topmargin}{0 cm}
% --- special indexing to cope with the list of articles
\newif\ifIndexMade
\def\makeartIndex{%
\protected@write1{}{\string\global\string\IndexMadetrue}
\newwrite\@artIndexfile
\immediate\openout\@artIndexfile=\jobname.adx
\def\artIndex{\@bsphack\begingroup\@sanitize\@wrartIndex}
\typeout{Writing artIndex file \jobname.adx}%
\let\makeartIndex\@empty
}
\def\@wrartIndex#1{%
\protected@write\@artIndexfile{}{%
\string\artIndexentry{\factTitle}%
{\thepage}%
{\string\last#1}%
{\factAuthor\factAuthorB\factAuthorC\factAuthorD}}%
\endgroup%
\@esphack%
}
\def\artIndex{\@bsphack\begingroup \@sanitize\@artIndex}
\def\@artIndex#1{\endgroup\@esphack}
\def\artIndexLast#1{%
\protected@write1{}{\string\gdef\string\last#1{\thepage}}%
}
% --- to set up the headers permanently
\newcommand{\markperm}[2]{%
\global\def\@evenhead{\arabic{page}\hfil{#1}}%
\global\def\@oddhead{{#2}\hfil\arabic{page}}
}
% (yes, I know I've hardwired arabic page numbering)
% --- to set up the headers with a once off first
\newcommand{\markonce}[3]{%
\global\def\@evenhead{\arabic{page}\hfil{#1}\markperm{#2}{#3}}%
\global\def\@oddhead{{#1}\hfil\arabic{page}\markperm{#2}{#3}}%
}
%% Put a line after the abstract %%
\newcommand{\drawline}{%
\vspace*{1mm}
\begin{center}
\rule{\textwidth}{0.2mm}
\end{center}
\vspace*{1mm}
}
\newcommand{\reff}[1]{(\ref{#1})}
% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
% Define the author and title commands. Bascially each just stuffs
% the argument into a storage command. I need to define the blank
% storage commands because I am using the "renewcomand", since I
% need to do this for each article.
\def\author{}
\def\institute{}
\def\factTitle{}
\def\factAuthor{}
\def\factAuthorB{}
\def\factAuthorC{}
\def\factAuthorD{}
\def\factInstitute{}
\def\factInstituteB{}
\def\factInstituteC{}
\def\factInstituteD{}
\def\footInst{}
\def\footInstB{}
\def\footInstC{}
\def\footInstD{}
\def\footAuthorInst{}
\def\footAuthorInstB{}
\def\footAuthorInstC{}
\def\footAuthorInstD{}
\renewcommand{\author}[2][]{
\renewcommand{\footAuthorInst}{#1}
\renewcommand{\factAuthor}{#2}
}
\newcommand{\authorB}[2][]{
\renewcommand{\footAuthorInstB}{#1}
\renewcommand{\factAuthorB}{#2}
}
\newcommand{\authorC}[2][]{
\renewcommand{\footAuthorInstC}{#1}
\renewcommand{\factAuthorC}{#2}
}
\newcommand{\authorD}[2][]{
\renewcommand{\footAuthorInstD}{#1}
\renewcommand{\factAuthorD}{#2}
}
\renewcommand{\institute}[2][]{
\renewcommand{\footInst}{#1}
\renewcommand{\factInstitute}{#2}
}
\newcommand{\instituteB}[2][*]{
\renewcommand{\footInstB}{#1}
\renewcommand{\factInstituteB}{#2}
}
\newcommand{\instituteC}[2][**]{
\renewcommand{\footInstC}{#1}
\renewcommand{\factInstituteC}{#2}
}
\newcommand{\instituteD}[2][***]{
\renewcommand{\footInstD}{#1}
\renewcommand{\factInstituteD}{#2}
}
\def\nameTag{}
\renewcommand{\title}[1]{
\renewcommand{\factTitle}{#1}
}
\newcommand{\iimsaddress}{
\begin{center}
\textit{Institute of Information \& Mathematical Sciences\\
Massey University at Albany, Auckland, New Zealand.}
\end{center}
}
\newcommand{\maketitle}[1][\factTitle]{
\begin{center}
\markonce{
\parbox{10cm}{\small
\textit{The Python Papers Monograph,} Vol.~1 (2009)\\[1mm]
Available online at http://ojs.pythonpapers.org/index.php/tppm
}
}
{\factAuthor\factAuthorB\factAuthorC\factAuthorD}
{#1}
\vspace*{1cm}
\begin{minipage}{12cm}
\begin{center}
{\bfseries\Large\factTitle\par}
\vspace*{2mm}
\end{center}
\end{minipage}
\vspace*{3mm}
\textsc{%
\factAuthor $^{\footAuthorInst}$%
\factAuthorB $^{\footAuthorInstB}$%
\factAuthorC $^{\footAuthorInstC}$%
\factAuthorD $^{\footAuthorInstD}$%
}
\vspace*{1mm}
$^{\footInst}$\textit{\factInstitute}
$^{\footInstB}$\textit{\factInstituteB}
$^{\footInstC}$\textit{\factInstituteC}
$^{\footInstD}$\textit{\factInstituteD}
\artIndex{\nameTag}
\end{center}
}
% calculate the margins so that the text on opposite sides
% of the same sheet is lined up at the edges
% ok, its a fudge, but you can change the oddsidemargin
% to shift both sides in sync.
\oddsidemargin1cm
\evensidemargin\paperwidth
\addtolength{\evensidemargin}{-\oddsidemargin}
\addtolength{\evensidemargin}{-205mm}
% --- turn off a few things -------------------------------------------------
\renewcommand{\markboth}[2]{}
\renewcommand{\markright}[1]{}
\newcommand{\markheading}[1]{}
\usepackage{amsmath,amsfonts,dsfont}
\usepackage{amsthm}
\newif\ifCombinedRlims
% === end of ARLIMS adaptions ===============================================
\if@twoside
\else
\raggedbottom
\fi
\if@twocolumn
\twocolumn
\sloppy
\flushbottom
\else
\onecolumn
\fi
\endinput
%%
%% End of file `arlims.cls'.

View File

@@ -0,0 +1,898 @@
%%
%% This is file `arlims.cls',
%% It is based on the original `article.cls' from a tetex 3.0
%% distribution (Ubuntu tetex-base 3.0-19 package).
%%
%% Additions to it were made by (probably) various authors at the
%% Massey University Institute of Information and Mathematical
%% Sciences.
%%
%% For further information contact Heath James
%% <h.a.james@massey.ac.nz>, Guy Kloss <g.kloss@massey.ac.nz>, Paul
%% Cowpertwait <p.s.cowpertwait@massey.ac.nz> or anybody you expect
%% suitable for this purpose ...
%%
%% Copyright 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004
%% The LaTeX3 Project and any individual authors listed elsewhere
%% in this file.
%%
%% This file was generated from file(s) of the LaTeX base system.
%% Numerous changes were made to be fit for RLIMS.
%% --------------------------------------------------------------
%%
%% Changes:
%%
%% * numerous changes previously to fit article.sty for the purpose
%%
%% version 1.5:
%%
%% * fixed the instructions
%% * added better default options to the template
%% * changed the year to 2008
%%
%% version 1.4:
%%
%% * fitted to a newer version of article.sty
%% * cleanups in layout and formatting
%% * better compliance to LaTeX2e
%% * re-fitted \institute, \instituteB, ...C, ...D
%% it doesn't work 100% nicely, but it works)
%% * removed dependency on natbib.sty (so cite.sty, or else can be
%% used as liked)
%% * changed paper size to a4paper by default
%% * removed dependencies to graphicx.sty and subfigure.sty
%% (users like to choose and do that themselves)
%%
%% --------------------------------------------------------------
%%
%% It may be distributed and/or modified under the
%% conditions of the LaTeX Project Public License, either version 1.3
%% of this license or (at your option) any later version.
%% The latest version of this license is in
%% http://www.latex-project.org/lppl.txt
%% and version 1.3 or later is part of all distributions of LaTeX
%% version 2003/12/01 or later.
%%
%% This file has the LPPL maintenance status "maintained".
%%
%% This file may only be distributed together with a copy of the LaTeX
%% base system. You may however distribute the LaTeX base system without
%% such generated files.
%%
%% The list of all files belonging to the LaTeX base distribution is
%% given in the file `manifest.txt'. See also `legal.txt' for additional
%% information.
%%
%% The list of derived (unpacked) files belonging to the distribution
%% and covered by LPPL is defined by the unpacking scripts (with
%% extension .ins) which are part of the distribution.
%% \CharacterTable
%% {Upper-case \A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z
%% Lower-case \a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z
%% Digits \0\1\2"s\4\5\6\7\8\9
%% Exclamation \! Double quote \" Hash (number) \#
%% Dollar \$ Percent \% Ampersand \&
%% Acute accent \' Left paren \( Right paren \)
%% Asterisk \* Plus \+ Comma \,
%% Minus \- Point \. Solidus \/
%% Colon \: Semicolon \; Less than \<
%% Equals \= Greater than \> Question mark \?
%% Commercial at \@ Left bracket \[ Backslash \\
%% Right bracket \] Circumflex \^ Underscore \_
%% Grave accent \` Left brace \{ Vertical bar \|
%% Right brace \} Tilde \~}
\NeedsTeXFormat{LaTeX2e}[1995/12/01]
\ProvidesClass{arlims}
[2008/04/23 v1.5
Massey Research Letters Article Class]
\newcommand\@ptsize{}
\newif\if@restonecol
\newif\if@titlepage
\@titlepagefalse
\if@compatibility\else
\DeclareOption{a4paper}
{\setlength\paperheight {297mm}%
\setlength\paperwidth {210mm}}
\DeclareOption{a5paper}
{\setlength\paperheight {210mm}%
\setlength\paperwidth {148mm}}
\DeclareOption{b5paper}
{\setlength\paperheight {250mm}%
\setlength\paperwidth {176mm}}
\DeclareOption{letterpaper}
{\setlength\paperheight {11in}%
\setlength\paperwidth {8.5in}}
\DeclareOption{legalpaper}
{\setlength\paperheight {14in}%
\setlength\paperwidth {8.5in}}
\DeclareOption{executivepaper}
{\setlength\paperheight {10.5in}%
\setlength\paperwidth {7.25in}}
\DeclareOption{landscape}
{\setlength\@tempdima {\paperheight}%
\setlength\paperheight {\paperwidth}%
\setlength\paperwidth {\@tempdima}}
\fi
\if@compatibility
\renewcommand\@ptsize{0}
\else
\DeclareOption{10pt}{\renewcommand\@ptsize{0}}
\fi
\DeclareOption{11pt}{\renewcommand\@ptsize{1}}
\DeclareOption{12pt}{\renewcommand\@ptsize{2}}
\if@compatibility\else
\DeclareOption{oneside}{\@twosidefalse \@mparswitchfalse}
\fi
\DeclareOption{twoside}{\@twosidetrue \@mparswitchtrue}
\DeclareOption{draft}{\setlength\overfullrule{5pt}}
\if@compatibility\else
\DeclareOption{final}{\setlength\overfullrule{0pt}}
\fi
\DeclareOption{titlepage}{\@titlepagetrue}
\if@compatibility\else
\DeclareOption{notitlepage}{\@titlepagefalse}
\fi
\if@compatibility\else
\DeclareOption{onecolumn}{\@twocolumnfalse}
\fi
\DeclareOption{twocolumn}{\@twocolumntrue}
\DeclareOption{leqno}{\input{leqno.clo}}
\DeclareOption{fleqn}{\input{fleqn.clo}}
\DeclareOption{openbib}{%
\AtEndOfPackage{%
\renewcommand\@openbib@code{%
\advance\leftmargin\bibindent
\itemindent -\bibindent
\listparindent \itemindent
\parsep \z@
}%
\renewcommand\newblock{\par}}%
}
\ExecuteOptions{a4paper,12pt,oneside,onecolumn,final}
\ProcessOptions
\input{size1\@ptsize.clo}
\setlength\lineskip{1\p@}
\setlength\normallineskip{1\p@}
\renewcommand\baselinestretch{}
\setlength\parskip{0\p@ \@plus \p@}
\@lowpenalty 51
\@medpenalty 151
\@highpenalty 301
\setcounter{topnumber}{2}
\renewcommand\topfraction{.7}
\setcounter{bottomnumber}{1}
\renewcommand\bottomfraction{.3}
\setcounter{totalnumber}{3}
\renewcommand\textfraction{.2}
\renewcommand\floatpagefraction{.5}
\setcounter{dbltopnumber}{2}
\renewcommand\dbltopfraction{.7}
\renewcommand\dblfloatpagefraction{.5}
\if@twoside
\def\ps@headings{%
\let\@oddfoot\@empty\let\@evenfoot\@empty
\def\@evenhead{\thepage\hfil\slshape\leftmark}%
\def\@oddhead{{\slshape\rightmark}\hfil\thepage}%
\let\@mkboth\markboth
\def\sectionmark##1{%
\markboth {\MakeUppercase{%
\ifnum \c@secnumdepth >\z@
\thesection\quad
\fi
##1}}{}}%
\def\subsectionmark##1{%
\markright {%
\ifnum \c@secnumdepth >\@ne
\thesubsection\quad
\fi
##1}}}
\else
\def\ps@headings{%
\let\@oddfoot\@empty
\def\@oddhead{{\slshape\rightmark}\hfil\thepage}%
\let\@mkboth\markboth
\def\sectionmark##1{%
\markright {\MakeUppercase{%
\ifnum \c@secnumdepth >\m@ne
\thesection\quad
\fi
##1}}}}
\fi
\def\ps@myheadings{%
\let\@oddfoot\@empty\let\@evenfoot\@empty
\def\@evenhead{\thepage\hfil\slshape\leftmark}%
\def\@oddhead{{\slshape\rightmark}\hfil\thepage}%
\let\@mkboth\@gobbletwo
\let\sectionmark\@gobble
\let\subsectionmark\@gobble
}
%---------- old definition of title page -------------------------------
\newif\ifGrxlb\ifGrxlb
\if@titlepage
\newcommand\maketitle{\begin{titlepage}%
\let\footnotesize\small
\let\footnoterule\relax
\let \footnote \thanks
\null\vfil
\vskip 60\p@
\begin{center}%
{\LARGE \@title \par}%
\vskip 3em%
{\large
\lineskip .75em%
\begin{tabular}[t]{c}%
\@author
\end{tabular}\par}%
\vskip 1.5em%
{\large \@date \par}% % Set date in \large size.
\end{center}\par
\@thanks
\vfil\null
\end{titlepage}%
\setcounter{footnote}{0}%
\global\let\thanks\relax
\global\let\maketitle\relax
\global\let\@thanks\@empty
\global\let\@author\@empty
\global\let\@date\@empty
\global\let\@title\@empty
\global\let\title\relax
\global\let\author\relax
\global\let\date\relax
\global\let\and\relax
}
\else
\newcommand\maketitle{\par
\begingroup
\renewcommand\thefootnote{\@fnsymbol\c@footnote}%
\def\@makefnmark{\rlap{\@textsuperscript{\normalfont\@thefnmark}}}%
\long\def\@makefntext##1{\parindent 1em\noindent
\hb@xt@1.8em{%
\hss\@textsuperscript{\normalfont\@thefnmark}}##1}%
\if@twocolumn
\ifnum \col@number=\@ne
\@maketitle
\else
\twocolumn[\@maketitle]%
\fi
\else
\newpage
\global\@topnum\z@ % Prevents figures from going at top of page.
\@maketitle
\fi
\thispagestyle{plain}\@thanks
\endgroup
\setcounter{footnote}{0}%
\global\let\thanks\relax
\global\let\maketitle\relax
\global\let\@maketitle\relax
\global\let\@thanks\@empty
\global\let\@author\@empty
\global\let\@date\@empty
\global\let\@title\@empty
\global\let\title\relax
\global\let\author\relax
\global\let\date\relax
\global\let\and\relax
}
\def\@maketitle{%
\newpage
\null
\vskip 2em%
\begin{center}%
\let \footnote \thanks
{\LARGE \@title \par}%
\vskip 1.5em%
{\large
\lineskip .5em%
\begin{tabular}[t]{c}%
\@author
\end{tabular}\par}%
\vskip 1em%
{\large \@date}%
\end{center}%
\par
\vskip 1.5em}
\fi
\fi
%---------------- end of old definition of title page ---------------------
\setcounter{secnumdepth}{3}
\newcounter {part}
\newcounter {section}
\newcounter {subsection}[section]
\newcounter {subsubsection}[subsection]
\newcounter {paragraph}[subsubsection]
\newcounter {subparagraph}[paragraph]
\renewcommand \thepart {\@Roman\c@part}
\renewcommand \thesection {\@arabic\c@section}
\renewcommand\thesubsection {\thesection.\@arabic\c@subsection}
\renewcommand\thesubsubsection{\thesubsection .\@arabic\c@subsubsection}
\renewcommand\theparagraph {\thesubsubsection.\@arabic\c@paragraph}
\renewcommand\thesubparagraph {\theparagraph.\@arabic\c@subparagraph}
\newcommand\part{%
\if@noskipsec \leavevmode \fi
\par
\addvspace{4ex}%
\@afterindentfalse
\secdef\@part\@spart}
\def\@part[#1]#2{%
\ifnum \c@secnumdepth >\m@ne
\refstepcounter{part}%
\addcontentsline{toc}{part}{\thepart\hspace{1em}#1}%
\else
\addcontentsline{toc}{part}{#1}%
\fi
{\parindent \z@ \raggedright
\interlinepenalty \@M
\normalfont
\ifnum \c@secnumdepth >\m@ne
\Large\bfseries \partname\nobreakspace\thepart
\par\nobreak
\fi
\huge \bfseries #2%
\markboth{}{}\par}%
\nobreak
\vskip 3ex
\@afterheading}
\def\@spart#1{%
{\parindent \z@ \raggedright
\interlinepenalty \@M
\normalfont
\huge \bfseries #1\par}%
\nobreak
\vskip 3ex
\@afterheading}
\newcommand\section{\@startsection {section}{1}{\z@}%
{-3.5ex \@plus -1ex \@minus -.2ex}%
{2.3ex \@plus.2ex}%
{\normalfont\Large\bfseries}}
\newcommand\subsection{\@startsection{subsection}{2}{\z@}%
{-3.25ex\@plus -1ex \@minus -.2ex}%
{1.5ex \@plus .2ex}%
{\normalfont\large\bfseries}}
\newcommand\subsubsection{\@startsection{subsubsection}{3}{\z@}%
{-3.25ex\@plus -1ex \@minus -.2ex}%
{1.5ex \@plus .2ex}%
{\normalfont\normalsize\bfseries}}
\newcommand\paragraph{\@startsection{paragraph}{4}{\z@}%
{3.25ex \@plus1ex \@minus.2ex}%
{-1em}%
{\normalfont\normalsize\bfseries}}
\newcommand\subparagraph{\@startsection{subparagraph}{5}{\parindent}%
{3.25ex \@plus1ex \@minus .2ex}%
{-1em}%
{\normalfont\normalsize\bfseries}}
\if@twocolumn
\setlength\leftmargini {2em}
\else
\setlength\leftmargini {2.5em}
\fi
\leftmargin \leftmargini
\setlength\leftmarginii {2.2em}
\setlength\leftmarginiii {1.87em}
\setlength\leftmarginiv {1.7em}
\if@twocolumn
\setlength\leftmarginv {.5em}
\setlength\leftmarginvi {.5em}
\else
\setlength\leftmarginv {1em}
\setlength\leftmarginvi {1em}
\fi
\setlength \labelsep {.5em}
\setlength \labelwidth{\leftmargini}
\addtolength\labelwidth{-\labelsep}
\@beginparpenalty -\@lowpenalty
\@endparpenalty -\@lowpenalty
\@itempenalty -\@lowpenalty
\renewcommand\theenumi{\@arabic\c@enumi}
\renewcommand\theenumii{\@alph\c@enumii}
\renewcommand\theenumiii{\@roman\c@enumiii}
\renewcommand\theenumiv{\@Alph\c@enumiv}
\newcommand\labelenumi{\theenumi.}
\newcommand\labelenumii{(\theenumii)}
\newcommand\labelenumiii{\theenumiii.}
\newcommand\labelenumiv{\theenumiv.}
\renewcommand\p@enumii{\theenumi}
\renewcommand\p@enumiii{\theenumi(\theenumii)}
\renewcommand\p@enumiv{\p@enumiii\theenumiii}
\newcommand\labelitemi{\textbullet}
\newcommand\labelitemii{\normalfont\bfseries \textendash}
\newcommand\labelitemiii{\textasteriskcentered}
\newcommand\labelitemiv{\textperiodcentered}
\newenvironment{description}
{\list{}{\labelwidth\z@ \itemindent-\leftmargin
\let\makelabel\descriptionlabel}}
{\endlist}
\newcommand*\descriptionlabel[1]{\hspace\labelsep
\normalfont\bfseries #1}
\if@titlepage
\newenvironment{abstract}{%
\titlepage
\null\vfil
\@beginparpenalty\@lowpenalty
\begin{center}%
\bfseries \abstractname
\@endparpenalty\@M
\end{center}}%
{\par\vfil\null\endtitlepage}
\else
\newenvironment{abstract}{%
\if@twocolumn
\section*{\abstractname}%
\else
\small
\begin{center}%
{\bfseries \abstractname\vspace{-.5em}\vspace{\z@}}%
\end{center}%
\quotation
\fi}
{\if@twocolumn\else\endquotation\fi}
\fi
\newenvironment{verse}
{\let\\\@centercr
\list{}{\itemsep \z@
\itemindent -1.5em%
\listparindent\itemindent
\rightmargin \leftmargin
\advance\leftmargin 1.5em}%
\item\relax}
{\endlist}
\newenvironment{quotation}
{\list{}{\listparindent 1.5em%
\itemindent \listparindent
\rightmargin \leftmargin
\parsep \z@ \@plus\p@}%
\item\relax}
{\endlist}
\newenvironment{quote}
{\list{}{\rightmargin\leftmargin}%
\item\relax}
{\endlist}
\if@compatibility
\newenvironment{titlepage}
{%
\if@twocolumn
\@restonecoltrue\onecolumn
\else
\@restonecolfalse\newpage
\fi
\thispagestyle{empty}%
\setcounter{page}\z@
}%
{\if@restonecol\twocolumn \else \newpage \fi
}
\else
\newenvironment{titlepage}
{%
\if@twocolumn
\@restonecoltrue\onecolumn
\else
\@restonecolfalse\newpage
\fi
\thispagestyle{empty}%
\setcounter{page}\@ne
}%
{\if@restonecol\twocolumn \else \newpage \fi
\if@twoside\else
\setcounter{page}\@ne
\fi
}
\fi
\newcommand\appendix{\par
\setcounter{section}{0}%
\setcounter{subsection}{0}%
\gdef\thesection{\@Alph\c@section}}
\setlength\arraycolsep{5\p@}
\setlength\tabcolsep{6\p@}
\setlength\arrayrulewidth{.4\p@}
\setlength\doublerulesep{2\p@}
\setlength\tabbingsep{\labelsep}
\skip\@mpfootins = \skip\footins
\setlength\fboxsep{3\p@}
\setlength\fboxrule{.4\p@}
\renewcommand \theequation {\@arabic\c@equation}
\newcounter{figure}
\renewcommand \thefigure {\@arabic\c@figure}
\def\fps@figure{tbp}
\def\ftype@figure{1}
\def\ext@figure{lof}
\def\fnum@figure{\figurename\nobreakspace\thefigure}
\newenvironment{figure}
{\@float{figure}}
{\end@float}
\newenvironment{figure*}
{\@dblfloat{figure}}
{\end@dblfloat}
\newcounter{table}
\renewcommand\thetable{\@arabic\c@table}
\def\fps@table{tbp}
\def\ftype@table{2}
\def\ext@table{lot}
\def\fnum@table{\tablename\nobreakspace\thetable}
\newenvironment{table}
{\@float{table}}
{\end@float}
\newenvironment{table*}
{\@dblfloat{table}}
{\end@dblfloat}
\newlength\abovecaptionskip
\newlength\belowcaptionskip
\setlength\abovecaptionskip{10\p@}
\setlength\belowcaptionskip{0\p@}
\long\def\@makecaption#1#2{%
\vskip\abovecaptionskip
\sbox\@tempboxa{#1: #2}%
\ifdim \wd\@tempboxa >\hsize
#1: #2\par
\else
\global \@minipagefalse
\hb@xt@\hsize{\hfil\box\@tempboxa\hfil}%
\fi
\vskip\belowcaptionskip}
\DeclareOldFontCommand{\rm}{\normalfont\rmfamily}{\mathrm}
\DeclareOldFontCommand{\sf}{\normalfont\sffamily}{\mathsf}
\DeclareOldFontCommand{\tt}{\normalfont\ttfamily}{\mathtt}
\DeclareOldFontCommand{\bf}{\normalfont\bfseries}{\mathbf}
\DeclareOldFontCommand{\it}{\normalfont\itshape}{\mathit}
\DeclareOldFontCommand{\sl}{\normalfont\slshape}{\@nomath\sl}
\DeclareOldFontCommand{\sc}{\normalfont\scshape}{\@nomath\sc}
\DeclareRobustCommand*\cal{\@fontswitch\relax\mathcal}
\DeclareRobustCommand*\mit{\@fontswitch\relax\mathnormal}
\newcommand\@pnumwidth{1.55em}
\newcommand\@tocrmarg{2.55em}
\newcommand\@dotsep{4.5}
\setcounter{tocdepth}{3}
\newcommand\tableofcontents{%
\section*{\contentsname
\@mkboth{%
\MakeUppercase\contentsname}{\MakeUppercase\contentsname}}%
\@starttoc{toc}%
}
\newcommand*\l@part[2]{%
\ifnum \c@tocdepth >-2\relax
\addpenalty\@secpenalty
\addvspace{2.25em \@plus\p@}%
\setlength\@tempdima{3em}%
\begingroup
\parindent \z@ \rightskip \@pnumwidth
\parfillskip -\@pnumwidth
{\leavevmode
\large \bfseries #1\hfil \hb@xt@\@pnumwidth{\hss #2}}\par
\nobreak
\if@compatibility
\global\@nobreaktrue
\everypar{\global\@nobreakfalse\everypar{}}%
\fi
\endgroup
\fi}
\newcommand*\l@section[2]{%
\ifnum \c@tocdepth >\z@
\addpenalty\@secpenalty
\addvspace{1.0em \@plus\p@}%
\setlength\@tempdima{1.5em}%
\begingroup
\parindent \z@ \rightskip \@pnumwidth
\parfillskip -\@pnumwidth
\leavevmode \bfseries
\advance\leftskip\@tempdima
\hskip -\leftskip
#1\nobreak\hfil \nobreak\hb@xt@\@pnumwidth{\hss #2}\par
\endgroup
\fi}
\newcommand*\l@subsection{\@dottedtocline{2}{1.5em}{2.3em}}
\newcommand*\l@subsubsection{\@dottedtocline{3}{3.8em}{3.2em}}
\newcommand*\l@paragraph{\@dottedtocline{4}{7.0em}{4.1em}}
\newcommand*\l@subparagraph{\@dottedtocline{5}{10em}{5em}}
\newcommand\listoffigures{%
\section*{\listfigurename}%
\@mkboth{\MakeUppercase\listfigurename}%
{\MakeUppercase\listfigurename}%
\@starttoc{lof}%
}
\newcommand*\l@figure{\@dottedtocline{1}{1.5em}{2.3em}}
\newcommand\listoftables{%
\section*{\listtablename}%
\@mkboth{%
\MakeUppercase\listtablename}%
{\MakeUppercase\listtablename}%
\@starttoc{lot}%
}
\let\l@table\l@figure
\newdimen\bibindent
\setlength\bibindent{1.5em}
\newenvironment{thebibliography}[1]
{\section*{\refname}%
\@mkboth{\MakeUppercase\refname}{\MakeUppercase\refname}%
\list{\@biblabel{\@arabic\c@enumiv}}%
{\settowidth\labelwidth{\@biblabel{#1}}%
\leftmargin\labelwidth
\advance\leftmargin\labelsep
\@openbib@code
\usecounter{enumiv}%
\let\p@enumiv\@empty
\renewcommand\theenumiv{\@arabic\c@enumiv}}%
\sloppy
\clubpenalty4000
\@clubpenalty \clubpenalty
\widowpenalty4000%
\sfcode`\.\@m}
{\def\@noitemerr
{\@latex@warning{Empty `thebibliography' environment}}%
\endlist}
\newcommand\newblock{\hskip .11em\@plus.33em\@minus.07em}
\let\@openbib@code\@empty
\newenvironment{theindex}
{\if@twocolumn
\@restonecolfalse
\else
\@restonecoltrue
\fi
\twocolumn[\section*{\indexname}]%
\@mkboth{\MakeUppercase\indexname}%
{\MakeUppercase\indexname}%
\thispagestyle{plain}\parindent\z@
\parskip\z@ \@plus .3\p@\relax
\columnseprule \z@
\columnsep 35\p@
\let\item\@idxitem}
{\if@restonecol\onecolumn\else\clearpage\fi}
\newcommand\@idxitem{\par\hangindent 40\p@}
\newcommand\subitem{\@idxitem \hspace*{20\p@}}
\newcommand\subsubitem{\@idxitem \hspace*{30\p@}}
\newcommand\indexspace{\par \vskip 10\p@ \@plus5\p@ \@minus3\p@\relax}
\renewcommand\footnoterule{%
\kern-3\p@
\hrule\@width.4\columnwidth
\kern2.6\p@}
\newcommand\@makefntext[1]{%
\parindent 1em%
\noindent
\hb@xt@1.8em{\hss\@makefnmark}#1}
\newcommand\contentsname{Contents}
\newcommand\listfigurename{List of Figures}
\newcommand\listtablename{List of Tables}
\newcommand\refname{References}
\newcommand\indexname{Index}
\newcommand\figurename{Figure}
\newcommand\tablename{Table}
\newcommand\partname{Part}
\newcommand\appendixname{Appendix}
\newcommand\abstractname{}
\def\today{\ifcase\month\or
January\or February\or March\or April\or May\or June\or
July\or August\or September\or October\or November\or December\fi
\space\number\day, \number\year}
\setlength\columnsep{10\p@}
\setlength\columnseprule{0\p@}
\pagestyle{plain}
\pagenumbering{arabic}
% === ARLIMS adaptions following ============================================
\pagestyle{empty}
\setlength{\oddsidemargin}{1.5 cm}
\setlength{\evensidemargin}{-0.5 cm}
\setlength{\textheight}{22 cm}
\setlength{\textwidth}{15 cm}
\setlength{\topmargin}{0 cm}
% --- special indexing to cope with the list of articles
\newif\ifIndexMade
\def\makeartIndex{%
\protected@write1{}{\string\global\string\IndexMadetrue}
\newwrite\@artIndexfile
\immediate\openout\@artIndexfile=\jobname.adx
\def\artIndex{\@bsphack\begingroup\@sanitize\@wrartIndex}
\typeout{Writing artIndex file \jobname.adx}%
\let\makeartIndex\@empty
}
\def\@wrartIndex#1{%
\protected@write\@artIndexfile{}{%
\string\artIndexentry{\factTitle}%
{\thepage}%
{\string\last#1}%
{\factAuthor\factAuthorB\factAuthorC\factAuthorD}}%
\endgroup%
\@esphack%
}
\def\artIndex{\@bsphack\begingroup \@sanitize\@artIndex}
\def\@artIndex#1{\endgroup\@esphack}
\def\artIndexLast#1{%
\protected@write1{}{\string\gdef\string\last#1{\thepage}}%
}
% --- to set up the headers permanently
\newcommand{\markperm}[2]{%
\global\def\@evenhead{\arabic{page}\hfil{#1}}%
\global\def\@oddhead{{#2}\hfil\arabic{page}}
}
% (yes, I know I've hardwired arabic page numbering)
% --- to set up the headers with a once off first
\newcommand{\markonce}[3]{%
\global\def\@evenhead{\arabic{page}\hfil{#1}\markperm{#2}{#3}}%
\global\def\@oddhead{{#1}\hfil\arabic{page}\markperm{#2}{#3}}%
}
%% Put a line after the abstract %%
\newcommand{\drawline}{%
\vspace*{1mm}
\begin{center}
\rule{\textwidth}{0.2mm}
\end{center}
\vspace*{1mm}
}
\newcommand{\reff}[1]{(\ref{#1})}
% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
% Define the author and title commands. Bascially each just stuffs
% the argument into a storage command. I need to define the blank
% storage commands because I am using the "renewcomand", since I
% need to do this for each article.
\def\author{}
\def\institute{}
\def\factTitle{}
\def\factAuthor{}
\def\factAuthorB{}
\def\factAuthorC{}
\def\factAuthorD{}
\def\factInstitute{}
\def\factInstituteB{}
\def\factInstituteC{}
\def\factInstituteD{}
\def\footInst{}
\def\footInstB{}
\def\footInstC{}
\def\footInstD{}
\def\footAuthorInst{}
\def\footAuthorInstB{}
\def\footAuthorInstC{}
\def\footAuthorInstD{}
\renewcommand{\author}[2][]{
\renewcommand{\footAuthorInst}{#1}
\renewcommand{\factAuthor}{#2}
}
\newcommand{\authorB}[2][]{
\renewcommand{\footAuthorInstB}{#1}
\renewcommand{\factAuthorB}{#2}
}
\newcommand{\authorC}[2][]{
\renewcommand{\footAuthorInstC}{#1}
\renewcommand{\factAuthorC}{#2}
}
\newcommand{\authorD}[2][]{
\renewcommand{\footAuthorInstD}{#1}
\renewcommand{\factAuthorD}{#2}
}
\renewcommand{\institute}[2][]{
\renewcommand{\footInst}{#1}
\renewcommand{\factInstitute}{#2}
}
\newcommand{\instituteB}[2][*]{
\renewcommand{\footInstB}{#1}
\renewcommand{\factInstituteB}{#2}
}
\newcommand{\instituteC}[2][**]{
\renewcommand{\footInstC}{#1}
\renewcommand{\factInstituteC}{#2}
}
\newcommand{\instituteD}[2][***]{
\renewcommand{\footInstD}{#1}
\renewcommand{\factInstituteD}{#2}
}
\def\nameTag{}
\renewcommand{\title}[1]{
\renewcommand{\factTitle}{#1}
}
\newcommand{\iimsaddress}{
\begin{center}
\textit{Institute of Information \& Mathematical Sciences\\
Massey University at Albany, Auckland, New Zealand.}
\end{center}
}
\newcommand{\maketitle}[1][\factTitle]{
\begin{center}
\markonce{
\parbox{10cm}{\small
\textit{The Python Papers Source Codes,} Vol.~1 (2009)\\[1mm]
Available online at http://ojs.pythonpapers.org/index.php/tppsc/issue/view/13
}
}
{\factAuthor\factAuthorB\factAuthorC\factAuthorD}
{#1}
\vspace*{1cm}
\begin{minipage}{12cm}
\begin{center}
{\bfseries\Large\factTitle\par}
\vspace*{2mm}
\end{center}
\end{minipage}
\vspace*{3mm}
\textsc{%
\factAuthor $^{\footAuthorInst}$%
\factAuthorB $^{\footAuthorInstB}$%
\factAuthorC $^{\footAuthorInstC}$%
\factAuthorD $^{\footAuthorInstD}$%
}
\vspace*{1mm}
$^{\footInst}$\textit{\factInstitute}
$^{\footInstB}$\textit{\factInstituteB}
$^{\footInstC}$\textit{\factInstituteC}
$^{\footInstD}$\textit{\factInstituteD}
\artIndex{\nameTag}
\end{center}
}
% calculate the margins so that the text on opposite sides
% of the same sheet is lined up at the edges
% ok, its a fudge, but you can change the oddsidemargin
% to shift both sides in sync.
\oddsidemargin1cm
\evensidemargin\paperwidth
\addtolength{\evensidemargin}{-\oddsidemargin}
\addtolength{\evensidemargin}{-205mm}
% --- turn off a few things -------------------------------------------------
\renewcommand{\markboth}[2]{}
\renewcommand{\markright}[1]{}
\newcommand{\markheading}[1]{}
\usepackage{amsmath,amsfonts,dsfont}
\usepackage{amsthm}
\newif\ifCombinedRlims
% === end of ARLIMS adaptions ===============================================
\if@twoside
\else
\raggedbottom
\fi
\if@twocolumn
\twocolumn
\sloppy
\flushbottom
\else
\onecolumn
\fi
\endinput
%%
%% End of file `arlims.cls'.

View File

@@ -0,0 +1,76 @@
"""
The Beer Distribution Problem for the PuLP Modeller
Authors: Antony Phillips, Dr Stuart Mitchell 2007
"""
# Import PuLP modeler functions
import pulp
# Creates a list of all the supply nodes
warehouses = ["A", "B"]
# Creates a dictionary for the number of units of supply for each supply node
supply = {"A": 1000,
"B": 4000}
# Creates a list of all demand nodes
bars = ["1", "2", "3", "4", "5"]
# Creates a dictionary for the number of units of demand for each demand node
demand = {"1":500,
"2":900,
"3":1800,
"4":200,
"5":700,}
# Creates a list of costs of each transportation path
costs = [ #Bars
#1 2 3 4 5
[2,4,5,2,1],#A Warehouses
[3,1,3,2,3] #B
]
# The cost data is made into a dictionary
costs = pulp.makeDict([warehouses, bars], costs,0)
# Creates the 'prob' variable to contain the problem data
prob = pulp.LpProblem("Beer Distribution Problem", pulp.LpMinimize)
# Creates a list of tuples containing all the possible routes for transport
routes = [(w,b) for w in warehouses for b in bars]
# A dictionary called x is created to contain quantity shipped on the routes
x = pulp.LpVariable.dicts("route", (warehouses, bars),
lowBound = 0
cat = pulp.LpInteger)
# The objective function is added to 'prob' first
prob += sum([x[w][b]*costs[w][b] for (w,b) in routes]), \
"Sum_of_Transporting_Costs"
# Supply maximum constraints are added to prob for each supply node (warehouse)
for w in warehouses:
prob += sum([x[w][b] for b in bars]) <= supply[w], \
"Sum_of_Products_out_of_Warehouse_%s"%w
# Demand minimum constraints are added to prob for each demand node (bar)
for b in bars:
prob += sum([x[w][b] for w in warehouses]) >= demand[b], \
"Sum_of_Products_into_Bar%s"%b
# The problem data is written to an .lp file
prob.writeLP("BeerDistributionProblem.lp")
# The problem is solved using PuLP's choice of Solver
prob.solve()
# The status of the solution is printed to the screen
print "Status:", pulp.LpStatus[prob.status]
# Each of the variables is printed with it's resolved optimum value
for v in prob.variables():
print v.name, "=", v.varValue
# The optimised objective function value is printed to the screen
print "Total Cost of Transportation = ", prob.objective.value()

View File

@@ -0,0 +1,50 @@
"""
A set partitioning model of a wedding seating problem
Authors: Stuart Mitchell 2009
"""
import pulp
max_tables = 5
max_table_size = 4
guests = 'A B C D E F G I J K L M N O P Q R'.split()
def happiness(table):
"""
Find the happiness of the table
- by calculating the maximum distance between the letters
"""
return abs(ord(table[0]) - ord(table[-1]))
#create list of all possible tables
possible_tables = [tuple(c) for c in pulp.allcombinations(guests,
max_table_size)]
#create a binary variable to state that a table setting is used
x = pulp.LpVariable.dicts('table', possible_tables,
lowBound = 0,
upBound = 1,
cat = pulp.LpInteger)
seating_model = pulp.LpProblem("Wedding Seating Model", pulp.LpMinimize)
seating_model += sum([happiness(table) * x[table] for table in possible_tables])
#specify the maximum number of tables
seating_model += sum([x[table] for table in possible_tables]) <= max_tables, \
"Maximum_number_of_tables"
#A guest must seated at one and only one table
for guest in guests:
seating_model += sum([x[table] for table in possible_tables
if guest in table]) == 1, "Must_seat_%s"%guest
seating_model.solve()
print "The choosen tables are out of a total of %s:"%len(possible_tables)
for table in possible_tables:
if x[table].value() == 1.0:
print table

View File

@@ -0,0 +1,38 @@
"""
Example problem file that solves the whiskas blending problem
"""
import pulp
#initialise the model
whiskas_model = pulp.LpProblem('The Whiskas Problem', pulp.LpMinimize)
# make a list of ingredients
ingredients = ['chicken', 'beef', 'mutton', 'rice', 'wheat', 'gel']
# create a dictionary of pulp variables with keys from ingredients
# the default lower bound is -inf
x = pulp.LpVariable.dict('x_%s', ingredients, lowBound =0)
# cost data
cost = dict(zip(ingredients, [0.013, 0.008, 0.010, 0.002, 0.005, 0.001]))
# create the objective
whiskas_model += sum( [cost[i] * x[i] for i in ingredients])
# ingredient parameters
protein = dict(zip(ingredients, [0.100, 0.200, 0.150, 0.000, 0.040, 0.000]))
fat = dict(zip(ingredients, [0.080, 0.100, 0.110, 0.010, 0.010, 0.000]))
fibre = dict(zip(ingredients, [0.001, 0.005, 0.003, 0.100, 0.150, 0.000]))
salt = dict(zip(ingredients, [0.002, 0.005, 0.007, 0.002, 0.008, 0.000]))
#note these are constraints and not an objective as there is a equality/inequality
whiskas_model += sum([protein[i]*x[i] for i in ingredients]) >= 8.0
whiskas_model += sum([fat[i]*x[i] for i in ingredients]) >= 6.0
whiskas_model += sum([fibre[i]*x[i] for i in ingredients]) <= 2.0
whiskas_model += sum([salt[i]*x[i] for i in ingredients]) <= 0.4
#problem is then solved with the default solver
whiskas_model.solve()
#print the result
for ingredient in ingredients:
print 'The mass of %s is %s grams per can'%(ingredient,
x[ingredient].value())

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -0,0 +1,97 @@
% This file was created with JabRef 2.5.
% Encoding: ISO8859_1
@InProceedings{ flopc,
title = "FLOPC++ An Algebraic Modeling Language Embedded in C++",
booktitle = "Operations Research Proceedings 2006",
series = "Operations Research Proceedings",
author = "Tim Helge Hultberg",
editor = "Karl-Heinz Waldmann and Ulrike M. Stocker",
publisher = "Springer Berlin Heidelberg",
location = "Karlsruhe",
pages = "187--190",
number = "6",
year = "2006",
abstract = "FLOPC++ is an open source algebraic modeling language implemented as a C++ class library. It allows linear optimization problems to be modeled in a declarative style, similar to algebraic modeling languages, such as GAMS and AMPL, within a C++ program. The project is part of COmputational INfrastructure for Operations Research (COIN-OR) and uses its Open Solver Interface (OSI) to achieve solver independence."
}
@Misc{ gams,
author = "A. Brooke and D. Kendrick and A. Meeraus",
title = "GAMS: A User's Guide",
text = "A. Brooke, D. Kendrick, and A. Meeraus. GAMS: A User's Guide, Release 2.25. The Scientific Press, 1992.",
year = "1992",
url = "citeseer.ist.psu.edu/brooke92gams.html"
}
@Article{ ampl,
title = "AMPL: A Mathematical Programming Language",
author = "Robert Fourer and David M. Gay and Brian W. Kernighan",
journal = "Management Science",
pages = "519--554",
volume = "36",
year = "1990"
}
@Article{ columngeneration,
title = "Selected Topics in Column Generation ",
author = "Marco E. L{\"u}bbecke and Jacques Desrosiers",
journal = "OPERATIONS RESEARCH",
pages = "1007--1023",
volume = "53",
number = "6",
month = "November- December",
year = "2005",
doi = "10.1287/opre.1050.0234"
}
@Electronic{ pulpwiki,
title = "Pulp-or wiki",
author = "Stuart A Mitchell and Antony Phillips",
url = "http://pulp-or.googlecode.com"
}
@Electronic{ scienceofbetter,
title = "Operations Research: The Science of Better",
url = "http://www.scienceofbetter.org/"
}
@Article{ coin-or,
title = "The Common Optimization INterface for Operations Research",
author = "Robin Lougee-Heimer",
journal = "IBM Journal of Research and Development",
pages = "57--66",
volume = "47",
number = "1",
month = "January",
year = "2003"
}
@Electronic{ glpk,
title = "GNU Linear Programming Kit",
url = "http://www.gnu.org/software/glpk/glpk.html"
}
@Electronic{ cplex,
title = "Cplex Website",
url = "http://www.ilog.com/products/cplex/"
}
@Electronic{ gurobi,
title = "Gurobi Website",
url = "http://www.gurobi.com/"
}
@Electronic{ coopr,
title = "Coopr: A COmmon Optimization Python Repository",
author = "William E. Hart",
url = "https://software.sandia.gov/trac/coopr"
}
@InProceedings{ pyomo,
title = "Python Optimization Modeling Objects (Pyomo)",
booktitle = "Proc INFORMS Computing Society Conference",
author = "William Hart",
year = "2009",
url = "http://www.optimization-online.org/DB_HTML/2008/09/2095.html"
}

BIN
doc/KiwiPycon.odp Normal file

Binary file not shown.

BIN
doc/KiwiPycon.pdf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
doc/PulpOptimisation.odp Normal file

Binary file not shown.

BIN
doc/PulpOptimisation.pdf Normal file

Binary file not shown.

Binary file not shown.

BIN
doc/pulp.pdf Normal file

Binary file not shown.

5
doc/source/AUTHORS.txt Normal file
View File

@@ -0,0 +1,5 @@
+ Stuart Mitchell (s.mitchell@auckland.ac.nz)
+ Anita Kean
+ Andrew Mason
+ Michael O\'Sullivan
+ Antony Phillips

View File

@@ -0,0 +1,336 @@
A Blending Problem
===================
Problem Description
-------------------
.. image:: images/whiskas_label.jpg
Whiskas cat food, shown above, is manufactured by Uncle Bens.
Uncle Bens 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.
.. image:: images/whiskas_blend.jpg
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. The contributions (in grams) per
gram of ingredient are given in the table below.
============ ========= ========= ======== =======
Stuff Protein Fat Fibre Salt
============ ========= ========= ======== =======
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.002
Wheat bran 0.040 0.010 0.150 0.008
============ ========= ========= ======== =======
Simplified Formulation
~~~~~~~~~~~~~~~~~~~~~~
First we will consider a simplified problem to build a simple Python model.
Identify the Decision Variables
+++++++++++++++++++++++++++++++++++
Assume Whiskas want to make their cat food out of just two ingredients:
Chicken and Beef. We will first define our decision variables:
.. math::
x_1 &= \text{ percentage of chicken meat in a can of cat food }\\
x_2 &= \text{ percentage of beef used in a can of cat food }
The limitations on these variables (greater than zero) must be noted but
for the Python implementation, they are not entered or listed separately or with the other constraints.
Formulate the Objective Function
++++++++++++++++++++++++++++++++
The objective function becomes:
.. math:: \textbf{ min } 0.013 x_1 + 0.008 x_2
The Constraints
+++++++++++++++
The constraints on the variables are that they must sum to 100 and that the nutritional requirements are met:
.. math::
1.000 x_1 + 1.000 x_2 &= 100.0\\
0.100 x_1 + 0.200 x_2 &\ge 8.0\\
0.080 x_1 + 0.100 x_2 &\ge 6.0\\
0.001 x_1 + 0.005 x_2 &\le 2.0\\
0.002 x_1 + 0.005 x_2 &\le 0.4\\
Solution to Simplified Problem
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To obtain the solution to this Linear Program, we can write a short
program in Python to call PuLP's modelling functions, which will then
call a solver. This will explain step-by-step how to write this Python
program. It is suggested that you repeat the exercise yourself. The code
for this example is found in `WhiskasModel1.py <https://projects.coin-or.org/PuLP/browser/trunk/examples/WhiskasModel1.py?format=txt>`_
The start of the your file should then be headed with a short commenting section outlining the purpose of the program. For example:
.. literalinclude:: ../../../examples/WhiskasModel1.py
:lines: 1-5
Then you will import PuLP's functions for use in your code:
.. literalinclude:: ../../../examples/WhiskasModel1.py
:lines: 7-8
A variable called ``prob`` (although its name is not important) is
created using the :class:`~pulp.LpProblem` function. It has two parameters, the first
being the arbitrary name of this problem (as a string), and the second
parameter being either ``LpMinimize`` or ``LpMaximize`` depending on the
type of LP you are trying to solve:
.. literalinclude:: ../../../examples/WhiskasModel1.py
:lines: 10-11
The problem variables ``x1`` and ``x2`` are created using the
:class:`~pulp.LpVariable` class. It has four parameters, the first is the
arbitrary name of what this variable represents, the second is the lower
bound on this variable, the third is the upper bound, and the fourth
is essentially the type of data (discrete or continuous). The options
for the fourth parameter are ``LpContinuous`` or ``LpInteger``, with the
default as ``LpContinuous``. If we were modelling the number of cans
to produce, we would need to input ``LpInteger`` since it is discrete
data. The bounds can be entered directly as a number, or ``None`` to
represent no bound (i.e. positive or negative infinity), with ``None``
as the default. If the first few parameters are entered and the rest
are ignored (as shown), they take their default values. However, if you
wish to specify the third parameter, but you want the second to be the
default value, you will need to specifically set the second parameter as
it's default value. i.e you cannot leave a parameter entry blank.
e.g::
LpVariable("example", None, 100)
or::
LpVariable("example", upBound = 100)
To explicitly create the two variables needed for this problem:
.. literalinclude:: ../../../examples/WhiskasModel1.py
:lines: 13-15
The variable ``prob`` now begins collecting problem data with the
``+=`` operator. The objective function is logically entered first, with
an important comma ``,`` at the end of the statement and a short string
explaining what this objective function is:
.. literalinclude:: ../../../examples/WhiskasModel1.py
:lines: 17-18
The constraints are now entered (Note: any "non-negative"
constraints were already included when defining the variables). This is
done with the '+=' operator again, since we are adding more data to the
``prob`` variable. The constraint is logically entered after this, with a
comma at the end of the constraint equation and a brief description of
the cause of that constraint:
.. literalinclude:: ../../../examples/WhiskasModel1.py
:lines: 20-25
Now that all the problem data is entered, the :meth:`~pulp.LpProblem.writeLP` function
can be used to copy this information into a .lp file into the directory
that your code-block is running from. Once your code runs successfully, you
can open this .lp file with a text editor to see what the above steps were
doing. You will notice that there is no assignment operator (such as an
equals sign) on this line. This is because the function/method called
:meth:`~pulp.LpProblem.writeLP` is being performed to the
variable/object ``prob`` (and the
string ``"WhiskasModel.lp"`` is an additional parameter). The dot ``.``
between the variable/object and the function/method is important and is
seen frequently in Object Oriented software (such as this):
.. literalinclude:: ../../../examples/WhiskasModel1.py
:lines: 27-28
The LP is solved using the solver that PuLP chooses. The input
brackets after :meth:`~pulp.LpProblem.solve` are left empty in this case, however they can be
used to specify which solver to use (e.g ``prob.solve(CPLEX())`` ):
.. literalinclude:: ../../../examples/WhiskasModel1.py
:lines: 30-31
Now the results of the solver call can be displayed as output to
us. Firstly, we request the status of the solution, which can be one of
"Not Solved", "Infeasible", "Unbounded", "Undefined" or "Optimal". The
value of ``prob`` (:attr:`pulp.pulp.LpProblem.status`) is returned as an integer, which must be converted
to its significant text meaning using the
:attr:`~pulp.constants.LpStatus` dictionary. Since
:attr:`~pulp.constants.LpStatus` is a dictionary(:obj:`dict`), its input must be in square brackets:
.. literalinclude:: ../../../examples/WhiskasModel1.py
:lines: 33-34
The variables and their resolved optimum values can now be printed
to the screen.
.. literalinclude:: ../../../examples/WhiskasModel1.py
:lines: 36-38
The ``for`` loop makes ``variable`` cycle through all
the problem variable names (in this case just ``ChickenPercent`` and
``BeefPercent``). Then it prints each variable name, followed by an
equals sign, followed by its optimum value.
:attr:`~pulp.LpVariable.name` and
:attr:`~pulp.LpVariable.varValue` are
properties of the object ``variable``.
The optimised objective function value is printed to the screen,
using the value function. This ensures that the number is in the right
format to be displayed. :attr:`~pulp.LpProblem.objective` is an attribute of the object
``prob``:
.. literalinclude:: ../../../examples/WhiskasModel1.py
:lines: 40-41
Running this file should then produce the output to show that
Chicken will make up 33.33%, Beef will make up 66.67% and the
Total cost of ingredients per can is 96 cents.
Full Formulation
----------------
Now we will formulate the problem fully with
all the variables. Whilst it could be implemented into Python with
little addition to our method above, we will look at a better way which
does not mix the problem data, and the formulation as much. This will
make it easier to change any problem data for other tests. We will start
the same way by algebraically defining the problem:
#. Identify the Decision Variables
For the Whiskas Cat Food Problem the decision variables are the percentages of
the different ingredients we include in the can.
Since the can is 100g, these percentages also represent the amount in g of each
ingredient included.
We must formally define our decision variables, being sure to state the units
we are using.
.. math::
x_1 &= \text{percentage of chicken meat in a can of cat food}\\
x_2 &= \text{percentage of beef used in a can of cat food}\\
x_3 &= \text{percentage of mutton used in a can of cat food}\\
x_4 &= \text{percentage of rice used in a can of cat food}\\
x_5 &= \text{percentage of wheat bran used in a can of cat food}\\
x_6 &= \text{percentage of gel used in a can of cat food}
Note that these percentages must be between 0 and 100.
#. Formulate the Objective Function
For the Whiskas Cat Food Problem the objective is to minimise the total cost
of ingredients per can of cat food.
We know the cost per g of each ingredient. We decide the percentage of each
ingredient in the can, so we must divide by 100 and multiply by the weight of
the can in g. This will give us the weight in g of each
ingredient:
.. math::
\min \$0.013 x_1 + \$0.008 x_2 + \$0.010 x_3 + \$0.002 x_4 + \$0.005 x_5 + \$0.001 x_6
#. Formulate the Constraints
The constraints for the Whiskas Cat Food Problem are that:
* The sum of the percentages must make up the whole can (= 100%).
* The stated nutritional analysis requirements are met.
The constraint for the "whole can" is:
.. math:: x_1 + x_2 + x_3 + x_4 + x_5 +x _6 = 100
To meet the nutritional analysis requirements, we need to have at
least 8g of Protein per 100g, 6g of fat, but no more than 2g of fibre
and 0.4g of salt. To formulate these constraints we make use of the
previous table of contributions from each ingredient. This allows us
to formulate the following constraints on the total contributions of
protein, fat, fibre and salt from the ingredients:
.. math::
0.100 x_1 +0.200 x_2 +0.150 x_3 +0.000 x_4 +0.040 x_5 +0.0 x_6 0&\ge 8.0 \\
0.080 x_1 +0.100 x_2 +0.110 x_3 +0.010 x_4 +0.010 x_5 0+0.0 x_6 &\ge 6.0 \\
0.001 x_1 +0.005 x_2 +0.003 x_3 +0.100 x_4 0+0.150 x_5 +0.0 x_6 &\le 2.0 \\
0.002 x_1 +0.005 x_2 +0.007 x_3 0+0.002 x_4 +0.008 x_5 +0.0 x_6 &\le 0.4
Solution to Full Problem
~~~~~~~~~~~~~~~~~~~~~~~~
To obtain the solution to this Linear Program, we again write a
short program in Python to call PuLP's modelling functions, which will
then call a solver. This will explain step-by-step how to write this
Python program with it's improvement to the above model. It is suggested
that you repeat the exercise yourself. The code for this example is
found in the `WhiskasModel2.py <https://projects.coin-or.org/PuLP/browser/trunk/examples/WhiskasModel2.py?format=txt>`_
As with last time, it is advisable to head your file with commenting on its
purpose, and the author name and date. Importing of the PuLP functions is also done in the same way:
.. literalinclude:: ../../../examples/WhiskasModel2.py
:lines: 1-8
Next, before the ``prob`` variable or type of problem are defined,
the key problem data is entered into dictionaries. This includes the
list of Ingredients, followed by the cost of each Ingredient, and it's
percentage of each of the four nutrients. These values are clearly laid
out and could easily be changed by someone with little knowledge of
programming. The ingredients are the reference keys, with the numbers as
the data.
.. literalinclude:: ../../../examples/WhiskasModel2.py
:lines: 10-51
The ``prob`` variable is created to contain the formulation, and the
usual parameters are passed into :obj:`~pulp.LpProblem`.
.. literalinclude:: ../../../examples/WhiskasModel2.py
:lines: 53-54
A dictionary called ``ingredient_vars`` is created which contains
the LP variables, with their defined lower bound of zero. The reference
keys to the dictionary are the Ingredient names, and the data is
``Ingr_IngredientName``. (e.g. MUTTON: Ingr_MUTTON)
.. literalinclude:: ../../../examples/WhiskasModel2.py
:lines: 56-57
Since ``costs`` and ``ingredient_vars`` are now dictionaries with the
reference keys as the Ingredient names, the data can be simply extracted
with a list comprehension as shown. The :func:`~pulp.lpSum` function will add the
elements of the resulting list. Thus the objective function is simply
entered and assigned a name:
.. literalinclude:: ../../../examples/WhiskasModel2.py
:lines: 59-60
Further list comprehensions are used to define the other 5 constraints, which are also each given names describing them.
.. literalinclude:: ../../../examples/WhiskasModel2.py
:lines: 62-67
Following this, the :ref:`writeLP<writeLP>` line etc follow exactly the same as
in the simplified example.
The optimal solution is 60% Beef and 40% Gel leading to a objective
Function value of 52 cents per can.

View File

@@ -0,0 +1,62 @@
A Set Partitioning Problem
============================
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:
+ set packing - all items must be contained in zero or one partitions;
+ set covering - all items must be contained in at least one partition.
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.
.. image:: images/wedding_seating.jpg
.. raw:: html
<div xmlns:cc="http://creativecommons.org/ns#" about="http://www.flickr.com/photos/71463577@N00/3735357685"><a rel="cc:attributionURL" href="http://www.flickr.com/photos/mbphotography/">http://www.flickr.com/photos/mbphotography/</a> / <a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/2.0/">CC BY-NC-ND 2.0</a></div>
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) 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 :func:`~pulp.allcombinations` to generate a list of all
possible table seatings.
.. literalinclude:: ../../../examples/wedding.py
:lines: 20-22
Then we create a binary variable that will be 1 if the table will be in the solution, or zero otherwise.
.. literalinclude:: ../../../examples/wedding.py
:lines: 24-28
We create the :class:`~pulp.LpProblem` and then make the objective function. Note that
happiness function used in this script would be difficult to model in any other way.
.. literalinclude:: ../../../examples/wedding.py
:lines: 30-32
We specify the total number of tables allowed in the solution.
.. literalinclude:: ../../../examples/wedding.py
:lines: 34-35
This set of constraints defines the set partitioning problem by guaranteeing that a guest is allocated to
exactly one table.
.. literalinclude:: ../../../examples/wedding.py
:lines: 38-41
The full file can be found here `wedding.py <https://projects.coin-or.org/PuLP/browser/trunk/examples/wedding.py?format=txt>`_
.. literalinclude:: ../../../examples/wedding.py

View File

@@ -0,0 +1,161 @@
A Sudoku Problem formulated as an LP
====================================
Problem Description
-------------------
A `sudoku problem <http://en.wikipedia.org/wiki/Sudoku>`_ is a problem where there are is an incomplete 9x9 table of numbers which must be filled according
to several rules:
+ Within any of the 9 individual 3x3 boxes, each of the numbers 1 to 9 must be
found
+ Within any column of the 9x9 grid, each of the numbers 1 to 9 must be found
+ Within any row of the 9x9 grid, each of the numbers 1 to 9 must be found
On this page we will formulate the below problem from wikipedia to model using PuLP. Once created, our code will need little modification to solve any sudoku problem at all.
.. image:: images/wikisudokuproblem.jpg
Formulation
-----------
Identify the Decision Variables
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In order to formulate this problem as a linear program, we cannot simply create
a variable for each of the 81 squares between 1 and 9 representing the value in
that square. This is because in linear programming there is no "not equal to"
operator and so we cannot use the necessary constraints of no squares within a
box/row/column being equal in value to each other. Whilst we can ensure the sum
of all the values in a box/row/column equal 45, this will still result in many
solutions satisfying the 45 constraint but still with 2 of the same number in
the same box/row/column.
Instead, we must create 729 individual binary (0-1) problem variables. These
represent 9 problem variables per square for each of 81 squares, where the 9
variables each correspond to the number that might be in that square. The
binary nature of the variable says whether the existence of that number in that
square is true or false. Therefore, there can clearly be only 1 of the 9
variables for each square as true (1) and the other 8 must be false (0) since
only one number can be placed into any square. This will become more clear.
Formulate the Objective Function
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Interestingly, with sudoku there is no solution that is better than another
solution, since a solution by definition, must satisfy all the constraints.
Therefore, we are not really trying to minimise or maximise anything, we are
just trying to find the values on our variables that satisfy the constraints.
Therefore, whilst either :attr:`~pulp.LpMinimize` or :attr:`~pulp.LpMaximize` must be entered, it is not
important which. Similarly, the objective function can be anything, so in this
example it is simply zero.
That is we are trying to minimize zero, subject to our constraints (meeting the constraints being the important part)
Formulate the Constraints
~~~~~~~~~~~~~~~~~~~~~~~~~
These are simply the known constraints of a sudoku problem plus the constraints
on our own created variables we have used to express the features of the
problem:
+ The values in the squares in any row must be each of 1 to 9
+ The values in the squares in any column must be each of 1 to 9
+ The values in the squares in any box must be each of 1 to 9
(a box is one of the 9 non-overlapping 3x3 grids within the overall 9x9 grid)
+ There must be only one number within any square (seems logically obvious, but
it is important to our formulation to ensure because of our variable choices)
+ The starting sudoku numbers must be in those same places in the final
solution (this is a constraint since these numbers are not changeable in the
actual problem, whereas we can control any other numbers. If none or very few
starting numbers were present, the sudoku problem would have a very large
number of feasible solutions, instead of just one)
Solution
--------
The introductory commenting and import statement are entered
.. literalinclude:: ../../../examples/Sudoku1.py
:lines: 1-8
In the unique case of the sudoku problem, the row names, column names and variable option values are all the exact same list of numbers (as strings) from "1" to "9".
.. literalinclude:: ../../../examples/Sudoku1.py
:lines: 10-16
A list called `Boxes` is created with 9 elements, each being another list. These 9 lists correspond to each of the 9 boxes, and each of the lists contains tuples as the elements with the row and column indices for each square in that box. Explicitly entering the values in a similar way to the following would have had the same effect (but would have been a waste of time):
.. literalinclude:: ../../../examples/Sudoku1.py
:lines: 18-22
Therefore, Boxes[0] will return a list of tuples containing the locations of each of the 9 squares in the first box.
The prob variable is created to contain the problem data. LpMinimize has the same effect as LpMaximise in this case.
.. literalinclude:: ../../../examples/Sudoku1.py
:lines: 24-25
The 729 problem variables are created since the `(Vals,Rows,Cols)` creates a variable for each combination of value, row and column. An example variable would be: Choice_4_2_9, and it is defined to be a binary variable (able to take only the integers 1 or 0. If Choice_4_2_9 was 1, it would mean the number 4 was present in the square situated in row 2, column 9. (If it was 0, it would mean there was not a 4 there)
.. literalinclude:: ../../../examples/Sudoku1.py
:lines: 27-28
As explained above, the objective function (what we try to change using the problem variables) is simply zero (constant) since we are only concerned with any variable combination that can satisfy the constraints.
.. literalinclude:: ../../../examples/Sudoku1.py
:lines: 30-31
Since there are 9 variables for each square, it is important to specify that only exactly one of them can take the value of "1" (and the rest are "0"). Therefore, the below code reads: for each of the 81 squares, the sum of all the 9 variables (each representing a value that could be there) relating to that particular square must equal 1.
.. literalinclude:: ../../../examples/Sudoku1.py
:lines: 33-36
These constraints ensure that each number (value) can only occur once in each row, column and box.
.. literalinclude:: ../../../examples/Sudoku1.py
:lines: 38-47
The starting numbers are entered as constraints i.e a "5" in row "1" column "1" is true.
.. literalinclude:: ../../../examples/Sudoku1.py
:lines: 49-78
The problem is written to an LP file, solved using CPLEX (due to CPLEX's simple output) and the solution status is printed to the screen
.. literalinclude:: ../../../examples/Sudoku1.py
:lines: 80-87
Instead of printing out all 729 of the binary problem variables and their respective values, it is most meaningful to draw the solution in a text file. The code also puts lines inbetween every third row and column to make the solution easier to read. The sudokuout.txt file is created in the same folder as the .py file.
.. literalinclude:: ../../../examples/Sudoku1.py
:lines: 92-108
A note of the location of the solution is printed to the solution
.. literalinclude:: ../../../examples/Sudoku1.py
:lines: 110-111
The full file above is given provided `Sudoku1.py <https://projects.coin-or.org/PuLP/browser/trunk/examples/Sudoku1.py?format=txt>`_
The final solution should be the following:
.. image:: images/wikisudokusolution.jpg
Extra for Experts
-----------------
In the above formulation we did not consider the fact that there may be multiple solutions if the sudoku problem is not well defined.
We can make our code return all the solutions by editing our code as shown after the `prob.writeLP` line. Essentially we are just looping over the solve statement, and each time after a successful solve, adding a constraint that the same solution cannot be used again. When there are no more solutions, our program ends.
.. literalinclude:: ../../../examples/Sudoku2.py
:lines: 82-115
The full file using this is available `Sudoku2.py <https://projects.coin-or.org/PuLP/browser/trunk/examples/Sudoku2.py?format=txt>`_. When using this code for sudoku problems with a large number of solutions, it could take a very long time to solve them all. To create sudoku problems with multiple solutions from unique solution sudoku problem, you can simply delete a starting number constraint. You may find that deleting several constraints will still lead to a single optimal solution but the removal of one particular constraint leads to a sudden dramatic increase in the number of solutions.

View File

@@ -0,0 +1,422 @@
A Transportation Problem
========================
Problem Description
-------------------
A boutique brewery has two warehouses from which it distributes beer to five
carefully chosen bars. At the start of every week, each bar sends an order to
the brewerys head office for so many crates of beer, which is then dispatched
from the appropriate warehouse to the bar. The brewery would like to have an
interactive computer program which they can run week by week to tell them which
warehouse should supply which bar so as to minimize the costs of the whole
operation. For example, suppose that at the start of a given week the brewery
has 1000 cases at warehouse A, and 4000 cases at warehouse B, and that the bars
require 500, 900, 1800, 200, and 700 cases respectively. Which warehouse should
supply which bar?
Formulation
-----------
For transportation problems, using a graphical representation of the problem
is often helpful during formulation. Here is a graphical representation of The
Beer Distribution Problem.
.. image:: images/brewery_nodes.jpg
Identify the Decision Variables
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In transportation problems we are deciding how to transport goods from their
supply nodes to their demand nodes. The decision variables are the Arcs
connecting these nodes, as shown in the diagram below. We are deciding how many
crates of beer to transport from each warehouse to each pub.
.. image:: images/brewery_arcs.jpg
* A1 = number of crates of beer to ship from Warehouse A to Bar 1
* A5 = number of crates of beer to ship from Warehouse A to Bar 5
* B1 = number of crates of beer to ship from Warehouse B to Bar 1
* B5 = number of crates of beer to ship from Warehouse B to Bar 5
Let:
.. math::
W &= \{A,B\} \\
B &= \{1, 2, 3, 4, 5 \} \\
x_{(w,b)} &\ge 0 \ldots \forall w \in W, b \in B \\
x_{(w,b)} & \in \mathbb{Z}^+ \ldots \forall w \in W, b \in B \\
The lower bound on the variables is Zero, and the values must all be Integers
(since the number of crates cannot be negative or fractional). There is no
upper bound.
Formulate the Objective Function
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The objective function has been loosely defined as cost. The problem can only be
formulated as a linear program if the cost of transportation from warehouse to
pub is a linear function of the amounts of crates transported. Note that this is
sometimes not the case. This may be due to factors such as economies of scale or
fixed costs. For example, transporting 10 crates may not cost 10 times as much
as transporting one crate, since it may be the case that one truck can
accommodate 10 crates as easily as one. Usually in this situation there are
fixed costs in operating a truck which implies that the costs go up in jumps
(when an extra truck is required). In these situations, it is possible to model
such a cost by using zero-one integer variables: we will look at this later in
the course.
We shall assume then that there is a fixed transportation cost per crate. (If
the capacity of a truck is small compared with the number of crates that must be
delivered then this is a valid assumption). Lets assume we talk with the
financial manager, and she gives us the following transportation costs (dollars
per crate):
======================= === ===
From Warehouse to Bar A B
======================= === ===
1 2 3
2 4 1
3 5 3
4 2 2
5 1 3
======================= === ===
Minimise the Transporting Costs = Cost per crate for RouteA1 * A1 (number of crates on RouteA1)
\+ ...
\+ Cost per crate for RouteB5 * B5 (number of crates on RouteB5)
.. math::
\min \sum_{w \in W, b \in B} c_{(w,b)} x_{(w,b)}
Formulate the Constraints
~~~~~~~~~~~~~~~~~~~~~~~~~
The constraints come from considerations of supply and demand. The supply of
beer at warehouse A is 1000 cases. The total amount of beer shipped from
warehouse A cannot exceed this amount. Similarly, the amount of beer shipped
from warehouse B cannot exceed the supply of beer at warehouse B. The sum of
the values on all the arcs leading out of a warehouse, must be less than or
equal to the supply value at that warehouse:
Such that:
* A1 + A2 + A3 + A4 + A5 <= 1000
* B1 + B2 + B3 + B4 + B5 <= 4000
.. math::
\sum_{ b \in B} x_{(w,b)} <= s_w \ldots \forall w \in W
The demand for beer at bar 1 is 500 cases, so the amount of beer delivered there
must be at least 500 to avoid lost sales. Similarly, considering the amounts
delivered to the other bars must be at least equal to the demand at those bars.
Note, we are assuming there are no penalties for oversupplying bars (other than
the extra transportation cost we incur). We can _balance_ the transportation
problem to make sure that demand is met exactly - there will be more on this
later. For now, the sum of the values on all the arcs leading into a bar, must
be greater than or equal to the demand value at that bar:
* A1 + B1 >= 500
* A2 + B2 >= 900
* A3 + B3 >= 1800
* A4 + B4 >= 200
* A5 + B5 >= 700
.. math::
\sum_{ w \in W} x_{(w,b)} >= d_b \ldots \forall b \in B
Finally, we have already specified the amount of beer shipped must be
non-negative.
PuLP Model
----------
Whilst the LP as defined above could be formulated into Python code in the same
way as the `A Blending Problem` (Whiskas), for Transportation Problems, there is
a more efficient way which we will use in this course. The example file for this
problem is found in the examples directory BeerDistributionProblem.py
First, start your Python file with a heading and the import PuLP statement:
.. code-block:: python
"""
The Beer Distribution Problem for the PuLP Modeller
Authors: Antony Phillips, Dr Stuart Mitchell 2007
"""
# Import PuLP modeller functions
from pulp import *
The start of the formulation is a simple definition of the nodes and their limits/capacities. The node names are put into lists, and their associated capacities are put into dictionaries with the node names as the reference keys:
.. code-block:: python
# Creates a list of all the supply nodes
Warehouses = ["A","B"]
# Creates a dictionary for the number of units of supply for each supply node
supply = {"A": 1000,
"B": 4000}
# Creates a list of all demand nodes
Bars = ["1", "2", "3", "4", "5"]
# Creates a dictionary for the number of units of demand for each demand node
demand = {"1": 500,
"2": 900,
"3": 1800,
"4": 200,
"5": 700}
The cost data is then inputted into a list, with two sub lists: the first
containing the costs of shipping from Warehouse A, and the second containing the
costs of shipping from Warehouse B. Note here that the commenting and structure
of the code makes the data appear as a table for easy editing. The `Warehouses`
and `Bars` lists (Supply and Demand nodes) are added to make a large list (of
all nodes) and inputted into PuLPs `makeDict` function. The second parameter is
the costs list as was previously created, and the last parameter sets the
default value for an arc cost. Once the cost dictionary is created, if
`costs["A"]["1"]` is called, it will return the cost of transporting from
warehouse A to bar 1, 2. If `costs["C"]["2"]` is called, it will return 0, since
this is the defined default.
.. code-block:: python
# Creates a list of costs of each transportation path
costs = [ #Bars
#1 2 3 4 5
[2,4,5,2,1],#A Warehouses
[3,1,3,2,3] #B
]
}}}
The `prob` variable is created using the `LpProblem` function, with the usual
input parameters.
.. code-block:: python
# Creates the prob variable to contain the problem data
prob = LpProblem("Beer Distribution Problem",LpMinimize)
A list of tuples is created containing all the arcs.
.. code-block:: python
# Creates a list of tuples containing all the possible routes for transport
Routes = [(w,b) for w in Warehouses for b in Bars]
A dictionary called `route_var` is created which contains the LP variables. The
reference keys to the dictionary are the warehouse name, then the bar
name(`["A"]["2"]`) , and the data is `Route_Tuple`. (e.g. `["A"]["2"]`:
Route_A_2). The lower limit of zero is set, the upper limit of `None` is set,
and the variables are defined to be Integers.
.. code-block:: python
# A dictionary called route_vars is created to contain the referenced variables (the routes)
route_vars = LpVariable.dicts("Route",(Warehouses,Bars),0,None,LpInteger)
The objective function is added to the variable `prob` using a list
comprehension. Since `route_vars` and `costs` are now dictionaries (with further
internal dictionaries), they can be used as if they were tables, as `for (w,b)
in Routes` will cycle through all the combinations/arcs. Note that `i` and `j`
could have been used, but `w` and `b` are more meaningful.
.. code-block:: python
# The objective function is added to prob first
prob += lpSum([route_vars[w][b]*costs[w][b] for (w,b) in Routes]), "Sum of Transporting Costs"
The supply and demand constraints are added using a normal `for` loop and a list
comprehension. Supply Constraints: For each warehouse in turn, the values of the
decision variables (number of beer cases on arc) to each of the bars is summed,
and then constrained to being less than or equal to the supply max for that
warehouse. Demand Constraints: For each bar in turn, the values of the decision
variables (number on arc) from each of the warehouses is summed, and then
constrained to being greater than or equal to the demand minimum.
.. code-block:: python
# The supply maximum constraints are added to prob for each supply node (warehouse)
for w in Warehouses:
prob += lpSum([route_vars[w][b] for b in Bars]) <= supply[w], "Sum of Products out of Warehouse %s"%w
# The demand minimum constraints are added to prob for each demand node (bar)
for b in Bars:
prob += lpSum([route_vars[w][b] for w in Warehouses]) >= demand[b], "Sum of Products into Bars %s"%b
Following this is the `prob.writeLP` line, and the rest as explained in previous
examples.
You will notice that the linear programme solution was also an integer solution.
As long as Supply and Demand are integers, the linear programming solution will
always be an integer. Read about naturally integer solutions for more details.
Extensions
----------
Validation
~~~~~~~~~~
Since we have guaranteed the Supply and Demand are integer, we know that the
solution to the linear programme will be integer, so we don't need to check the
integrality of the solution.
Storage and "Buying In"
~~~~~~~~~~~~~~~~~~~~~~~
Transportation models are usually _balanced_, i.e., the total supply = the total
demand. This is because extra supply usually must be stored somewhere (with an
associated storage cost) and extra demand is usually satisfied by purchasing
extra goods from alternative sources (this is know as "buying in" extra goods)
or by substituting another product (incurring a penalty cost).
In The Beer Distribution Problem, the total supply is 5000 cases of beer, but
the total demand is only for 4100 cases. The extra supply can be sent to an
_dummy_ demand node. The cost of flow going to the dummy demand node is then the
storage cost at each of the supply nodes.
.. image:: images/extra_supply.jpg
This is added into the above model very simply. Since the objective function and
constraints all operated on the original supply, demand and cost
lists/dictionaries, the only changes that must be made to include another demand node are:
.. code-block:: python
# Creates a list of all demand nodes
Bars = ["1","2","3","4","5","D"]
# Creates a dictionary for the number of units of demand for each demand node
demand = {"1": 500,
"2": 900,
"3": 1800,
"4": 200,
"5": 700,
"D": 900}
# Creates a list of costs of each transportation path
costs = [ #Bars
#1 2 3 4 5 D
[2,4,5,2,1,0],#A Warehouses
[3,1,3,2,3,0] #B
]
The `Bars` list is expanded and the `Demand` dictionary is expanded to make the
Dummy Demand require 900 crates, to balance the problem. The cost list is also
expanded, to show the cost of "sending to the Dummy Node" which is realistically
just leaving the stock at the warehouses. This may have an associated cost which
could be entered here instead of the zeros. Note that the solution could still
be solved when there was an unbalanced excess supply.
If a transportation problem has more demand than supply, we can balance the
problem using a dummy supply node. Note that with excess demand, the problem is
"Infeasible" when unbalanced.
Assume there has been a production problem and only 4000 cases of beer could be
produced. Since the total demand is 4100, we need to get extra cases of beer from the dummy supply node.
.. image:: images/extra_demand.jpg
This dummy supply node is added in simply and logically into the `Warehouse`
list, `Supply` dictionary, and `costs` list. The Supply value is chosen to
balance the problem, and cost of transport is zero to all demand nodes.
.. code-block:: python
# Creates a list of all the supply nodes
Warehouses = ["A","B","D"]
# Creates a dictionary for the number of units of supply for each supply node
supply = {"A": 1000,
"B": 3000,
"D": 100}
# Creates a list of all demand nodes
Bars = ["1","2","3","4","5"]
# Creates a dictionary for the number of units of demand for each demand node
demand = {"1": 500,
"2": 900,
"3": 1800,
"4": 200,
"5": 700}
# Creates a list of costs of each transportation path
costs = [ #Bars
#1 2 3 4 5 D
[2,4,5,2,1], #A
[3,1,3,2,3], #B Warehouses
[0,0,0,0,0] #D
]
Presentation of Solution and Analysis
-------------------------------------
There are many ways to present the solution to The Beer Distribution Problem:
as a list of shipments, in a table, etc.
::
TRANSPORTATION SOLUTION -- Non-zero shipments
TotalCost = ____
Ship ___ crates of beer from warehouse A to pub 1
Ship ___ crates of beer from warehouse A to pub 5
Ship ___ crates of beer from warehouse B to pub 1
Ship ___ crates of beer from warehouse B to pub 2
Ship ___ crates of beer from warehouse B to pub 3
Ship ___ crates of beer from warehouse B to pub 4
This information gives rise to the following management summary:
::
The Beer Distribution Problem
Mike O'Sullivan, 1234567
We are minimising the transportation cost for a brewery operation. The brewery
transports cases of beer from its warehouses to several bars.
The brewery has two warehouses (A and B respectively) and 5 bars (1, 2, 3, 4 and
5).
The supply of crates of beer at the warehouses is:
__________
The forecasted demand (in crates of beer) at the bars is:
__________
The cost of transporting 1 crate of beer from a warehouse to a bar is given in
the following table:
__________
To minimise the transportation cost the brewery should make the following
shipments:
Ship ___ crates of beer from warehouse A to pub 1
Ship ___ crates of beer from warehouse A to pub 5
Ship ___ crates of beer from warehouse B to pub 1
Ship ___ crates of beer from warehouse B to pub 2
Ship ___ crates of beer from warehouse B to pub 3
Ship ___ crates of beer from warehouse B to pub 4
The total transportation cost of this shipping schedule is $_____.
Ongoing Monitoring
------------------
Ongoing Monitoring may take the form of:
* Updating your data files and resolving as the data changes (changing costs, supplies, demands);
* Resolving our model for new nodes (e.g., new warehouses or bars);
* Looking to see if cheaper transportation options are available along routes where the transportation cost affects the optimal solution (e.g., how much total savings can we get by reducing the transportation cost from Warehouse B to Bar 1).

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -0,0 +1,18 @@
Case Studies
=====================
.. toctree::
:maxdepth: 2
a_blending_problem
a_set_partitioning_problem
a_sudoku_problem
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

View File

@@ -0,0 +1,52 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
from matplotlib import rc
rc('text', usetex=True)
rc('font', family='serif')
def plot_interval(a,c,x_left, x_right,i, fbound):
lh = c*(1-a[0])
rh = c*(1+a[1])
x=arange(x_left, x_right+1)
y=0*x
arrow_r = Arrow(c,0, c*a[1],0,0.2)
arrow_l = Arrow(c,0,-c*a[0],0,0.2)
plot(x,y)
text((x_left+lh)/2.0,0.1,'freebound interval [%s, %s] is penalty-free' % (lh,rh))
text((x_left+lh)/2.0, 0.2, 'rhs=%s, %s' % (c, fbound))
cur_ax = gca()
cur_ax.add_patch(arrow_l)
cur_ax.add_patch(arrow_r)
axis([x_left,x_right,-0.1,0.3])
yticks([])
title('Elasticized constraint\_%s $C(x)= %s $' % (i, c))
figure()
subplots_adjust(hspace=0.5)
fbound = 'proportionFreeBound'
i=1
subplot(2,1,i)
a=[0.01,0.01]
c = 200
x_left = 0.97*c
x_right = 1.03*c
fb_string = '%s%s = %s' %(fbound,'', a[0])
plot_interval(a,c,x_left, x_right,i, fb_string)
i += 1
subplot(2,1,i)
a=[0.02, 0.05]
c = 500
x_left = 0.9*c #scale of window
x_right = 1.2*c #scale of window
fb_string = '%s%s = [%s,%s]' % (fbound,'List', a[0],a[1])
plot_interval(a,c,x_left, x_right,i, fb_string)
savefig('freebound.jpg')
savefig('freebound.pdf')
# vim: fenc=utf-8: ft=python:sw=4:et:nu:fdm=indent:fdn=1:syn=python

View File

@@ -0,0 +1,9 @@
{% extends '!layout.html' %}
{% block footer %}
{{ super() }}
<center>
<a rel="license" href="http://creativecommons.org/licenses/by-sa/3.0/nz/"><img alt="Creative Commons License" style="border-width:0" src="http://i.creativecommons.org/l/by-sa/3.0/nz/88x31.png" /></a><br /><span xmlns:dc="http://purl.org/dc/elements/1.1/" href="http://purl.org/dc/dcmitype/Text" property="dc:title" rel="dc:type">PuLP documentation</span> by <span xmlns:cc="http://creativecommons.org/ns#" property="cc:attributionName">Pulp documentation team</span> is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-sa/3.0/nz/">Creative Commons Attribution-Share Alike 3.0 New Zealand License</a>.
</center>
{% endblock %}

202
doc/source/conf.py Normal file
View File

@@ -0,0 +1,202 @@
# -*- coding: utf-8 -*-
#
# pulp sphinx documentation build configuration file, created by
# sphinx-quickstart on Mon Nov 2 11:54:01 2009.
#
# This file is execfile()d with the current directory set to its containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import sys, os
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.insert(0,os.path.abspath('../src/pulp/'))
# -- General configuration -----------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.pngmath', 'sphinx.ext.ifconfig', 'sphinx.ext.autosummary']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
pngmath_use_preview = True
todo_include_todos = True
autoclass_content = 'both'
# The suffix of source filenames.
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'PuLP'
copyright = u'2009-, pulp documentation team.'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The full version, including alpha/beta/rc tags.
release = open(os.path.join('..','..','VERSION')).read().strip()
# The short X.Y version.
version = release[:3]
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# List of documents that shouldn't be included in the build.
#unused_docs = []
# List of directories, relative to source directory, that shouldn't be searched
# for source files.
exclude_trees = []
# The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# -- Options for HTML output ---------------------------------------------------
# The theme to use for HTML and HTML Help pages. Major themes that come with
# Sphinx are currently 'default' and 'sphinxdoc'.
html_theme = 'default'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
#html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# If false, no module index is generated.
#html_use_modindex = True
# If false, no index is generated.
#html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = ''
# Output file base name for HTML help builder.
htmlhelp_basename = 'pulpdoc'
# -- Options for LaTeX output --------------------------------------------------
# The paper size ('letter' or 'a4').
#latex_paper_size = 'letter'
# The font size ('10pt', '11pt' or '12pt').
#latex_font_size = '10pt'
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('index', 'pulp.tex', u'pulp Documentation',
u'pulp documentation team', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# Additional stuff for the LaTeX preamble.
#latex_preamble = ''
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# If false, no module index is generated.
#latex_use_modindex = True
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {'http://docs.python.org/': None}

89
doc/source/constants.rst Normal file
View File

@@ -0,0 +1,89 @@
:mod:`pulp.constants`
=====================
.. automodule:: pulp.constants
:members:
:undoc-members:
:inherited-members:
:show-inheritance:
.. data:: LpStatus
Return status from solver:
+-----------------------------+---------------+-----------------+
| LpStatus key | string value | numerical value |
+=============================+===============+=================+
| :data:`LpStatusOptimal` | "Optimal" | 1 |
+-----------------------------+---------------+-----------------+
| :data:`LpStatusNotSolved` | "Not Solved" | 0 |
+-----------------------------+---------------+-----------------+
| :data:`LpStatusInfeasible` | "Infeasible" | -1 |
+-----------------------------+---------------+-----------------+
| :data:`LpStatusUnbounded` | "Unbounded" | -2 |
+-----------------------------+---------------+-----------------+
| :data:`LpStatusUndefined` | "Undefined" | -3 |
+-----------------------------+---------------+-----------------+
.. data:: LpStatusOptimal
LpStatusOptimal = 1
.. data:: LpStatusNotSolved
LpStatusNotSolved = 0
.. data:: LpStatusInfeasible
LpStatusInfeasible = -1
.. data:: LpStatusUnbounded
LpStatusUnbounded = -2
.. data:: LpStatusUndefined
LpStatusUndefined = -3
.. data:: LpSenses
Dictionary of values for :attr:`~pulp.pulp.LpProblem.sense`:
LpSenses =
{:data:`LpMaximize`:"Maximize", :data:`LpMinimize`:"Minimize"}
.. data:: LpMinimize
LpMinimize = 1
.. data:: LpMaximize
LpMaximize = -1
.. data:: LpConstraintEQ
LpConstraintEQ = 0
.. data:: LpConstraintLE
LpConstraintLE = -1
.. data:: LpConstraintGE
LpConstraintGE = 1
.. data:: LpConstraintSenses
+--------------------------+----------------+-----------------+
| LpConstraint key | symbolic value | numerical value |
+==========================+================+=================+
| :data:`LpConstraintEQ` | "==" | 0 |
+--------------------------+----------------+-----------------+
| :data:`LpConstraintLE` | "<=" | -1 |
+--------------------------+----------------+-----------------+
| :data:`LpConstraintGE` | ">=" | 1 |
+--------------------------+----------------+-----------------+

View File

@@ -0,0 +1,62 @@
Elastic Constraints
^^^^^^^^^^^^^^^^^^^
.. currentmodule:: pulp
A constraint :math:`C(x) = c` (equality may be replaced by :math:`\le`
or :math:`\ge`)
can be elasticized to the form
.. math:: C(x) \in D
where :math:`D` denotes some interval containing the value
:math:`c`.
Define the constraint in two steps:
#. instantiate constraint (subclass of :class:`LpConstraint`) with target :math:`c`.
#. call its :meth:`~LpConstraint.makeElasticSubProblem` method which returns
an object of type :class:`FixedElasticSubProblem`
(subclass of :class:`LpProblem`) - its objective is the minimization
of the distance of :math:`C(x)` from :math:`D`.
.. code-block:: python
constraint = LpConstraint(..., rhs = c)
elasticProblem = constraint.makeElasticSubProblem(
penalty = <penalty_value>,
proportionFreeBound = <freebound_value>,
proportionFreeBoundList = <freebound_list_value>,
)
where:
* ``<penalty_value>`` is a real number
* ``<freebound_value>`` :math:`a \in [0,1]` specifies a symmetric
target interval :math:`D = (c(1-a),c(1+a))` about :math:`c`
* ``<freebound_list_value> = [a,b]``, a list of
proportions :math:`a, b \in [0,1]` specifying an asymmetric target
interval :math:`D = (c(1-a),c(1+b))` about :math:`c`
The penalty applies to the constraint at points :math:`x` where
:math:`C(x) \not \in D`.
The magnitude of ``<penalty_value>`` can be assessed by examining
the final objective function in the ``.lp`` file written by
:meth:`LpProblem.writeLP`.
Example:
>>> constraint_1 = LpConstraint('ex_1',sense=1,rhs=200)
>>> elasticProblem_1 = constraint_1.makeElasticSubproblem(penalty=1, proportionFreeBound = 0.01)
>>> constraint_2 = LpConstraint('ex_2',sense=0,rhs=500)
>>> elasticProblem_2 = constraint_2.makeElasticSubproblem(penalty=1,
proportionFreeBoundList = [0.02, 0.05])
#. constraint_1 has a penalty-free target interval of 1% either side of the rhs value, 200
#. constraint_2 has a penalty-free target interval of
- 2% on left and 5% on the right side of the rhs value, 500
.. image:: _static/freebound.*
:height: 5in
:alt: Freebound interval
Following are the methods of the return-value:

63
doc/source/index.rst Normal file
View File

@@ -0,0 +1,63 @@
.. pulp_sphinx documentation master file, created by
sphinx-quickstart on Sun Nov 1 14:59:49 2009.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Optimization with PuLP
----------------------
You can begin learning Python and using PuLP
by looking at the content below. We recommend that you read The Optimisation Process,
Optimisation Concepts, and the Introduction to Python
before beginning the case-studies. For instructions for the installation of PuLP
see :ref:`installation`.
The full PuLP function documentation is available, and useful functions
will be explained in the case studies.
The case studies are in order, so the later case studies will assume you have
(at least) read the earlier case studies. However, we will provide links to any
relevant information you will need.
.. toctree::
:maxdepth: 2
main/index
CaseStudies/index
.. comment block
main/Concepts
main/Introduction
main/Installing%20Python InstallingPythonatHome
main/BasicPythonCoding
main/Introduction to PuLP
main/InstallingPulpatHome
main/CaseStudies/ABLendingProblem
main/UsingElasticConstraints
main/Using FractionalConstraints
main/SequentialOptimisation
main/ExtraCaseStudies
PuLP Internal Documentation
===========================
.. toctree::
:maxdepth: 1
constants
pulp
solvers
Authors
=======
The authors of this documentation (the pulp documentation team) include:
.. include:: AUTHORS.txt
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

531
doc/source/main/amply.rst Normal file
View File

@@ -0,0 +1,531 @@
Amply
======
Introduction
------------
Amply allows you to load and manipulate AMPL data as Python data structures.
Amply only supports a specific subset of the AMPL syntax:
* set declarations
* set data statements
* parameter declarations
* parameter data statements
Declarations and data statements
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Typically, problems expressed in AMPL consist of two parts, a *model* section and a *data* section.
Amply is only designed to parse the parameter and set statements contained within AMPL data sections.
However, in order to parse these statements correctly, information that would usually be contained
within the model section may be required. For instance, it may not be possible to infer the dimension
of a set purely from its data statement. Therefore, Amply also supports set and parameter declarations.
These do not have to be put in a separate section, they only need to occur before the corresponding
data statement.
The declaration syntax supported is extremely limited, and does not include most
elements of the AMPL programming language. The intention is that this library
is used as a way of loading data specified in an AMPL-like syntax.
Furthermore, Amply does not perform any validation on data statements.
About this document
^^^^^^^^^^^^^^^^^^^^
This document is intended as a guide to the syntax supported by Amply, and not as a general
AMPL reference manual. For more in depth coverage see the `GNU MathProg manual, Chapter 5: Model data
<http://www.cs.unb.ca/~bremner/docs/glpk/gmpl.pdf>`_ or the following links:
* `Sets in AMPL <http://twiki.esc.auckland.ac.nz/twiki/bin/view/OpsRes/SetsInAMPL>`_
* `Parameters in AMPL <http://twiki.esc.auckland.ac.nz/twiki/bin/view/OpsRes/ParametersInAMPL>`_
Quickstart Guide
----------------
.. testsetup::
>>> from pulp import Amply
Import the class: ::
>>> from pulp import Amply
A simple set. Sets behave a lot like lists.
.. doctest::
>>> data = Amply("set CITIES := Auckland Wellington Christchurch;")
>>> print data.CITIES
<SetObject: ['Auckland', 'Wellington', 'Christchurch']>
>>> print data['CITIES']
<SetObject: ['Auckland', 'Wellington', 'Christchurch']>
>>> for c in data.CITIES: print c
...
Auckland
Wellington
Christchurch
>>> print data.CITIES[0]
Auckland
>>> print len(data.CITIES)
3
Data can be integers, reals, symbolic, or quoted strings:
.. doctest::
>>> data = Amply("""
... set BitsNPieces := 0 3.2 -6e4 Hello "Hello, World!";
... """)
>>> print data.BitsNPieces
<SetObject: [0.0, 3.2000000000000002, -60000.0, 'Hello', 'Hello, World!']>
Sets can contain multidimensional data, but we have to declare them to be so first.
.. doctest::
>>> data = Amply("""
... set pairs dimen 2;
... set pairs := (1, 2) (2, 3) (3, 4);
... """)
>>> print data.pairs
<SetObject: [(1, 2), (2, 3), (3, 4)]>
Sets themselves can be multidimensional (i.e. be subscriptable):
.. doctest::
>>> data = Amply("""
... set CITIES{COUNTRIES};
... set CITIES[Australia] := Adelaide Melbourne Sydney;
... set CITIES[Italy] := Florence Milan Rome;
... """)
>>> print data.CITIES['Australia']
['Adelaide', 'Melbourne', 'Sydney']
>>> print data.CITIES['Italy']
['Florence', 'Milan', 'Rome']
Note that in the above example, the set COUNTRIES didn't actually have to exist itself.
Amply does not perform any validation on subscripts, it only uses them to figure out
how many subscripts a set has. To specify more than one, separate them by commas:
.. doctest::
>>> data = Amply("""
... set SUBURBS{COUNTRIES, CITIES};
... set SUBURBS[Australia, Melbourne] := Docklands 'South Wharf' Kensington;
... """)
>>> print data.SUBURBS['Australia', 'Melbourne']
['Docklands', 'South Wharf', 'Kensington']
*Slices* can be used to simplify the entry of multi-dimensional data.
.. doctest::
>>> data=Amply("""
... set TRIPLES dimen 3;
... set TRIPLES := (1, 1, *) 2 3 4 (*, 2, *) 6 7 8 9 (*, *, *) (1, 1, 1);
... """)
>>> print data.TRIPLES
<SetObject: [(1, 1, 2), (1, 1, 3), (1, 1, 4), (6, 2, 7), (8, 2, 9), (1, 1, 1)]>
>
Set data can also be specified using a matrix notation.
A '+' indicates that the pair is included in the set whereas a '-' indicates a
pair not in the set.
.. doctest::
>>> data=Amply("""
... set ROUTES dimen 2;
... set ROUTES : A B C D :=
... E + - - +
... F + + - -
... ;
... """)
>>> print data.ROUTES
<SetObject: [('E', 'A'), ('E', 'D'), ('F', 'A'), ('F', 'B')]>
Matrices can also be transposed:
.. doctest::
>>> data=Amply("""
... set ROUTES dimen 2;
... set ROUTES (tr) : E F :=
... A + +
... B - +
... C - -
... D + -
... ;
... """)
>>> print data.ROUTES
<SetObject: [('E', 'A'), ('F', 'A'), ('F', 'B'), ('E', 'D')]>
Matrices only specify 2d data, however they can be combined with slices
to define higher-dimensional data:
.. doctest::
>>> data = Amply("""
... set QUADS dimen 2;
... set QUADS :=
... (1, 1, *, *) : 2 3 4 :=
... 2 + - +
... 3 - + +
... (1, 2, *, *) : 2 3 4 :=
... 2 - + -
... 3 + - -
... ;
... """)
>>> print data.QUADS
<SetObject: [(1, 1, 2, 2), (1, 1, 2, 4), (1, 1, 3, 3), (1, 1, 3, 4), (1, 2, 2, 3), (1, 2, 3, 2)]>
Parameters are also supported:
.. doctest::
>>> data = Amply("""
... param T := 30;
... param n := 5;
... """)
>>> print data.T
30
>>> print data.n
5
Parameters are commonly indexed over sets. No validation is done by Amply,
and the sets do not have to exist. Parameter objects are represented
as a mapping.
.. doctest::
>>> data = Amply("""
... param COSTS{PRODUCTS};
... param COSTS :=
... FISH 8.5
... CARROTS 2.4
... POTATOES 1.6
... ;
... """)
>>> print data.COSTS
<ParamObject: {'POTATOES': 1.6000000000000001, 'FISH': 8.5, 'CARROTS': 2.3999999999999999}>
>>> print data.COSTS['FISH']
8.5
Parameter data statements can include a *default* clause. If a '.' is included
in the data, it is replaced with the default value:
.. doctest::
>>> data = Amply("""
... param COSTS{P};
... param COSTS default 2 :=
... F 2
... E 1
... D .
... ;
... """)
>>> print data.COSTS['D']
2.0
Parameter declarations can also have a default clause. For these parameters,
any attempt to access the parameter for a key that has not been defined
will return the default value:
.. doctest::
>>> data = Amply("""
... param COSTS{P} default 42;
... param COSTS :=
... F 2
... E 1
... ;
... """)
>>> print data.COSTS['DOES NOT EXIST']
42.0
Parameters can be indexed over multiple sets. The resulting values can be
accessed by treating the parameter object as a nested dictionary, or by
using a tuple as an index:
.. doctest::
>>> data = Amply("""
... param COSTS{CITIES, PRODUCTS};
... param COSTS :=
... Auckland FISH 5
... Auckland CHIPS 3
... Wellington FISH 4
... Wellington CHIPS 1
... ;
... """)
>>> print data.COSTS
<ParamObject: {'Wellington': {'FISH': 4.0, 'CHIPS': 1.0}, 'Auckland': {'FISH': 5.0, 'CHIPS': 3.0}}>
>>> print data.COSTS['Wellington']['CHIPS'] # nested dict
1.0
>>> print data.COSTS['Wellington', 'CHIPS'] # tuple as key
1.0
Parameters support a slice syntax similar to that of sets:
.. doctest::
>>> data = Amply("""
... param COSTS{CITIES, PRODUCTS};
... param COSTS :=
... [Auckland, * ]
... FISH 5
... CHIPS 3
... [Wellington, * ]
... FISH 4
... CHIPS 1
... ;
... """)
>>> print data.COSTS
<ParamObject: {'Wellington': {'FISH': 4.0, 'CHIPS': 1.0}, 'Auckland': {'FISH': 5.0, 'CHIPS': 3.0}}>
Parameters indexed over two sets can also be specified in tabular format:
.. doctest::
>>> data = Amply("""
... param COSTS{CITIES, PRODUCTS};
... param COSTS: FISH CHIPS :=
... Auckland 5 3
... Wellington 4 1
... ;
... """)
>>> print data.COSTS
<ParamObject: {'Wellington': {'FISH': 4.0, 'CHIPS': 1.0}, 'Auckland': {'FISH': 5.0, 'CHIPS': 3.0}}>
Tabular data can also be transposed:
.. doctest::
>>> data = Amply("""
... param COSTS{CITIES, PRODUCTS};
... param COSTS (tr): Auckland Wellington :=
... FISH 5 4
... CHIPS 3 1
... ;
... """)
>>> print data.COSTS
<ParamObject: {'Wellington': {'FISH': 4.0, 'CHIPS': 1.0}, 'Auckland': {'FISH': 5.0, 'CHIPS': 3.0}}>
Slices can be combined with tabular data for parameters indexed over more than
2 sets:
.. doctest::
>>> data = Amply("""
... param COSTS{CITIES, PRODUCTS, SIZE};
... param COSTS :=
... [Auckland, *, *] : SMALL LARGE :=
... FISH 5 9
... CHIPS 3 5
... [Wellington, *, *] : SMALL LARGE :=
... FISH 4 7
... CHIPS 1 2
... ;
... """)
>>> print data.COSTS
<ParamObject: {'Wellington': {'FISH': {'SMALL': 4.0, 'LARGE': 7.0}, 'CHIPS': {'SMALL': 1.0, 'LARGE': 2.0}}, 'Auckland': {'FISH': {'SMALL': 5.0, 'LARGE': 9.0}, '
API
---
All functionality is contained within the ``Amply`` class.
.. class:: Amply(string="")
.. method:: load_string(string)
Parse string data.
.. method:: load_file(file)
Parse contents of file or file-like object (has a read() method).
.. staticmethod:: from_file(file)
Alternate constructor. Create Amply object from contents of file or file-like object.
The parsed data structures can then be accessed from an ``Amply`` object via
attribute lookup (if the name of the symbol is a valid Python name) or item
lookup. ::
from pulp import Amply
data = Amply("set CITIES := Auckland Hamilton Wellington")
# attribute lookup
assert data.CITIES == ['Auckland', 'Hamilton', 'Wellington']
# item lookup
assert data['CITIES'] == data.CITIES
Note that additional data may be loaded into an Amply object simply by calling
one of its methods. A common idiom might be to specify the set and parameter
declarations within your Python script, then load the actual data from
external files. ::
from pulp import Amply
data = Amply("""
set CITIES;
set ROUTES dimen 2;
param COSTS{ROUTES};
param DISTANCES{ROUTES};
""")
for data_file in ('cities.dat', 'routes.dat', 'costs.dat', 'distances.dat'):
data.load_file(open(data_file))
.. Commented out the below, not sure if we need it (incomplete)
Reference
---------
Sets
^^^^
Set declarations
~~~~~~~~~~~~~~~~
A set declaration is an extremely limited version of set statements which are valid in AMPL models.
They determine the *subscript domain* and *data dimension* of the set. If not specified, the default
subscript domain is an empty set and the default dimension is 1.
.. productionlist::
set_def_stmt: "set" `name` [`subscript_domain`] ["dimen" `integer`] ";"
subscript_domain: "{" `name` ("," `name`)* "}"
The following statment declares a set named "countries". ::
set countries;
The following statement declares a set named "cities" which is indexed over "countries". ::
set cities {countries};
The following declares a set named "routes" with 2d data. ::
set routes dimen 2;
Set data statements
~~~~~~~~~~~~~~~~~~~~~
A set data statement is used to specify the members of a set. It consists of one or more
*data records*. There are four types of data records: simple data, slice records, matrix
data and transposed matrix data.
.. productionlist::
set_stmt: "set" `name` [`set_member`] `data_record`+ ";"
data_record: `simple_data` | `set_slice_record` | `matrix_data` | `tr_matrix_data`
Simple Data
############
A simple data record is an optionally comma-separated list of data values.
.. productionlist::
simple_data: `data` ([","] `data`)*
For instance: ::
set CITIES := Auckland Hamilton 'Palmerston North' Wellington;
::
set ROUTES dimen 2;
set ROUTES := (Auckland, Hamilton) (Auckland, Wellington);
Slice Records
###############
Slice records are used to simplify the entry of multi-dimensional sets. They allow you to partially
specify the values of elements. A slice affects all data records that follow it (until a new slice
is specified).
.. productionlist::
set_slice_record: "(" `set_slice_component` ("," `set_slice_component`)* ")"
set_slice_component: `number` | `symbol` | "*"
This is best demonstrated by some examples. The sets A and B are identical: ::
set A dimen 3;
set B dimen 3;
set A := (1, 2, 3) (1, 3, 2) (1, 4, 6) (1, 8, 8) (2, 1, 3) (2, 1, 1) (2, 1, 2);
set B := (1, *, *) (2, 3) (3, 2) (4, 6) (8, 8) (2, 1, *) 3 1 2;
The number of asterisks in a slice is called the *slice dimension*. Any data records that follow
are interpreted as being of the same dimension; the value is taken as the value of the slice with
the asterisks replaced with the value of the record.
Matrix records
################
Matrix records are a convenient way of specifying 2-dimensional data. The data record looks like
a matrix with row and column headings, where the values are either '+' if the combination is in
the set, and '-' if the combination is not in the set. A common use-case is for defining the
set of arcs that exist between a set of nodes.
.. productionlist::
matrix_data: ":" `matrix_columns` ":=" `matrix_row`+
matrix_columns: `data`+
matrix_row: `data` ("+"|"-")+
tr_matrix_data: "(tr)" `matrix_data`
Matrices can also be transposed by including ``(tr)`` immediately preceding the record.
In the example below the sets A, B and C are identical: ::
set A dimen 2;
set B dimen 2;
set C dimen 2;
set A := (1, 1) (1, 3) (2, 2) (3, 1) (3, 2) (3, 3);
set B : 1 2 3 :=
1 + - +
2 - + -
3 + + +
;
set C (tr) : 1 2 3 :=
1 + - +
2 - + +
3 + - +
;
Matrices can be used for sets with higher dimensions by placing them after 2 dimensional
slice records.
Set examples
~~~~~~~~~~~~
Parameters
^^^^^^^^^^^^
Plain Data
~~~~~~~~~~~~~
Tabular data
~~~~~~~~~~~~~~
Tabbing Data
~~~~~~~~~~~~~~

View File

@@ -0,0 +1,392 @@
Basic Python Coding
===================
In this course you will learn basic programming in Python, but there is also
excellent Python Language reference material available on the internet freely.
You can download the book `Dive Into Python <http://www.diveintopython.org/>`_
or there are a host of `Beginners Guides <http://wiki.python.org/moin/BeginnersGuide>`_
to Python on the Python Website. Follow the links to either:
+ `BeginnersGuide/NonProgrammers <http://wiki.python.org/moin/BeginnersGuide/NonProgrammers>`_
+ `BeginnersGuide/Programmers <http://wiki.python.org/moin/BeginnersGuide/Programmers>`_
depending on your level of current programming knowledge. The code sections
below assume a knowledge of fundamental programming principles and mainly
focus on Syntax specific to programming in Python.
Note: >>> represents a Python command line prompt.
Loops in Python
---------------
for Loop
~~~~~~~~
The general format is:
.. code-block:: python
for variable in sequence:
#some commands
#other commands after for loop
Note that the formatting (indents and new lines) governs the end of the for
loop, whereas the start of the loop is the colon :.
Observe the loop below, which is similar to loops you will be using in the
course. The variable i moves through the string list becoming each string in
turn. The top section is the code in a .py file and the bottom section shows
the output
.. code-block:: python
#The following code demonstrates a list with strings
ingredientslist = ["Rice","Water","Jelly"]
for i in ingredientslist:
print i
print "No longer in the loop"
gives
.. code-block:: python
Rice
Water
Jelly
No longer in the loop
while Loop
~~~~~~~~~~
These are similar to for loops except they continue to loop until the specified
condition is no longer true. A while loop is not told to work through any
specific sequence.
.. code-block:: python
i = 3
while i <= 15:
# some commands
i = i + 1 # a command that will eventually end the loop is naturally
required
# other commands after while loop
For this specific simple while loop, it would have been better to do as a for
loop but it demonstrates the syntax. while loops are useful if the number of
iterations before the loop needs to end, is unknown.
The if statement
~~~~~~~~~~~~~~~~
This is performed in much the same way as the loops above. The key identifier is
the colon : to start the statements and the end of indentation to end it.
.. code-block:: python
if j in testlist:
# some commands
elif j == 5:
# some commands
else:
# some commands
Here it is shown that "elif" (else if) and "else" can also be used after an if
statement. "else" can in fact be used after both the loops in the same way.
Array types in python
--------------------------
Lists
~~~~~
A list is simply a sequence of variables grouped together. The range function is
often used to create lists of integers, with the general format of
range(start,stop,step). The default for start is 0 and the default for step is
1.
>>> range(3,8)
[3,4,5,6,7]
This is a list/sequence. As well as integers, lists can also have strings in
them or a mix of integers, floats and strings. They can be created by a loop
(as shown in the next section) or by explicit creation (below). Note that the
print statement will display a string/variable/list/... to the user
>>> a = [5,8,"pt"]
>>> print a
[5,8,'pt']
>>> print a[0]
5
Tuples
~~~~~~
Tuples are basically the same as lists, but with the important difference that
they cannot be modified once they have been created. They are assigned by:
>>> x = (4,1,8,"string",[1,0],("j",4,"o"),14)
Tuples can have any type of number, strings, lists, other tuples, functions and
objects, inside them. Also note that the first element in the tuple is numbered
as element "zero". Accessing this data is done by:
>>> x[0]
4
>>> x[3]
"string"
Dictionaries
~~~~~~~~~~~~
A Dictionary is a list of reference keys each with associated data, whereby the
order does not affect the operation of the dictionary at all. With dictionaries,
the keys are not consecutive integers (unlike lists), and instead could be
integers, floats or strings. This will become clear:
>>> x = {} # creates a new empty dictionary - note the curly brackets denoting the creation of a dictionary
>>> x[4] = "programming" # the string "programming" is added to the dictionary x, with "4" as it's reference
>>> x["games"] = 12
>>> print x["games"]
12
In a dictionary, the reference keys and the stored values can be any type of
input. New dictionary elements are added as they are created (with a list, you
cannot access or write to a place in the list that exceeds the initially defined
list dimensions).
.. code-block:: python
costs = {"CHICKEN": 1.3, "BEEF": 0.8, "MUTTON": 12}
print "Cost of Meats"
for i in costs:
print i
print costs[i]
costs["LAMB"] = 5
print "Updated Costs of Meats"
for i in costs:
print i
print costs[i]
gives
.. code-block:: python
Cost of Meats
CHICKEN
1.3
MUTTON
12
BEEF
0.8
Updated Costs of Meats
LAMB
5
CHICKEN
1.3
MUTTON
12
BEEF
0.8
In the above example, the dictionary is created using curly brackets and colons
to represent the assignment of data to the dictionary keys. The variable i is
assigned to each of the keys in turn (in the same way it would be for a list
with
>>> for i in range(1,10)
). Then the dictionary is called with this key, and
it returns the data stored under that key name. These types of for loops using
dictionaries will be highly relevant in using PuLP to model LPs in this course.
List/Tuple/Dictionary Syntax Note
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Note that the creation of a:
- list is done with square brackets [];
- tuple is done with round brackets and a comma (,);
- dictionary is done with parentheses{}.
After creation however, when accessing elements in the list/tuple/dictionary,
the operation is always performed with square brackets (i.e a[3]?). If a was a
list or tuple, this would return the fourth element. If a was a dictionary it
would return the data stored with a reference key of 3.
List Comprehensions
~~~~~~~~~~~~~~~~~~~
Python supports List Comprehensions which are a fast and concise way to create
lists without using multiple lines. They are easily understandable when simple,
and you will be using them in your code for this course.
>>> a = [i for i in range(5)]
>>> a
[0, 1, 2, 3, 4]
This statement above will create the list [0,1,2,3,4] and assign it to the
variable "a".
>>> odds = [i for i in range(25) if i%2==1]
>>> odds
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23]
This statement above uses the if statement and the modulus operator(%) so that
only odd numbers are included in the list: [1,3,5,...,19,21,23]. (Note: The
modulus operator calculates the remainder from an integer division.)
>>> fifths = [i for i in range(25) if i%5==0]
>>> fifths
[0, 5, 10, 15, 20]
This will create a list with every fifth value in it [0,5,10,15,20]. Existing
lists can also be used in the creation of new lists below:
>>> a = [i for i in range(25) if (i in odds and i not in fifths)]
Note that this could also have been done in one step from scratch:
>>> a = [i for i in range(25) if (i%2==1 and i%5==0)]
For a challenge you can try creating
a. a list of prime numbers up to 100, and
#. a list of all "perfect" numbers.
`More List Comprehensions Examples <http://www.secnetix.de/olli/Python/list_comprehensions.hawk>`_
`Wikipedia: Perfect Numbers <http://en.wikipedia.org/wiki/Perfect_numbers>`_.
Other important language features
---------------------------------
Commenting in Python
~~~~~~~~~~~~~~~~~~~~
Commenting at the top of a file is done using """ to start and to end the
comment section. Commenting done throughout the code is done using the hash
# symbol at the start of the line.
Import Statement
~~~~~~~~~~~~~~~~
At the top of all your Python coding that you intend to use PuLP to model, you
will need the import statement. This statement makes the contents of another
module (file of program code) available in the module you are currently writing
i.e. functions and values defined in pulp.py that you will be required to call,
become usable. In this course you will use:
>>> from pulp import *
The asterisk represents that you are importing all names from the module of
pulp. Calling a function defined in pulp.py now can be done as if they were
defined in your own module.
Functions
~~~~~~~~~
Functions in Python are defined by: (def is short for define)
.. code-block:: python
def name(inputparameter1, inputparameter2, . . .):
#function body
For a real example, note that if inputs are assigned a value in the function
definition, that is the default value, and will be used only if no other value
is passed in. The order of the input parameters (in the definition) does not
matter at all, as long as when the function is called, the positional parameters
are entered in the corresponding order. If keywords are used the order of the
parameters does not matter at all:
.. code-block:: python
def string_appender(head='begin', tail='end', end_message='EOL'):
result = head + tail + end_message
return result
>>> string_appender('newbegin', end_message = 'StringOver')
newbeginendStringOver
In the above example, the output from the function call is printed. The default
value for head is 'begin', but instead the input of 'newbegin' was used. The
default value for tail of 'end' was used. And the input value of endmessage was
used. Note that end_message must be specified as a key word argument as no
value is given for tail
Classes
~~~~~~~
To demonstrate how classes work in Python, look at the following class structure.
The class name is Pattern, and it contains several class variables which are
relevant to any instance of the Pattern class (i.e a Pattern). The functions are
__init__
function which creates an instance of the Pattern
class and assigns the attributes of name and lengthsdict using self.
__str__
function defines what to return if the class instance is printed.
trim
function acts like any normal function, except as with all class functions,
self must be in the input brackets.
.. code-block:: python
class Pattern:
"""
Information on a specific pattern in the SpongeRoll Problem
"""
cost = 1
trimValue = 0.04
totalRollLength = 20
lenOpts = [5, 7, 9]
def __init__(self,name,lengths = None):
self.name = name
self.lengthsdict = dict(zip(self.lenOpts,lengths))
def __str__(self):
return self.name
def trim(self):
return Pattern.totalRollLength - sum([int(i)*self.lengthsdict[i] for i in self.lengthsdict])
This class could be used as follows:
>>> Pattern.cost # The class attributes can be accessed without making an instance of the class
1
>>> a = Pattern("PatternA",[1,0,1])
>>> a.cost # a is now an instance of the Pattern class and is associated with Pattern class variables
1
>>> print a # This calls the Pattern.__str__() function
"PatternA"
>>> a.trim() # This calls the Pattern.trim() function. Note that no input is required.
The self in the function definition is an implied input

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

16
doc/source/main/index.rst Normal file
View File

@@ -0,0 +1,16 @@
Main Topics
=====================
.. toctree::
:maxdepth: 2
the_optimisation_process
optimisation_concepts
basic_python_coding
installing_pulp_at_home
amply

View File

@@ -0,0 +1,13 @@
.. _installation:
Installing PuLP at Home
=======================
PuLP is a free open source software written in Python. It is used to describe
optimisation problems as mathematical models. PuLP can then call any of numerous
external LP solvers (CBC, GLPK, CPLEX, Gurobi etc) to solve this model and then
use python commands to manipulate and display the solution.
.. include:: ../../../INSTALL

View File

@@ -0,0 +1,42 @@
Optimisation Concepts
=====================
Linear Programing
-----------------
The simplest type of mathematical program is a linear program. For your
mathematical program to be a linear program you need the following
conditions to be true:
* The decision variables must be real variables;
* The objective must be a linear expression;
* The constraints must be linear expressions.
Linear expressions are any expression of the form
.. math::
a_1 x_1 + a_2 x_2 + a_3 x_3 + ... a_n x_n \{<= , =, >=\} b
where the :math:`a_i` and :math:`b` are known constants and :math:`x_i` are variables. The process
of solving a linear program is called linear programing. Linear programing
is done via the Revised Simplex Method (also known as the Primal Simplex Method),
the Dual Simplex Method or an Interior Point Method. Some solvers like cplex
allow you to specify which method you use, but we wont go into further detail
here.
Integer Programing
------------------
Integer programs are almost identical to linear programs with one very
important exception. Some of the decision variables in integer programs may
need to have only integer values. The variables are known as integer variables.
Since most integer programs contain a mix of continuous variables and integer
variables they are often known as mixed integer programs. While the change
from linear programing is a minor one, the effect on the solution process is
enormous. Integer programs can be very difficult problems to solve and there
is a lot of current research finding “good” ways to solve integer programs.
Integer programs can be solved using the branch-and-bound process.
Note For MIPs of any reasonable size the solution time grows
exponentially as the number of integer variables increases.

View File

@@ -0,0 +1,113 @@
The Optimisation Process
========================
Solving an optimisation problem is not a linear process, but the process can be
broken down into five general steps:
+ Getting the problem description
+ Formulating the mathematical program
+ Solving the mathematical program
+ Performing some post-optimal analysis
+ Presenting the solution and analysis
However, there are often "feedback loops" within this process. For example,
after formulating and solving an optimisation problem, you will often want to
consider the validity of your solution (often consulting with the person who
provided the problem description). If your solution is invalid you may need to
alter or update your formulation to incorporate your new understanding of the
actual problem. This process is shown in the Operations Research Methodology
Diagram.
.. image:: images/or_methodology.jpg
The modeling process starts with a well-defined model description, then uses
mathematics to formulate a mathematical program. Next, the modeler enters the
mathematical program into some solver software, e.g., Excel and solves the
model. Finally, the solution is translated into a decision in terms of the
original model description.
Using Python gives you a "shortcut" through the modeling process. By formulating
the mathematical program in Python you have already put it into a form that
can be used easily by PuLP the modeller to call many solvers, e.g. CPLEX, COIN,
gurobi so you don't need to enter the mathematical program into the solver
software. However, you usually don't put any "hard" numbers into your
formulation, instead you "populate" your model using data files, so there is
some work involved in creating the appropriate data file. The advantage of using
data files is that the same model may used many times with different data sets.
The Modeling Process
--------------------
The modeling process is a "neat and tidy" simplification of the optimisation
process. Let's consider the five steps of the optimisation process in more
detail:
Getting the Problem Description
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The aim of this step is to come up with a formal, rigourous model description.
Usually you start an optimisation project with an abstract description of a
problem and some data. Often you need to spend some time talking with the
person providing the problem (usually known as the client). By talking with the
client and considering the data available you can come up with the more
rigourous model description you are used to. Sometimes not all the data will be
relevant or you will need to ask the client if they can provide some other
data. Sometimes the limitations of the available data may change your model
description and subsequent formulation significantly.
Formulating the mathematical program
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In this step we identify the key quantifiable decisions, restrictions and goals
from the problem description, and capture their interdependencies in a
mathematical model. We can break the formulation process into 4 key steps:
* Identify the Decision Variables paying particular attention to units (for example: we need to decide how many hours per week each process will run for).
* Formulate the Objective Function using the decision variables, we can construct a minimise or maximise objective function. The objective function typically reflects the total cost, or total profit, for a given value of the decision variables.
* Formulate the Constraints, either logical (for example, we cannot work for a negative number of hours), or explicit to the problem description. Again, the constraints are expressed in terms of the decision variables.
* Identify the Data needed for the objective function and constraints. To solve your mathematical program you will need to have some "hard numbers" as variable bounds and/or variable coefficients in your objective function and/or constraints.
Solving the mathematical program
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
For relatively simple or well understood problems the mathematical model can
often be solved to optimality (i.e., the best possible solution is identified).
This is done using algorithms such as the Revised Simplex Method
or Interior Point Methods. However, many
industrial problems would take too long to solve to optimality using these
techniques, and so are solved using heuristic methods which do not guarantee optimality.
Performing some post-optimal analysis
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Often there is uncertainty in the problem description (either with the accuracy
of the data provided, or with the value(s) of data in the future). In this
situation the robustness of our solution can be examined by performing
post-optimal analysis. This involves identifying how the optimal solution would
change under various changes to the formulation (for example, what would be the
effect of a given cost increasing, or a particular machine failing?). This sort
of analysis can also be useful for making tactical or strategic decisions (for
example, if we invested in opening another factory, what effect would this have on our revenue?).
Another important consideration in this step (and the next) is the validation of
the mathematical program's solution. You should carefully consider what the
solution's variable values mean in terms of the original problem description.
Make sure they make sense to you and, more importantly, your client (which is
why the next step, presenting the solution and analysis is important).
Presenting the solution and analysis
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A crucial step in the optimisation process is the presentation of the solution
and any post-optimal analysis. The translation from a mathematical program's
solution back into a concise and comprehensible summary is as important as the
translation from the problem description into the mathematical program. Key
observations and decisions generated via optimisation must be presented in an
easily understandable way for the client or project stakeholders.
Your presentation is a crucial first step in the implementation of the decisions
generated by your mathematical program. If the decisions and their consequences
(often determined by the mathematical program constraints) are not presented
clearly and intelligently your optimal decision will never be used.
This step is also your chance to suggest other work in the future. This could include:
* Periodic monitoring of the validity of your mathematical program;
* Further analysis of your solution, looking for other benefits for your client;
* Identification of future optimisation opportunities.

107
doc/source/pulp.rst Normal file
View File

@@ -0,0 +1,107 @@
=====================================
:mod:`pulp`: Pulp classes
=====================================
.. module:: pulp
.. currentmodule:: pulp
.. autosummary::
LpProblem
LpVariable
LpAffineExpression
LpConstraint
LpConstraint.makeElasticSubProblem
FixedElasticSubProblem
.. todo:: LpFractionConstraint, FractionElasticSubProblem
The LpProblem Class
-------------------
.. autoclass:: LpProblem
:show-inheritance:
Three important attributes of the problem are:
.. attribute:: objective
The objective of the problem, an :obj:`LpAffineExpression`
.. attribute:: constraints
An :class:`ordered dictionary<odict.OrderedDict>` of
:class:`constraints<LpConstraint>` of the problem - indexed by their names.
.. attribute:: status
The return :data:`status <pulp.constants.LpStatus>`
of the problem from the solver.
Some of the more important methods:
.. automethod:: solve
.. automethod:: roundSolution
.. automethod:: setObjective
.. automethod:: writeLP
Variables and Expressions
-------------------------
.. autoclass:: LpElement
:members:
----
.. autoclass:: LpVariable
:members:
Example:
>>> x = LpVariable('x',lowBound = 0, cat='Continuous')
>>> y = LpVariable('y', upBound = 5, cat='Integer')
gives :math:`x \in [0,\infty)`, :math:`y \in (-\infty, 5]`, an
integer.
----
.. autoclass:: LpAffineExpression
:show-inheritance:
In brief, :math:`\textsf{LpAffineExpression([(x[i],a[i]) for i in
I])} = \sum_{i \in I} a_i x_i` where (note the order):
* ``x[i]`` is an :class:`LpVariable`
* ``a[i]`` is a numerical coefficient.
----
.. autofunction:: lpSum
Constraints
-----------
.. autoclass:: LpConstraint
:show-inheritance:
:members: makeElasticSubProblem
.. include:: documentation/elastic.rst
.. autoclass:: FixedElasticSubProblem
:show-inheritance:
:members:
Combinations and Permutations
------------------------------
.. autofunction:: combination
.. autofunction:: allcombinations
.. autofunction:: permutation
.. autofunction:: allpermutations
.. autofunction:: value

11
doc/source/solvers.rst Normal file
View File

@@ -0,0 +1,11 @@
:mod:`pulp.solvers` Interface to Solvers
========================================
.. automodule:: pulp.solvers
:members:
:undoc-members:
:show-inheritance:

11
docs.cfg Normal file
View File

@@ -0,0 +1,11 @@
[buildout]
extends = buildout.cfg
parts += sphinx
[sphinx]
recipe = collective.recipe.sphinxbuilder
source = ${buildout:directory}/doc/source
build = ${buildout:directory}/doc
interpreter = ${buildout:directory}/bin/pythonpulp
outputs = html

View File

@@ -0,0 +1,100 @@
"""
The American Steel Problem for the PuLP Modeller
Authors: Antony Phillips, Dr Stuart Mitchell 2007
"""
# Import PuLP modeller functions
from pulp import *
# List of all the nodes
Nodes = ["Youngstown",
"Pittsburgh",
"Cincinatti",
"Kansas City",
"Chicago",
"Albany",
"Houston",
"Tempe",
"Gary"]
nodeData = {# NODE Supply Demand
"Youngstown": [10000,0],
"Pittsburgh": [15000,0],
"Cincinatti": [0,0],
"Kansas City": [0,0],
"Chicago": [0,0],
"Albany": [0,3000],
"Houston": [0,7000],
"Tempe": [0,4000],
"Gary": [0,6000]}
# List of all the arcs
Arcs = [("Youngstown","Albany"),
("Youngstown","Cincinatti"),
("Youngstown","Kansas City"),
("Youngstown","Chicago"),
("Pittsburgh","Cincinatti"),
("Pittsburgh","Kansas City"),
("Pittsburgh","Chicago"),
("Pittsburgh","Gary"),
("Cincinatti","Albany"),
("Cincinatti","Houston"),
("Kansas City","Houston"),
("Kansas City","Tempe"),
("Chicago","Tempe"),
("Chicago","Gary")]
arcData = { # ARC Cost Min Max
("Youngstown","Albany"): [0.5,0,1000],
("Youngstown","Cincinatti"): [0.35,0,3000],
("Youngstown","Kansas City"): [0.45,1000,5000],
("Youngstown","Chicago"): [0.375,0,5000],
("Pittsburgh","Cincinatti"): [0.35,0,2000],
("Pittsburgh","Kansas City"): [0.45,2000,3000],
("Pittsburgh","Chicago"): [0.4,0,4000],
("Pittsburgh","Gary"): [0.45,0,2000],
("Cincinatti","Albany"): [0.35,1000,5000],
("Cincinatti","Houston"): [0.55,0,6000],
("Kansas City","Houston"): [0.375,0,4000],
("Kansas City","Tempe"): [0.65,0,4000],
("Chicago","Tempe"): [0.6,0,2000],
("Chicago","Gary"): [0.12,0,4000]}
# Splits the dictionaries to be more understandable
(supply, demand) = splitDict(nodeData)
(costs, mins, maxs) = splitDict(arcData)
# Creates the boundless Variables as Integers
vars = LpVariable.dicts("Route",Arcs,None,None,LpInteger)
# Creates the upper and lower bounds on the variables
for a in Arcs:
vars[a].bounds(mins[a], maxs[a])
# Creates the 'prob' variable to contain the problem data
prob = LpProblem("American Steel Problem",LpMinimize)
# Creates the objective function
prob += lpSum([vars[a]* costs[a] for a in Arcs]), "Total Cost of Transport"
# Creates all problem constraints - this ensures the amount going into each node is at least equal to the amount leaving
for n in Nodes:
prob += (supply[n]+ lpSum([vars[(i,j)] for (i,j) in Arcs if j == n]) >=
demand[n]+ lpSum([vars[(i,j)] for (i,j) in Arcs if i == n])), "Steel Flow Conservation in Node %s"%n
# The problem data is written to an .lp file
prob.writeLP("AmericanSteelProblem.lp")
# The problem is solved using PuLP's choice of Solver
prob.solve()
# The status of the solution is printed to the screen
print "Status:", LpStatus[prob.status]
# Each of the variables is printed with it's resolved optimum value
for v in prob.variables():
print v.name, "=", v.varValue
# The optimised objective function value is printed to the screen
print "Total Cost of Transportation = ", value(prob.objective)

View File

@@ -0,0 +1,71 @@
"""
The Beer Distribution Problem for the PuLP Modeller
Authors: Antony Phillips, Dr Stuart Mitchell 2007
"""
# Import PuLP modeler functions
from pulp import *
# Creates a list of all the supply nodes
Warehouses = ["A", "B"]
# Creates a dictionary for the number of units of supply for each supply node
supply = {"A": 1000,
"B": 4000}
# Creates a list of all demand nodes
Bars = ["1", "2", "3", "4", "5"]
# Creates a dictionary for the number of units of demand for each demand node
demand = {"1":500,
"2":900,
"3":1800,
"4":200,
"5":700,}
# Creates a list of costs of each transportation path
costs = [ #Bars
#1 2 3 4 5
[2,4,5,2,1],#A Warehouses
[3,1,3,2,3] #B
]
# The cost data is made into a dictionary
costs = makeDict([Warehouses,Bars],costs,0)
# Creates the 'prob' variable to contain the problem data
prob = LpProblem("Beer Distribution Problem",LpMinimize)
# Creates a list of tuples containing all the possible routes for transport
Routes = [(w,b) for w in Warehouses for b in Bars]
# A dictionary called 'Vars' is created to contain the referenced variables(the routes)
vars = LpVariable.dicts("Route",(Warehouses,Bars),0,None,LpInteger)
# The objective function is added to 'prob' first
prob += lpSum([vars[w][b]*costs[w][b] for (w,b) in Routes]), "Sum_of_Transporting_Costs"
# The supply maximum constraints are added to prob for each supply node (warehouse)
for w in Warehouses:
prob += lpSum([vars[w][b] for b in Bars])<=supply[w], "Sum_of_Products_out_of_Warehouse_%s"%w
# The demand minimum constraints are added to prob for each demand node (bar)
for b in Bars:
prob += lpSum([vars[w][b] for w in Warehouses])>=demand[b], "Sum_of_Products_into_Bar%s"%b
# The problem data is written to an .lp file
prob.writeLP("BeerDistributionProblem.lp")
# The problem is solved using PuLP's choice of Solver
prob.solve()
# The status of the solution is printed to the screen
print "Status:", LpStatus[prob.status]
# Each of the variables is printed with it's resolved optimum value
for v in prob.variables():
print v.name, "=", v.varValue
# The optimised objective function value is printed to the screen
print "Total Cost of Transportation = ", value(prob.objective)

View File

@@ -0,0 +1,82 @@
"""
The Beer Distribution Problem for the PuLP Modeller
Illustrates changing a constraint and solving
Authors: Antony Phillips, Dr Stuart Mitchell 2007
"""
# Import PuLP modeler functions
from pulp import *
# Creates a list of all the supply nodes
Warehouses = ["A", "B"]
# Creates a dictionary for the number of units of supply for each supply node
supply = {"A": 1000,
"B": 4000}
# Creates a list of all demand nodes
Bars = ["1", "2", "3", "4", "5"]
# Creates a dictionary for the number of units of demand for each demand node
demand = {"1":500,
"2":900,
"3":1800,
"4":200,
"5":700,}
# Creates a list of costs of each transportation path
costs = [ #Bars
#1 2 3 4 5
[2,4,5,2,1],#A Warehouses
[3,1,3,2,3] #B
]
# The cost data is made into a dictionary
costs = makeDict([Warehouses,Bars],costs,0)
# Creates the 'prob' variable to contain the problem data
prob = LpProblem("Beer Distribution Problem",LpMinimize)
# Creates a list of tuples containing all the possible routes for transport
Routes = [(w,b) for w in Warehouses for b in Bars]
# A dictionary called 'Vars' is created to contain the referenced variables(the routes)
vars = LpVariable.dicts("Route",(Warehouses,Bars),0,None,LpInteger)
# The objective function is added to 'prob' first
prob += lpSum([vars[w][b]*costs[w][b] for (w,b) in Routes]), "Sum_of_Transporting_Costs"
# The supply maximum constraints are added to prob for each supply node (warehouse)
for w in Warehouses:
prob += lpSum([vars[w][b] for b in Bars])<=supply[w], "Sum_of_Products_out_of_Warehouse_%s"%w
# The demand minimum constraints are added to prob for each demand node (bar)
# These constraints are stored for resolve later
bar_demand_constraint = {}
for b in Bars:
constraint = lpSum([vars[w][b] for w in Warehouses])>=demand[b]
prob += constraint, "Sum_of_Products_into_Bar_%s"%b
bar_demand_constraint[b] = constraint
# The problem data is written to an .lp file
prob.writeLP("BeerDistributionProblem.lp")
for demand in range(500, 601, 10):
# reoptimise the problem by increasing demand at bar '1'
# note the constant is stored as the LHS constant not the RHS of the constraint
bar_demand_constraint['1'].constant = - demand
#or alternatively,
#prob.constraints["Sum_of_Products_into_Bar_1"].constant = - demand
# The problem is solved using PuLP's choice of Solver
prob.solve()
# The status of the solution is printed to the screen
print "Status:", LpStatus[prob.status]
# Each of the variables is printed with it's resolved optimum value
for v in prob.variables():
print v.name, "=", v.varValue
# The optimised objective function value is printed to the screen
print "Total Cost of Transportation = ", value(prob.objective)

119
examples/CG.py Normal file
View File

@@ -0,0 +1,119 @@
"""
Column Generation Functions
Authors: Antony Phillips, Dr Stuart Mitchell 2008
"""
# Import PuLP modeler functions
from pulp import *
class Pattern:
"""
Information on a specific pattern in the SpongeRoll Problem
"""
cost = 1
trimValue = 0.04
totalRollLength = 20
lenOpts = ["5", "7", "9"]
def __init__(self, name, lengths = None):
self.name = name
self.lengthsdict = dict(zip(self.lenOpts,lengths))
def __str__(self):
return self.name
def trim(self):
return Pattern.totalRollLength - sum([int(i)*int(self.lengthsdict[i]) for i in self.lengthsdict])
def masterSolve(Patterns, rollData, relax = True):
# The rollData is made into separate dictionaries
(rollDemand,surplusPrice) = splitDict(rollData)
# The variable 'prob' is created
prob = LpProblem("Cutting Stock Problem",LpMinimize)
# vartype represents whether or not the variables are relaxed
if relax:
vartype = LpContinuous
else:
vartype = LpInteger
# The problem variables are created
pattVars = LpVariable.dicts("Pattern", Patterns, 0, None, vartype)
surplusVars = LpVariable.dicts("Surplus", Pattern.lenOpts, 0, None, vartype)
# The objective function is entered: (the total number of large rolls used * the cost of each) -
# (the value of the surplus stock) - (the value of the trim)
prob += lpSum([pattVars[i]*Pattern.cost for i in Patterns]) - lpSum([surplusVars[i]*surplusPrice[i]\
for i in Pattern.lenOpts]) - lpSum([pattVars[i]*i.trim()*Pattern.trimValue for i in Patterns])
# The demand minimum constraint is entered
for j in Pattern.lenOpts:
prob += lpSum([pattVars[i]*i.lengthsdict[j] for i in Patterns]) - surplusVars[j]>=rollDemand[j],"Min%s"%j
# The problem is solved
prob.solve()
# The variable values are rounded
prob.roundSolution()
if relax:
# Creates a dual variables list
duals = {}
for name,i in zip(['Min5','Min7','Min9'],Pattern.lenOpts):
duals[i] = prob.constraints[name].pi
return duals
else:
# Creates a dictionary of the variables and their values
varsdict = {}
for v in prob.variables():
varsdict[v.name] = v.varValue
# The number of rolls of each length in each pattern is printed
for i in Patterns:
print i, " = %s"%[i.lengthsdict[j] for j in Pattern.lenOpts]
return value(prob.objective), varsdict
def subSolve(Patterns, duals):
# The variable 'prob' is created
prob = LpProblem("SubProb",LpMinimize)
# The problem variables are created
vars = LpVariable.dicts("Roll Length", Pattern.lenOpts, 0, None, LpInteger)
trim = LpVariable("Trim", 0 ,None,LpInteger)
# The objective function is entered: the reduced cost of a new pattern
prob += (Pattern.cost - Pattern.trimValue*trim) - lpSum([vars[i]*duals[i] for i in Pattern.lenOpts]), "Objective"
# The conservation of length constraint is entered
prob += lpSum([vars[i]*int(i) for i in Pattern.lenOpts]) + trim == Pattern.totalRollLength, "lengthEquate"
# The problem is solved
prob.solve()
# The variable values are rounded
prob.roundSolution()
# The new pattern is written to a dictionary
varsdict = {}
newPattern = {}
for v in prob.variables():
varsdict[v.name] = v.varValue
for i,j in zip(Pattern.lenOpts,["Roll_Length_5","Roll_Length_7","Roll_Length_9"]):
newPattern[i] = int(varsdict[j])
# Check if there are more patterns which would reduce the master LP objective function further
if value(prob.objective) < -10**-5:
morePatterns = True # continue adding patterns
Patterns += [Pattern("P" + str(len(Patterns)), [newPattern[i] for i in ["5","7","9"]])]
else:
morePatterns = False # all patterns have been added
return Patterns, morePatterns

143
examples/CGcolumnwise.py Normal file
View File

@@ -0,0 +1,143 @@
"""
Columnwise Column Generation Functions
Authors: Antony Phillips, Dr Stuart Mitchell 2008
"""
# Import PuLP modeler functions
from pulp import *
class Pattern:
"""
Information on a specific pattern in the SpongeRoll Problem
"""
cost = 1
trimValue = 0.04
totalRollLength = 20
lenOpts = ["5", "7", "9"]
numPatterns = 0
def __init__(self, name, lengths = None):
self.name = name
self.lengthsdict = dict(zip(self.lenOpts,lengths))
Pattern.numPatterns += 1
def __str__(self):
return self.name
def trim(self):
return Pattern.totalRollLength - sum([int(i)*int(self.lengthsdict[i]) for i in self.lengthsdict])
def createMaster():
rollData = {#Length Demand SalePrice
"5": [150, 0.25],
"7": [200, 0.33],
"9": [300, 0.40]}
(rollDemand,surplusPrice) = splitDict(rollData)
# The variable 'prob' is created
prob = LpProblem("MasterSpongeRollProblem",LpMinimize)
# The variable 'obj' is created and set as the LP's objective function
obj = LpConstraintVar("Obj")
prob.setObjective(obj)
# The constraints are initialised and added to prob
constraints = {}
for l in Pattern.lenOpts:
constraints[l]= LpConstraintVar("Min" + str(l), LpConstraintGE, rollDemand[l])
prob += constraints[l]
# The surplus variables are created
surplusVars = []
for i in Pattern.lenOpts:
surplusVars += [LpVariable("Surplus "+ i,0,None,LpContinuous, -surplusPrice[i] * obj - constraints[i])]
return prob,obj,constraints
def addPatterns(obj,constraints,newPatterns):
# A list called Patterns is created to contain all the Pattern class
# objects created in this function call
Patterns = []
for i in newPatterns:
# The new patterns are checked to see that their length does not exceed
# the total roll length
lsum = 0
for j,k in zip(i,Pattern.lenOpts):
lsum += j * int(k)
if lsum > Pattern.totalRollLength:
raise "Length Options too large for Roll"
# The number of rolls of each length in each new pattern is printed
print "P"+str(Pattern.numPatterns),"=",i
# The patterns are instantiated as Pattern objects
Patterns += [Pattern("P" + str(Pattern.numPatterns),i)]
# The pattern variables are created
pattVars = []
for i in Patterns:
pattVars += [LpVariable("Pattern "+i.name,0,None,LpContinuous, (i.cost - Pattern.trimValue*i.trim()) * obj\
+ lpSum([constraints[l]*i.lengthsdict[l] for l in Pattern.lenOpts]))]
def masterSolve(prob,relax = True):
# Unrelaxes the Integer Constraint
if not relax:
for v in prob.variables():
v.cat = LpInteger
# The problem is solved and rounded
prob.solve()
prob.roundSolution()
if relax:
# A dictionary of dual variable values is returned
duals = {}
for i,name in zip(Pattern.lenOpts,["Min5","Min7","Min9"]):
duals[i] = prob.constraints[name].pi
return duals
else:
# A dictionary of variable values and the objective value are returned
varsdict = {}
for v in prob.variables():
varsdict[v.name] = v.varValue
return value(prob.objective), varsdict
def subSolve(duals):
# The variable 'prob' is created
prob = LpProblem("SubProb",LpMinimize)
# The problem variables are created
vars = LpVariable.dicts("Roll Length", Pattern.lenOpts, 0, None, LpInteger)
trim = LpVariable("Trim", 0 ,None,LpInteger)
# The objective function is entered: the reduced cost of a new pattern
prob += (Pattern.cost - Pattern.trimValue*trim) - lpSum([vars[i]*duals[i] for i in Pattern.lenOpts]), "Objective"
# The conservation of length constraint is entered
prob += lpSum([vars[i]*int(i) for i in Pattern.lenOpts]) + trim == Pattern.totalRollLength, "lengthEquate"
# The problem is solved
prob.solve()
# The variable values are rounded
prob.roundSolution()
newPatterns = []
# Check if there are more patterns which would reduce the master LP objective function further
if value(prob.objective) < -10**-5:
varsdict = {}
for v in prob.variables():
varsdict[v.name] = v.varValue
# Adds the new pattern to the newPatterns list
newPatterns += [[int(varsdict["Roll_Length_5"]),int(varsdict["Roll_Length_7"]),int(varsdict["Roll_Length_9"])]]
return newPatterns

View File

@@ -0,0 +1,91 @@
"""
The Computer Plant Problem for the PuLP Modeller
Authors: Antony Phillips, Dr Stuart Mitchell 2007
"""
# Import PuLP modeler functions
from pulp import *
# Creates a list of all the supply nodes
Plants = ["San Francisco",
"Los Angeles",
"Phoenix",
"Denver"]
# Creates a dictionary of lists for the number of units of supply at
# each plant and the fixed cost of running each plant
supplyData = {#Plant Supply Fixed Cost
"San Francisco":[1700, 70000],
"Los Angeles" :[2000, 70000],
"Phoenix" :[1700, 65000],
"Denver" :[2000, 70000]
}
# Creates a list of all demand nodes
Stores = ["San Diego",
"Barstow",
"Tucson",
"Dallas"]
# Creates a dictionary for the number of units of demand at each store
demand = { #Store Demand
"San Diego":1700,
"Barstow" :1000,
"Tucson" :1500,
"Dallas" :1200
}
# Creates a list of costs for each transportation path
costs = [ #Stores
#SD BA TU DA
[5, 3, 2, 6], #SF
[4, 7, 8, 10],#LA Plants
[6, 5, 3, 8], #PH
[9, 8, 6, 5] #DE
]
# Creates a list of tuples containing all the possible routes for transport
Routes = [(p,s) for p in Plants for s in Stores]
# Splits the dictionaries to be more understandable
(supply,fixedCost) = splitDict(supplyData)
# The cost data is made into a dictionary
costs = makeDict([Plants,Stores],costs,0)
# Creates the problem variables of the Flow on the Arcs
flow = LpVariable.dicts("Route",(Plants,Stores),0,None,LpInteger)
# Creates the master problem variables of whether to build the Plants or not
build = LpVariable.dicts("BuildaPlant",Plants,0,1,LpInteger)
# Creates the 'prob' variable to contain the problem data
prob = LpProblem("Computer Plant Problem",LpMinimize)
# The objective function is added to prob - The sum of the transportation costs and the building fixed costs
prob += lpSum([flow[p][s]*costs[p][s] for (p,s) in Routes])+lpSum([fixedCost[p]*build[p] for p in Plants]),"Total Costs"
# The Supply maximum constraints are added for each supply node (plant)
for p in Plants:
prob += lpSum([flow[p][s] for s in Stores])<=supply[p]*build[p], "Sum of Products out of Plant %s"%p
# The Demand minimum constraints are added for each demand node (store)
for s in Stores:
prob += lpSum([flow[p][s] for p in Plants])>=demand[s], "Sum of Products into Stores %s"%s
# The problem data is written to an .lp file
prob.writeLP("ComputerPlantProblem.lp")
# The problem is solved using PuLP's choice of Solver
prob.solve()
# The status of the solution is printed to the screen
print "Status:", LpStatus[prob.status]
# Each of the variables is printed with it's resolved optimum value
for v in prob.variables():
print v.name, "=", v.varValue
# The optimised objective function value is printed to the screen
print "Total Costs = ", value(prob.objective)

View File

@@ -0,0 +1,61 @@
"""
The Simplified Sponge Roll Problem for the PuLP Modeller
Authors: Antony Phillips, Dr Stuart Mitchell 2007
"""
# Import PuLP modeler functions
from pulp import *
# A list of all the roll lengths is created
LenOpts = ["5","7","9"]
# A dictionary of the demand for each roll length is created
rollDemand = {"5":150,
"7":200,
"9":300}
# A list of all the patterns is created
PatternNames = ["A","B","C"]
# Creates a list of the number of rolls in each pattern for each different roll length
patterns = [#A B C
[0,2,2],# 5
[1,1,0],# 7
[1,0,1] # 9
]
# The cost of each 20cm long sponge roll used
cost = 1
# The pattern data is made into a dictionary
patterns = makeDict([LenOpts,PatternNames],patterns,0)
# The problem variables of the number of each pattern to make are created
vars = LpVariable.dicts("Patt",PatternNames,0,None,LpInteger)
# The variable 'prob' is created
prob = LpProblem("Cutting Stock Problem",LpMinimize)
# The objective function is entered: the total number of large rolls used * the fixed cost of each
prob += lpSum([vars[i]*cost for i in PatternNames]),"Production Cost"
# The demand minimum constraint is entered
for i in LenOpts:
prob += lpSum([vars[j]*patterns[i][j] for j in PatternNames])>=rollDemand[i],"Ensuring enough %s cm rolls"%i
# The problem data is written to an .lp file
prob.writeLP("SpongeRollProblem.lp")
# The problem is solved using PuLP's choice of Solver
prob.solve()
# The status of the solution is printed to the screen
print "Status:", LpStatus[prob.status]
# Each of the variables is printed with it's resolved optimum value
for v in prob.variables():
print v.name, "=", v.varValue
# The optimised objective function value is printed to the screen
print "Production Costs = ", value(prob.objective)

View File

@@ -0,0 +1,78 @@
"""
The Simplified Sponge Roll Problem with Surplus and Trim for the PuLP Modeller
Authors: Antony Phillips, Dr Stuart Mitchell 2007
"""
# Import PuLP modeler functions
from pulp import *
# A list of all the roll lengths is created
LenOpts = ["5","7","9"]
rollData = {#Length Demand SalePrice
"5": [150, 0.25],
"7": [200, 0.33],
"9": [300, 0.40]}
# A list of all the patterns is created
PatternNames = ["A","B","C"]
# Creates a list of the number of rolls in each pattern for each different roll length
patterns = [#A B C
[0,2,2],# 5
[1,1,0],# 7
[1,0,1] # 9
]
# A dictionary of the number of cms of trim in each pattern is created
trim = {"A": 4,
"B": 2,
"C": 1}
# The cost of each 20cm long sponge roll used
cost = 1
# The sale value of each cm of trim
trimValue = 0.04
# The rollData is made into separate dictionaries
(rollDemand,surplusPrice) = splitDict(rollData)
# The pattern data is made into a dictionary
patterns = makeDict([LenOpts,PatternNames],patterns,0)
# The problem variables of the number of each pattern to make are created
pattVars = LpVariable.dicts("Patt",PatternNames,0,None,LpInteger)
# The problem variables of the number of surplus rolls for each length are created
surplusVars = LpVariable.dicts("Surp",LenOpts,0,None,LpInteger)
# The variable 'prob' is created
prob = LpProblem("Cutting Stock Problem",LpMinimize)
# The objective function is entered: the total number of large rolls used * the fixed cost of each minus the surplus
# sales and the trim sales
prob += lpSum([pattVars[i]*cost for i in PatternNames]) - lpSum([surplusVars[i]*surplusPrice[i] for i in LenOpts]) \
- lpSum([pattVars[i]*trim[i]*trimValue for i in PatternNames]),"Net Production Cost"
# The demand minimum constraint is entered
for i in LenOpts:
prob += lpSum([pattVars[j]*patterns[i][j] for j in PatternNames]) - surplusVars[i]\
>= rollDemand[i],"Ensuring enough %s cm rolls"%i
# The problem data is written to an .lp file
prob.writeLP("SpongeRollProblem.lp")
# The problem is solved using PuLP's choice of Solver
prob.solve()
# The status of the solution is printed to the screen
print "Status:", LpStatus[prob.status]
# Each of the variables is printed with it's resolved optimum value
for v in prob.variables():
print v.name, "=", v.varValue
# The optimised objective function value is printed to the screen
print "Production Costs = ", value(prob.objective)

View File

@@ -0,0 +1,135 @@
"""
The Full Sponge Roll Problem for the PuLP Modeller
Authors: Antony Phillips, Dr Stuart Mitchell 2007
"""
def calculatePatterns(totalRollLength,lenOpts,head):
"""
Recursively calculates the list of options lists for a cutting stock problem. The input
'tlist' is a pointer, and will be the output of the function call.
The inputs are:
totalRollLength - the length of the roll
lenOpts - a list of the sizes of remaining cutting options
head - the current list that has been passed down though the recusion
Returns the list of patterns
Authors: Bojan Blazevic, Dr Stuart Mitchell 2007
"""
if lenOpts:
patterns =[]
#take the first option off lenOpts
opt = lenOpts[0]
for rep in range(int(totalRollLength/opt)+1):
#reduce the length
l = totalRollLength - rep*opt
h = head[:]
h.append(rep)
patterns.extend(calculatePatterns(l, lenOpts[1:], h))
else:
#end of the recursion
patterns = [head]
return patterns
def makePatterns(totalRollLength,lenOpts):
"""
Makes the different cutting patterns for a cutting stock problem.
The inputs are:
totalRollLength : the length of the roll
lenOpts: a list of the sizes of cutting options as strings
Authors: Antony Phillips, Dr Stuart Mitchell 2007
"""
# calculatePatterns is called to create a list of the feasible cutting options in 'tlist'
patterns = calculatePatterns(totalRollLength,lenOpts,[])
# The list 'PatternNames' is created
PatternNames = []
for i in range(len(patterns)):
PatternNames += ["P"+str(i)]
# The amount of trim (unused material) for each pattern is calculated and added to the dictionary
# 'trim', with the reference key of the pattern name.
trim = {}
for name,pattern in zip(PatternNames,patterns):
ssum = 0
for rep,l in zip(pattern,lenOpts):
ssum += rep*l
trim[name] = totalRollLength - ssum
# The different cutting lengths are printed, and the number of each roll of that length in each
# pattern is printed below. This is so the user can see what each pattern contains.
print "Lens: %s" %lenOpts
for name,pattern in zip(PatternNames,patterns):
print name + " = %s"%pattern
return (PatternNames,patterns,trim)
# Import PuLP modeler functions
from pulp import *
# The Total Roll Length is entered
totalRollLength = 20
# The cost of each 20cm long sponge roll used
cost = 1
# The sale value of each cm of trim
trimValue = 0.04
# A list of all the roll lengths is created
LenOpts = ["5","7","9"]
rollData = {#Length Demand SalePrice
"5": [150, 0.25],
"7": [200, 0.33],
"9": [300, 0.40]}
# The pattern names and the patterns are created as lists, and the associated trim with each pattern
# is created as a dictionary. The inputs are the total roll length and the list (as integers) of
# cutting options.
(PatternNames,patterns,trim) = makePatterns(totalRollLength,[int(l) for l in LenOpts])
# The RollData is made into separate dictionaries
(rollDemand,surplusPrice) = splitDict(rollData)
# The pattern data is made into a dictionary so it can be called by patterns["7"]["P3"] for example.
# This will return the number of rolls of length "7" in pattern "P3"
patterns = makeDict([PatternNames,LenOpts],patterns,0)
# The variable 'prob' is created
prob = LpProblem("Cutting Stock Problem",LpMinimize)
# The problem variables of the number of each pattern to make are created
pattVars = LpVariable.dicts("Patt",PatternNames,0,None,LpInteger)
# The problem variables of the number of surplus rolls for each length are created
surplusVars = LpVariable.dicts("Surp",LenOpts,0,None,LpInteger)
# The objective function is entered: (the total number of large rolls used * the cost of each) - (the value of the surplus stock) - (the value of the trim)
prob += lpSum([pattVars[i]*cost for i in PatternNames]) - lpSum([surplusVars[i]*surplusPrice[i] for i in LenOpts]) - lpSum([pattVars[i]*trim[i]*trimValue for i in PatternNames]),"Net Production Cost"
# The demand minimum constraint is entered
for j in LenOpts:
prob += lpSum([pattVars[i]*patterns[i][j] for i in PatternNames]) - surplusVars[j]>=rollDemand[j],"Ensuring enough %s cm rolls"%j
# The problem data is written to an .lp file
prob.writeLP("SpongeRollProblem.lp")
# The problem is solved using PuLP's choice of Solver
prob.solve()
# The status of the solution is printed to the screen
print "Status:", LpStatus[prob.status]
# Each of the variables is printed with it's resolved optimum value
for v in prob.variables():
print v.name, "=", v.varValue
# The optimised objective function value is printed to the screen
print "Production Costs = ", value(prob.objective)

View File

@@ -0,0 +1,137 @@
"""
The Full Sponge Roll Problem using Classes for the PuLP Modeller
Authors: Antony Phillips, Dr Stuart Mitchell 2007
"""
def calculatePatterns(totalRollLength,lenOpts,head):
"""
Recursively calculates the list of options lists for a cutting stock problem. The input
'tlist' is a pointer, and will be the output of the function call.
The inputs are:
totalRollLength - the length of the roll
lenOpts - a list of the sizes of remaining cutting options
head - the current list that has been passed down though the recusion
Returns the list of patterns
Authors: Bojan Blazevic, Dr Stuart Mitchell 2007
"""
if lenOpts:
patterns =[]
#take the first option off lenOpts
opt = lenOpts[0]
for rep in range(int(totalRollLength/opt)+1):
#reduce the length
l = totalRollLength - rep*opt
h = head[:]
h.append(rep)
patterns.extend(calculatePatterns(l, lenOpts[1:], h))
else:
#end of the recursion
patterns = [head]
return patterns
def makePatterns(totalRollLength,lenOpts):
"""
Makes the different cutting patterns for a cutting stock problem.
The inputs are:
totalRollLength : the length of the roll
lenOpts: a list of the sizes of cutting options as strings
Authors: Antony Phillips, Dr Stuart Mitchell 2007
"""
# calculatePatterns is called to create a list of the feasible cutting options in 'tlist'
patternslist = calculatePatterns(totalRollLength,lenOpts,[])
# The list 'PatternNames' is created
PatternNames = []
for i in range(len(patternslist)):
PatternNames += ["P"+str(i)]
#Patterns = [0 for i in range(len(PatternNames))]
Patterns = []
for i,j in enumerate(PatternNames):
Patterns += [Pattern(j, patternslist[i])]
# The different cutting lengths are printed, and the number of each roll of that length in each
# pattern is printed below. This is so the user can see what each pattern contains.
print "Lens: %s" %lenOpts
for i in Patterns:
print i, " = %s"%[i.lengthsdict[j] for j in lenOpts]
return Patterns
class Pattern:
"""
Information on a specific pattern in the SpongeRoll Problem
"""
cost = 1
trimValue = 0.04
totalRollLength = 20
lenOpts = [5, 7, 9]
def __init__(self,name,lengths = None):
self.name = name
self.lengthsdict = dict(zip(self.lenOpts,lengths))
def __str__(self):
return self.name
def trim(self):
return Pattern.totalRollLength - sum([int(i)*self.lengthsdict[i] for i in self.lengthsdict])
# Import PuLP modeler functions
from pulp import *
rollData = {#Length Demand SalePrice
5: [150, 0.25],
7: [200, 0.33],
9: [300, 0.40]}
# The pattern names and the patterns are created as lists, and the associated trim with each pattern
# is created as a dictionary. The inputs are the total roll length and the list (as integers) of
# cutting options.
Patterns = makePatterns(Pattern.totalRollLength,Pattern.lenOpts)
# The rollData is made into separate dictionaries
(rollDemand,surplusPrice) = splitDict(rollData)
# The variable 'prob' is created
prob = LpProblem("Cutting Stock Problem",LpMinimize)
# The problem variables of the number of each pattern to make are created
pattVars = LpVariable.dicts("Patt",Patterns,0,None,LpInteger)
# The problem variables of the number of surplus rolls for each length are created
surplusVars = LpVariable.dicts("Surp",Pattern.lenOpts,0,None,LpInteger)
# The objective function is entered: (the total number of large rolls used * the cost of each) - (the value of the surplus stock) - (the value of the trim)
prob += lpSum([pattVars[i]*Pattern.cost for i in Patterns]) - lpSum([surplusVars[i]*surplusPrice[i] for i in Pattern.lenOpts]) - lpSum([pattVars[i]*i.trim()*Pattern.trimValue for i in Patterns]),"Net Production Cost"
# The demand minimum constraint is entered
for j in Pattern.lenOpts:
prob += lpSum([pattVars[i]*i.lengthsdict[j] for i in Patterns]) - surplusVars[j] >= rollDemand[j],"Ensuring enough %s cm rolls"%j
# The problem data is written to an .lp file
prob.writeLP("SpongeRollProblem.lp")
# The problem is solved
prob.solve()
# The status of the solution is printed to the screen
print "Status:", LpStatus[prob.status]
# Each of the variables is printed with it's resolved optimum value
for v in prob.variables():
print v.name, "=", v.varValue
# The optimised objective function value is printed to the screen
print "Production Costs = ", value(prob.objective)

View File

@@ -0,0 +1,43 @@
"""
The Sponge Roll Problem with Column Generation for the PuLP Modeller
Authors: Antony Phillips, Dr Stuart Mitchell 2008
"""
# Import Column Generation functions
from CG import *
# The roll data is created
rollData = {#Length Demand SalePrice
"5": [150, 0.25],
"7": [200, 0.33],
"9": [300, 0.40]}
# The boolean variable morePatterns is set to True to test for more patterns
morePatterns = True
# A list of starting patterns is created
patternslist = [[4,0,0],[0,2,0],[0,0,2]]
# The starting patterns are instantiated with the Pattern class
Patterns = []
for i in patternslist:
Patterns += [Pattern("P" + str(len(Patterns)), i)]
# This loop will be repeated until morePatterns is set to False
while morePatterns == True:
# Solve the problem as a Relaxed LP
duals = masterSolve(Patterns, rollData)
# Find another pattern
Patterns, morePatterns = subSolve(Patterns, duals)
# Re-solve as an Integer Problem
solution, varsdict = masterSolve(Patterns, rollData, relax = False)
# Display Solution
for i,j in varsdict.items():
print i, "=", j
print "objective = ", solution

View File

@@ -0,0 +1,33 @@
"""
The Sponge Roll Problem with Columnwise Column Generation for the PuLP Modeller
Authors: Antony Phillips, Dr Stuart Mitchell 2008
"""
# Import Column Generation functions
from CGcolumnwise import *
# The Master Problem is created
prob, obj, constraints = createMaster()
# A list of starting patterns is created
newPatterns = [[1,0,0],[0,1,0],[0,0,1]]
# New patterns will be added until newPatterns is an empty list
while newPatterns:
# The new patterns are added to the problem
addPatterns(obj,constraints,newPatterns)
# The master problem is solved, and the dual variables are returned
duals = masterSolve(prob)
# The sub problem is solved and a new pattern will be returned if there is one
# which can reduce the master objective function
newPatterns = subSolve(duals)
# The master problem is solved with Integer Constraints not relaxed
solution, varsdict = masterSolve(prob,relax = False)
# Display Solution
for i,j in varsdict.items():
print i, "=", j
print "objective = ", solution

111
examples/Sudoku1.py Normal file
View File

@@ -0,0 +1,111 @@
"""
The Sudoku Problem Formulation for the PuLP Modeller
Authors: Antony Phillips, Dr Stuart Mitcehll
"""
# Import PuLP modeler functions
from pulp import *
# A list of strings from "1" to "9" is created
Sequence = ["1", "2", "3", "4", "5", "6", "7", "8", "9"]
# The Vals, Rows and Cols sequences all follow this form
Vals = Sequence
Rows = Sequence
Cols = Sequence
# The boxes list is created, with the row and column index of each square in each box
Boxes =[]
for i in range(3):
for j in range(3):
Boxes += [[(Rows[3*i+k],Cols[3*j+l]) for k in range(3) for l in range(3)]]
# The prob variable is created to contain the problem data
prob = LpProblem("Sudoku Problem",LpMinimize)
# The problem variables are created
choices = LpVariable.dicts("Choice",(Vals,Rows,Cols),0,1,LpInteger)
# The arbitrary objective function is added
prob += 0, "Arbitrary Objective Function"
# A constraint ensuring that only one value can be in each square is created
for r in Rows:
for c in Cols:
prob += lpSum([choices[v][r][c] for v in Vals]) == 1, ""
# The row, column and box constraints are added for each value
for v in Vals:
for r in Rows:
prob += lpSum([choices[v][r][c] for c in Cols]) == 1,""
for c in Cols:
prob += lpSum([choices[v][r][c] for r in Rows]) == 1,""
for b in Boxes:
prob += lpSum([choices[v][r][c] for (r,c) in b]) == 1,""
# The starting numbers are entered as constraints
prob += choices["5"]["1"]["1"] == 1,""
prob += choices["6"]["2"]["1"] == 1,""
prob += choices["8"]["4"]["1"] == 1,""
prob += choices["4"]["5"]["1"] == 1,""
prob += choices["7"]["6"]["1"] == 1,""
prob += choices["3"]["1"]["2"] == 1,""
prob += choices["9"]["3"]["2"] == 1,""
prob += choices["6"]["7"]["2"] == 1,""
prob += choices["8"]["3"]["3"] == 1,""
prob += choices["1"]["2"]["4"] == 1,""
prob += choices["8"]["5"]["4"] == 1,""
prob += choices["4"]["8"]["4"] == 1,""
prob += choices["7"]["1"]["5"] == 1,""
prob += choices["9"]["2"]["5"] == 1,""
prob += choices["6"]["4"]["5"] == 1,""
prob += choices["2"]["6"]["5"] == 1,""
prob += choices["1"]["8"]["5"] == 1,""
prob += choices["8"]["9"]["5"] == 1,""
prob += choices["5"]["2"]["6"] == 1,""
prob += choices["3"]["5"]["6"] == 1,""
prob += choices["9"]["8"]["6"] == 1,""
prob += choices["2"]["7"]["7"] == 1,""
prob += choices["6"]["3"]["8"] == 1,""
prob += choices["8"]["7"]["8"] == 1,""
prob += choices["7"]["9"]["8"] == 1,""
prob += choices["3"]["4"]["9"] == 1,""
prob += choices["1"]["5"]["9"] == 1,""
prob += choices["6"]["6"]["9"] == 1,""
prob += choices["5"]["8"]["9"] == 1,""
# The problem data is written to an .lp file
prob.writeLP("Sudoku.lp")
# The problem is solved using PuLP's choice of Solver
prob.solve()
# The status of the solution is printed to the screen
print "Status:", LpStatus[prob.status]
# A file called sudokuout.txt is created/overwritten for writing to
sudokuout = open('sudokuout.txt','w')
# The solution is written to the sudokuout.txt file
for r in Rows:
if r == "1" or r == "4" or r == "7":
sudokuout.write("+-------+-------+-------+\n")
for c in Cols:
for v in Vals:
if value(choices[v][r][c])==1:
if c == "1" or c == "4" or c =="7":
sudokuout.write("| ")
sudokuout.write(v + " ")
if c == "9":
sudokuout.write("|\n")
sudokuout.write("+-------+-------+-------+")
sudokuout.close()
# The location of the solution is give to the user
print "Solution Written to sudokuout.txt"

115
examples/Sudoku2.py Normal file
View File

@@ -0,0 +1,115 @@
"""
The Looping Sudoku Problem Formulation for the PuLP Modeller
Authors: Antony Phillips, Dr Stuart Mitcehll
"""
# Import PuLP modeler functions
from pulp import *
# A list of strings from "1" to "9" is created
Sequence = ["1", "2", "3", "4", "5", "6", "7", "8", "9"]
# The Vals, Rows and Cols sequences all follow this form
Vals = Sequence
Rows = Sequence
Cols = Sequence
# The boxes list is created, with the row and column index of each square in each box
Boxes =[]
for i in range(3):
for j in range(3):
Boxes += [[(Rows[3*i+k],Cols[3*j+l]) for k in range(3) for l in range(3)]]
# The prob variable is created to contain the problem data
prob = LpProblem("Sudoku Problem",LpMinimize)
# The problem variables are created
choices = LpVariable.dicts("Choice",(Vals,Rows,Cols),0,1,LpInteger)
# The arbitrary objective function is added
prob += 0, "Arbitrary Objective Function"
# A constraint ensuring that only one value can be in each square is created
for r in Rows:
for c in Cols:
prob += lpSum([choices[v][r][c] for v in Vals]) == 1, ""
# The row, column and box constraints are added for each value
for v in Vals:
for r in Rows:
prob += lpSum([choices[v][r][c] for c in Cols]) == 1,""
for c in Cols:
prob += lpSum([choices[v][r][c] for r in Rows]) == 1,""
for b in Boxes:
prob += lpSum([choices[v][r][c] for (r,c) in b]) == 1,""
# The starting numbers are entered as constraints
prob += choices["5"]["1"]["1"] == 1,""
prob += choices["6"]["2"]["1"] == 1,""
prob += choices["8"]["4"]["1"] == 1,""
prob += choices["4"]["5"]["1"] == 1,""
prob += choices["7"]["6"]["1"] == 1,""
prob += choices["3"]["1"]["2"] == 1,""
prob += choices["9"]["3"]["2"] == 1,""
prob += choices["6"]["7"]["2"] == 1,""
prob += choices["8"]["3"]["3"] == 1,""
prob += choices["1"]["2"]["4"] == 1,""
prob += choices["8"]["5"]["4"] == 1,""
prob += choices["4"]["8"]["4"] == 1,""
prob += choices["7"]["1"]["5"] == 1,""
prob += choices["9"]["2"]["5"] == 1,""
prob += choices["6"]["4"]["5"] == 1,""
prob += choices["2"]["6"]["5"] == 1,""
prob += choices["1"]["8"]["5"] == 1,""
prob += choices["8"]["9"]["5"] == 1,""
prob += choices["5"]["2"]["6"] == 1,""
prob += choices["3"]["5"]["6"] == 1,""
prob += choices["9"]["8"]["6"] == 1,""
prob += choices["2"]["7"]["7"] == 1,""
prob += choices["6"]["3"]["8"] == 1,""
prob += choices["8"]["7"]["8"] == 1,""
prob += choices["7"]["9"]["8"] == 1,""
prob += choices["3"]["4"]["9"] == 1,""
prob += choices["1"]["5"]["9"] == 1,""
prob += choices["6"]["6"]["9"] == 1,""
prob += choices["5"]["8"]["9"] == 1,""
# The problem data is written to an .lp file
prob.writeLP("Sudoku.lp")
# A file called sudokuout.txt is created/overwritten for writing to
sudokuout = open('sudokuout.txt','w')
while True:
prob.solve()
# The status of the solution is printed to the screen
print "Status:", LpStatus[prob.status]
# The solution is printed if it was deemed "optimal" i.e met the constraints
if LpStatus[prob.status] == "Optimal":
# The solution is written to the sudokuout.txt file
for r in Rows:
if r == "1" or r == "4" or r == "7":
sudokuout.write("+-------+-------+-------+\n")
for c in Cols:
for v in Vals:
if value(choices[v][r][c])==1:
if c == "1" or c == "4" or c =="7":
sudokuout.write("| ")
sudokuout.write(v + " ")
if c == "9":
sudokuout.write("|\n")
sudokuout.write("+-------+-------+-------+\n\n")
# The constraint is added that the same solution cannot be returned again
prob += lpSum([choices[v][r][c] for v in Vals
for r in Rows
for c in Cols
if value(choices[v][r][c])==1]) <= 80
# If a new optimal solution cannot be found, we end the program
else:
break
sudokuout.close()
# The location of the solutions is give to the user
print "Solutions Written to sudokuout.txt"

41
examples/WhiskasModel1.py Normal file
View File

@@ -0,0 +1,41 @@
"""
The Simplified Whiskas Model Python Formulation for the PuLP Modeller
Authors: Antony Phillips, Dr Stuart Mitchell 2007
"""
# Import PuLP modeler functions
from pulp import *
# Create the 'prob' variable to contain the problem data
prob = LpProblem("The Whiskas Problem",LpMinimize)
# The 2 variables Beef and Chicken are created with a lower limit of zero
x1=LpVariable("ChickenPercent",0,None,LpInteger)
x2=LpVariable("BeefPercent",0)
# The objective function is added to 'prob' first
prob += 0.013*x1 + 0.008*x2, "Total Cost of Ingredients per can"
# The five constraints are entered
prob += x1 + x2 == 100, "PercentagesSum"
prob += 0.100*x1 + 0.200*x2 >= 8.0, "ProteinRequirement"
prob += 0.080*x1 + 0.100*x2 >= 6.0, "FatRequirement"
prob += 0.001*x1 + 0.005*x2 <= 2.0, "FibreRequirement"
prob += 0.002*x1 + 0.005*x2 <= 0.4, "SaltRequirement"
# The problem data is written to an .lp file
prob.writeLP("WhiskasModel.lp")
# The problem is solved using PuLP's choice of Solver
prob.solve()
# The status of the solution is printed to the screen
print "Status:", LpStatus[prob.status]
# Each of the variables is printed with it's resolved optimum value
for v in prob.variables():
print v.name, "=", v.varValue
# The optimised objective function value is printed to the screen
print "Total Cost of Ingredients per can = ", value(prob.objective)

83
examples/WhiskasModel2.py Normal file
View File

@@ -0,0 +1,83 @@
"""
The Full Whiskas Model Python Formulation for the PuLP Modeller
Authors: Antony Phillips, Dr Stuart Mitchell 2007
"""
# Import PuLP modeler functions
from pulp import *
# Creates a list of the Ingredients
Ingredients = ['CHICKEN', 'BEEF', 'MUTTON', 'RICE', 'WHEAT', 'GEL']
# A dictionary of the costs of each of the Ingredients is created
costs = {'CHICKEN': 0.013,
'BEEF': 0.008,
'MUTTON': 0.010,
'RICE': 0.002,
'WHEAT': 0.005,
'GEL': 0.001}
# A dictionary of the protein percent in each of the Ingredients is created
proteinPercent = {'CHICKEN': 0.100,
'BEEF': 0.200,
'MUTTON': 0.150,
'RICE': 0.000,
'WHEAT': 0.040,
'GEL': 0.000}
# A dictionary of the fat percent in each of the Ingredients is created
fatPercent = {'CHICKEN': 0.080,
'BEEF': 0.100,
'MUTTON': 0.110,
'RICE': 0.010,
'WHEAT': 0.010,
'GEL': 0.000}
# A dictionary of the fibre percent in each of the Ingredients is created
fibrePercent = {'CHICKEN': 0.001,
'BEEF': 0.005,
'MUTTON': 0.003,
'RICE': 0.100,
'WHEAT': 0.150,
'GEL': 0.000}
# A dictionary of the salt percent in each of the Ingredients is created
saltPercent = {'CHICKEN': 0.002,
'BEEF': 0.005,
'MUTTON': 0.007,
'RICE': 0.002,
'WHEAT': 0.008,
'GEL': 0.000}
# Create the 'prob' variable to contain the problem data
prob = LpProblem("The Whiskas Problem", LpMinimize)
# A dictionary called 'ingredient_vars' is created to contain the referenced Variables
ingredient_vars = LpVariable.dicts("Ingr",Ingredients,0)
# The objective function is added to 'prob' first
prob += lpSum([costs[i]*ingredient_vars[i] for i in Ingredients]), "Total Cost of Ingredients per can"
# The five constraints are added to 'prob'
prob += lpSum([ingredient_vars[i] for i in Ingredients]) == 100, "PercentagesSum"
prob += lpSum([proteinPercent[i] * ingredient_vars[i] for i in Ingredients]) >= 8.0, "ProteinRequirement"
prob += lpSum([fatPercent[i] * ingredient_vars[i] for i in Ingredients]) >= 6.0, "FatRequirement"
prob += lpSum([fibrePercent[i] * ingredient_vars[i] for i in Ingredients]) <= 2.0, "FibreRequirement"
prob += lpSum([saltPercent[i] * ingredient_vars[i] for i in Ingredients]) <= 0.4, "SaltRequirement"
# The problem data is written to an .lp file
prob.writeLP("WhiskasModel2.lp")
# The problem is solved using PuLP's choice of Solver
prob.solve()
# The status of the solution is printed to the screen
print "Status:", LpStatus[prob.status]
# Each of the variables is printed with it's resolved optimum value
for v in prob.variables():
print v.name, "=", v.varValue
# The optimised objective function value is printed to the screen
print "Total Cost of Ingredients per can = ", value(prob.objective)

31
examples/furniture.py Normal file
View File

@@ -0,0 +1,31 @@
"""
The Furniture problem from EngSci391 for the PuLP Modeller
Author: Dr Stuart Mitchell 2007
"""
from pulp import *
Chairs = ["A","B"]
costs = {"A":100,
"B":150}
Resources = ["Lathe","Polisher"]
capacity = {"Lathe" : 40,
"Polisher" : 48}
activity = [ #Chairs
#A B
[1, 2], #Lathe
[3, 1.5] #Polisher
]
activity = makeDict([Resources,Chairs],activity)
prob = LpProblem("Furniture Manufacturing Problem", LpMaximize)
vars = LpVariable.dicts("Number of Chairs",Chairs, lowBound = 0)
#objective
prob += lpSum([costs[c]*vars[c] for c in Chairs])
for r in Resources:
prob += lpSum([activity[r][c]*vars[c] for c in Chairs]) <= capacity[r], \
"capacity_of_%s"%r
prob.writeLP("furniture.lp")
prob.solve()
# Each of the variables is printed with it's value
for v in prob.variables():
print v.name, "=", v.varValue
# The optimised objective function value is printed to the screen
print "Total Revenue from Production = ", value(prob.objective)

52
examples/test1.py Normal file
View File

@@ -0,0 +1,52 @@
#!/usr/bin/env python
# @(#) $Jeannot: test1.py,v 1.11 2005/01/06 21:22:39 js Exp $
# Import PuLP modeler functions
from pulp import *
# A new LP problem
prob = LpProblem("test1", LpMinimize)
# Variables
# 0 <= x <= 4
x = LpVariable("x", 0, 4)
# -1 <= y <= 1
y = LpVariable("y", -1, 1)
# 0 <= z
z = LpVariable("z", 0)
# Use None for +/- Infinity, i.e. z <= 0 -> LpVariable("z", None, 0)
# Objective
prob += x + 4*y + 9*z, "obj"
# (the name at the end is facultative)
# Constraints
prob += x+y <= 5, "c1"
prob += x+z >= 10, "c2"
prob += -y+z == 7, "c3"
# (the names at the end are facultative)
# Write the problem as an LP file
prob.writeLP("test1.lp")
# Solve the problem using the default solver
prob.solve()
# Use prob.solve(GLPK()) instead to choose GLPK as the solver
# Use GLPK(msg = 0) to suppress GLPK messages
# If GLPK is not in your path and you lack the pulpGLPK module,
# replace GLPK() with GLPK("/path/")
# Where /path/ is the path to glpsol (excluding glpsol itself).
# If you want to use CPLEX, use CPLEX() instead of GLPK().
# If you want to use XPRESS, use XPRESS() instead of GLPK().
# If you want to use COIN, use COIN() instead of GLPK(). In this last case,
# two paths may be provided (one to clp, one to cbc).
# Print the status of the solved LP
print "Status:", LpStatus[prob.status]
# Print the value of the variables at the optimum
for v in prob.variables():
print v.name, "=", v.varValue
# Print the value of the objective
print "objective=", value(prob.objective)

48
examples/test2.py Normal file
View File

@@ -0,0 +1,48 @@
#!/usr/bin/env python
# @(#) $Jeannot: test2.py,v 1.16 2004/03/20 17:06:54 js Exp $
# This example is a PuLP rendition of the todd.mod problem included in the GLPK
# 4.4 distribution. It's a hard knapsack problem.
# Import PuLP modeler functions
from pulp import *
# Import math functions
from math import *
# A new LP problem
prob = LpProblem("test2", LpMaximize)
# Parameters
# Size of the problem
n = 15
k = floor(log(n)/log(2));
# A vector of n binary variables
x = LpVariable.matrix("x", range(n), 0, 1, LpInteger)
# A vector of weights
a = [pow(2,k + n + 1) + pow(2,k + n + 1 - j) + 1 for j in range(1,n+1)]
# The maximum weight
b = 0.5 * floor(sum(a))
# The total weight
weight = lpDot(a, x)
# Objective
prob += weight
# Constraint
prob += weight <= b
# Resolution
prob.solve()
# Print the status of the solved LP
print "Status:", LpStatus[prob.status]
# Print the value of the variables at the optimum
for v in prob.variables():
print v.name, "=", v.varValue
# Print the value of the objective
print "objective=", value(prob.objective)

122
examples/test3.py Normal file
View File

@@ -0,0 +1,122 @@
#!/usr/bin/env python
# @(#) $Jeannot: test3.py,v 1.3 2004/03/20 17:06:54 js Exp $
# Deterministic generation planning using mixed integer linear programming.
# The goal is to minimise the cost of generation while satisfaying demand
# using a few thermal units and an hydro unit.
# The thermal units have a proportional cost and a startup cost.
# The hydro unit has an initial storage.
from pulp import *
from math import *
prob = LpProblem("test3", LpMinimize)
# The number of time steps
tmax = 9
# The number of thermal units
units = 5
# The minimum demand
dmin = 10.0
# The maximum demand
dmax = 150.0
# The maximum thermal production
tpmax = 150.0
# The maximum hydro production
hpmax = 100.0
# Initial hydro storage
sini = 50.0
# Time range
time = range(tmax)
# Time range (and one more step for the last state of plants)
xtime = range(tmax+1)
# Units range
unit = range(units)
# The demand
demand = [dmin+(dmax-dmin)*0.5 + 0.5*(dmax-dmin)*sin(4*t*2*3.1415/tmax) for t in time]
# Maximum output for the thermal units
pmax = [tpmax / units for i in unit]
# Minimum output for the thermal units
pmin = [tpmax / (units*3.0) for i in unit]
# Proportional cost of the thermal units
costs = [i+1 for i in unit]
# Startup cost of the thermal units.
startupcosts = [100*(i+1) for i in unit]
# Production variables for each time step and each thermal unit.
p = LpVariable.matrix("p", (time, unit), 0)
for t in time:
for i in unit:
p[t][i].upBound = pmax[i]
# State (started/stopped) variables for each time step and each thermal unit
d = LpVariable.matrix("d", (xtime, unit), 0, 1, LpInteger)
# Production constraint relative to the unit state (started/stoped)
for t in time:
for i in unit:
# If the unit is not started (d==0) then p<=0 else p<=pmax
prob += p[t][i] <= pmax[i]*d[t][i]
# If the unit is not started then p>=0 else p>= pmin
prob += p[t][i] >= pmin[i]*d[t][i]
# Startup variables: 1 if the unit will be started next time step
u = LpVariable.matrix("u", (time, unit), 0)
# Dynamic startup constraints
# Initialy, all groups are started
for t in time:
for i in unit:
# u>=1 if the unit is started next time step
prob += u[t][i] >= d[t+1][i] - d[t][i]
# Storage for the hydro plant (must not go below 0)
s = LpVariable.matrix("s", xtime, 0)
# Initial storage
s[0] = sini
# Hydro production
ph = [s[t]-s[t+1] for t in time]
for t in time:
# Must be positive (no pumping)
prob += ph[t] >= 0
# And lower than hpmax
prob += ph[t] <= hpmax
# Total production must equal demand
for t in time:
prob += demand[t] == lpSum(p[t]) + ph[t]
# Thermal production cost
ctp = lpSum([lpSum([p[t][i] for t in time])*costs[i] for i in unit])
# Startup costs
cts = lpSum([lpSum([u[t][i] for t in time])*startupcosts[i] for i in unit])
# The objective is the total cost
prob += ctp + cts
# Solve the problem
prob.solve()
print "Minimum total cost:", prob.objective.value()
# Print the results
print " D S U ",
for i in unit: print " T%d " %i,
print
for t in time:
# Demand, hydro storage, hydro production
print "%5.1f" % demand[t], "%5.1f" % value(s[t]), "%5.1f" % value(ph[t]),
for i in unit:
# Thermal production
print "%4.1f" % value(p[t][i]),
# The state of the unit
if value(d[t][i]): print "+",
else: print "-",
# Wether the unit will be started
if value(u[t][i]): print "*",
else: print " ",
print

59
examples/test4.py Normal file
View File

@@ -0,0 +1,59 @@
#!/usr/bin/env python
# @(#) $Jeannot: test4.py,v 1.5 2004/03/20 17:06:54 js Exp $
# A two stage stochastic planification problem
# Example taken from:
# "On Optimal Allocation of Indivisibles under Incertainty"
# Vladimir I. Norkin, Yuri M. Ermoliev, Andrzej Ruszczynski
# IIASA, WP-94-021, April 1994 (revised October 1995).
from pulp import *
from random import *
C = 50
B = 500 # Resources available for the two years
s = 20 # Number of scenarios
n = 10 # Number of projects
N = range(n)
S = range(s)
# First year costs
c = [randint(0,C) for i in N]
# First year resources
d = [randint(0,C) for i in N]
# a=debut, b=taille
interval = [[(randint(0,C), randint(0,C)) for i in N] for j in S]
# Final earnings
q = [[randint(ai, ai+bi) for ai,bi in ab] for ab in interval]
# Second year resources
delta = [[randint(ai, ai+bi) for ai,bi in ab] for ab in interval]
# Variables
# x : Whether or not to start a project
x = LpVariable.matrix("x", (N,), 0, 1, LpInteger)
# y : Whether or not to finish it, in each scenario
y = LpVariable.matrix("y", (S, N), 0, 1, LpInteger)
# Problem
lp = LpProblem("Planification", LpMinimize)
# Objective: expected earnings
lp += lpDot(x, c) - lpDot(q, y)/float(s)
# Resources constraints for each scenario
for j in S:
lp += lpDot(d, x) + lpDot(delta[j], y[j]) <= B
# We can only finish a project that was started
for i in N:
for j in S:
lp += y[j][i] <= x[i]
# Resolution
lp.solve()
# Solution printing
for i in N:
print x[i], "=", x[i].value()

53
examples/test5.py Normal file
View File

@@ -0,0 +1,53 @@
#!/usr/bin/env python
# @(#) $Jeannot: test5.py,v 1.2 2004/03/20 17:06:54 js Exp $
# Market splitting problems from:
# G. Cornuejols, M. Dawande, A class of hard small 0-1 programs, 1998.
# With m>=4, these problems are often *very* difficult.
# Import PuLP modeler functions
from pulp import *
# Import random number generation functions
from random import *
# A new LP problem
prob = LpProblem("test5", LpMinimize)
# Parameters
# Number of constraints
m = 3
# Size of the integers involved
D = 100
# Number of variables
n = 10*(m-1)
# A vector of n binary variables
x = LpVariable.matrix("x", range(n), 0, 1, LpInteger)
# Slacks
s = LpVariable.matrix("s", range(m), 0)
w = LpVariable.matrix("w", range(m), 0)
# Objective
prob += lpSum(s) + lpSum(w)
# Constraints
d = [[randint(0,D) for i in range(n)] for j in range(m)]
for j in range(m):
prob += lpDot(d[j], x) + s[j] - w[j] == lpSum(d[j])/2
# Resolution
prob.solve()
# Print the status of the solved LP
print "Status:", LpStatus[prob.status]
# Print the value of the variables at the optimum
for v in prob.variables():
print v.name, "=", v.varValue
# Print the value of the objective
print "objective=", value(prob.objective)

61
examples/test6.py Normal file
View File

@@ -0,0 +1,61 @@
#!/usr/bin/env python
# @(#) $Jeannot: test1.py,v 1.11 2005/01/06 21:22:39 js Exp $
# Copywrite 2007 Stuart Mitchell
# Columnwise modelling
# Import PuLP modeler functions
from pulp import *
# A new LP problem
prob = LpProblem("test6", LpMinimize)
# objective
obj = LpConstraintVar("obj")
# constraints
a = LpConstraintVar("Ca", LpConstraintLE, 5)
b = LpConstraintVar("Cb", LpConstraintGE, 10)
c = LpConstraintVar("Cc", LpConstraintEQ, 7)
prob.setObjective(obj)
prob += a
prob += b
prob += c
# Variables
# 0 <= x <= 4
x = LpVariable("x", 0, 4, LpContinuous, obj + a + b)
# -1 <= y <= 1
y = LpVariable("y", -1, 1, LpContinuous, 4*obj + a - c)
# 0 <= z
z = LpVariable("z", 0, None, LpContinuous, 9*obj + b + c)
# Use None for +/- Infinity, i.e. z <= 0 -> LpVariable("z", None, 0)
# Write the problem as an LP file
prob.writeLP("test6.lp")
# Solve the problem using the default solver
prob.solve()
# Use prob.solve(GLPK()) instead to choose GLPK as the solver
# Use GLPK(msg = 0) to suppress GLPK messages
# If GLPK is not in your path and you lack the pulpGLPK module,
# replace GLPK() with GLPK("/path/")
# Where /path/ is the path to glpsol (excluding glpsol itself).
# If you want to use CPLEX, use CPLEX() instead of GLPK().
# If you want to use XPRESS, use XPRESS() instead of GLPK().
# If you want to use COIN, use COIN() instead of GLPK(). In this last case,
# two paths may be provided (one to clp, one to cbc).
# Print the status of the solved LP
print "Status:", LpStatus[prob.status]
# Print the value of the variables at the optimum
for v in prob.variables():
print v.name, "=", v.varValue
# Print the value of the objective
print "objective=", value(prob.objective)

35
examples/test7.py Normal file
View File

@@ -0,0 +1,35 @@
#!/usr/bin/env python
# Test for output of dual variables
# Import PuLP modeler functions
from pulp import *
# A new LP problem
prob = LpProblem("test7", LpMinimize)
x = LpVariable("x", 0, 4)
y = LpVariable("y", -1, 1)
z = LpVariable("z", 0)
prob += x + 4*y + 9*z, "obj"
prob += x + y <= 5, "c1"
prob += x + z >= 10,"c2"
prob += -y+ z == 7,"c3"
prob.writeLP("test7.lp")
prob.solve()
print "Status:", LpStatus[prob.status]
for v in prob.variables():
print v.name, "=", v.varValue, "\tReduced Cost =", v.dj
print "objective=", value(prob.objective)
print "\nSensitivity Analysis\nConstraint\t\tShadow Price\tSlack"
for name, c in prob.constraints.items():
print name, ":", c, "\t", c.pi, "\t\t", c.slack

50
examples/wedding.py Normal file
View File

@@ -0,0 +1,50 @@
"""
A set partitioning model of a wedding seating problem
Authors: Stuart Mitchell 2009
"""
import pulp
max_tables = 5
max_table_size = 4
guests = 'A B C D E F G I J K L M N O P Q R'.split()
def happiness(table):
"""
Find the happiness of the table
- by calculating the maximum distance between the letters
"""
return abs(ord(table[0]) - ord(table[-1]))
#create list of all possible tables
possible_tables = [tuple(c) for c in pulp.allcombinations(guests,
max_table_size)]
#create a binary variable to state that a table setting is used
x = pulp.LpVariable.dicts('table', possible_tables,
lowBound = 0,
upBound = 1,
cat = pulp.LpInteger)
seating_model = pulp.LpProblem("Wedding Seating Model", pulp.LpMinimize)
seating_model += sum([happiness(table) * x[table] for table in possible_tables])
#specify the maximum number of tables
seating_model += sum([x[table] for table in possible_tables]) <= max_tables, \
"Maximum_number_of_tables"
#A guest must seated at one and only one table
for guest in guests:
seating_model += sum([x[table] for table in possible_tables
if guest in table]) == 1, "Must_seat_%s"%guest
seating_model.solve()
print "The choosen tables are out of a total of %s:"%len(possible_tables)
for table in possible_tables:
if x[table].value() == 1.0:
print table

278
ez_setup.py Normal file
View File

@@ -0,0 +1,278 @@
#!python
"""Bootstrap setuptools installation
If you want to use setuptools in your package's setup.py, just include this
file in the same directory with it, and add this to the top of your setup.py::
from ez_setup import use_setuptools
use_setuptools()
If you want to require a specific version of setuptools, set a download
mirror, or use an alternate download directory, you can do so by supplying
the appropriate options to ``use_setuptools()``.
This file can also be run as a script to install or upgrade setuptools.
"""
import sys
DEFAULT_VERSION = "0.6c11"
DEFAULT_URL = "http://pypi.python.org/packages/%s/s/setuptools/" % sys.version[:3]
md5_data = {
'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca',
'setuptools-0.6b1-py2.4.egg': 'b79a8a403e4502fbb85ee3f1941735cb',
'setuptools-0.6b2-py2.3.egg': '5657759d8a6d8fc44070a9d07272d99b',
'setuptools-0.6b2-py2.4.egg': '4996a8d169d2be661fa32a6e52e4f82a',
'setuptools-0.6b3-py2.3.egg': 'bb31c0fc7399a63579975cad9f5a0618',
'setuptools-0.6b3-py2.4.egg': '38a8c6b3d6ecd22247f179f7da669fac',
'setuptools-0.6b4-py2.3.egg': '62045a24ed4e1ebc77fe039aa4e6f7e5',
'setuptools-0.6b4-py2.4.egg': '4cb2a185d228dacffb2d17f103b3b1c4',
'setuptools-0.6c1-py2.3.egg': 'b3f2b5539d65cb7f74ad79127f1a908c',
'setuptools-0.6c1-py2.4.egg': 'b45adeda0667d2d2ffe14009364f2a4b',
'setuptools-0.6c10-py2.3.egg': 'ce1e2ab5d3a0256456d9fc13800a7090',
'setuptools-0.6c10-py2.4.egg': '57d6d9d6e9b80772c59a53a8433a5dd4',
'setuptools-0.6c10-py2.5.egg': 'de46ac8b1c97c895572e5e8596aeb8c7',
'setuptools-0.6c10-py2.6.egg': '58ea40aef06da02ce641495523a0b7f5',
'setuptools-0.6c11-py2.3.egg': '2baeac6e13d414a9d28e7ba5b5a596de',
'setuptools-0.6c11-py2.4.egg': 'bd639f9b0eac4c42497034dec2ec0c2b',
'setuptools-0.6c11-py2.5.egg': '64c94f3bf7a72a13ec83e0b24f2749b2',
'setuptools-0.6c11-py2.6.egg': 'bfa92100bd772d5a213eedd356d64086',
'setuptools-0.6c2-py2.3.egg': 'f0064bf6aa2b7d0f3ba0b43f20817c27',
'setuptools-0.6c2-py2.4.egg': '616192eec35f47e8ea16cd6a122b7277',
'setuptools-0.6c3-py2.3.egg': 'f181fa125dfe85a259c9cd6f1d7b78fa',
'setuptools-0.6c3-py2.4.egg': 'e0ed74682c998bfb73bf803a50e7b71e',
'setuptools-0.6c3-py2.5.egg': 'abef16fdd61955514841c7c6bd98965e',
'setuptools-0.6c4-py2.3.egg': 'b0b9131acab32022bfac7f44c5d7971f',
'setuptools-0.6c4-py2.4.egg': '2a1f9656d4fbf3c97bf946c0a124e6e2',
'setuptools-0.6c4-py2.5.egg': '8f5a052e32cdb9c72bcf4b5526f28afc',
'setuptools-0.6c5-py2.3.egg': 'ee9fd80965da04f2f3e6b3576e9d8167',
'setuptools-0.6c5-py2.4.egg': 'afe2adf1c01701ee841761f5bcd8aa64',
'setuptools-0.6c5-py2.5.egg': 'a8d3f61494ccaa8714dfed37bccd3d5d',
'setuptools-0.6c6-py2.3.egg': '35686b78116a668847237b69d549ec20',
'setuptools-0.6c6-py2.4.egg': '3c56af57be3225019260a644430065ab',
'setuptools-0.6c6-py2.5.egg': 'b2f8a7520709a5b34f80946de5f02f53',
'setuptools-0.6c7-py2.3.egg': '209fdf9adc3a615e5115b725658e13e2',
'setuptools-0.6c7-py2.4.egg': '5a8f954807d46a0fb67cf1f26c55a82e',
'setuptools-0.6c7-py2.5.egg': '45d2ad28f9750e7434111fde831e8372',
'setuptools-0.6c8-py2.3.egg': '50759d29b349db8cfd807ba8303f1902',
'setuptools-0.6c8-py2.4.egg': 'cba38d74f7d483c06e9daa6070cce6de',
'setuptools-0.6c8-py2.5.egg': '1721747ee329dc150590a58b3e1ac95b',
'setuptools-0.6c9-py2.3.egg': 'a83c4020414807b496e4cfbe08507c03',
'setuptools-0.6c9-py2.4.egg': '260a2be2e5388d66bdaee06abec6342a',
'setuptools-0.6c9-py2.5.egg': 'fe67c3e5a17b12c0e7c541b7ea43a8e6',
'setuptools-0.6c9-py2.6.egg': 'ca37b1ff16fa2ede6e19383e7b59245a',
}
import sys, os
try: from hashlib import md5
except ImportError: from md5 import md5
def _validate_md5(egg_name, data):
if egg_name in md5_data:
digest = md5(data).hexdigest()
if digest != md5_data[egg_name]:
print >>sys.stderr, (
"md5 validation of %s failed! (Possible download problem?)"
% egg_name
)
sys.exit(2)
return data
def use_setuptools(
version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
download_delay=15
):
"""Automatically find/download setuptools and make it available on sys.path
`version` should be a valid setuptools version number that is available
as an egg for download under the `download_base` URL (which should end with
a '/'). `to_dir` is the directory where setuptools will be downloaded, if
it is not already available. If `download_delay` is specified, it should
be the number of seconds that will be paused before initiating a download,
should one be required. If an older version of setuptools is installed,
this routine will print a message to ``sys.stderr`` and raise SystemExit in
an attempt to abort the calling script.
"""
was_imported = 'pkg_resources' in sys.modules or 'setuptools' in sys.modules
def do_download():
egg = download_setuptools(version, download_base, to_dir, download_delay)
sys.path.insert(0, egg)
import setuptools; setuptools.bootstrap_install_from = egg
try:
import pkg_resources
except ImportError:
return do_download()
try:
pkg_resources.require("setuptools>="+version); return
except pkg_resources.VersionConflict, e:
if was_imported:
print >>sys.stderr, (
"The required version of setuptools (>=%s) is not available, and\n"
"can't be installed while this script is running. Please install\n"
" a more recent version first, using 'easy_install -U setuptools'."
"\n\n(Currently using %r)"
) % (version, e.args[0])
sys.exit(2)
else:
del pkg_resources, sys.modules['pkg_resources'] # reload ok
return do_download()
except pkg_resources.DistributionNotFound:
return do_download()
def download_setuptools(
version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
delay = 15
):
"""Download setuptools from a specified location and return its filename
`version` should be a valid setuptools version number that is available
as an egg for download under the `download_base` URL (which should end
with a '/'). `to_dir` is the directory where the egg will be downloaded.
`delay` is the number of seconds to pause before an actual download attempt.
"""
import urllib2, shutil
egg_name = "setuptools-%s-py%s.egg" % (version,sys.version[:3])
url = download_base + egg_name
saveto = os.path.join(to_dir, egg_name)
src = dst = None
if not os.path.exists(saveto): # Avoid repeated downloads
try:
from distutils import log
if delay:
log.warn("""
---------------------------------------------------------------------------
This script requires setuptools version %s to run (even to display
help). I will attempt to download it for you (from
%s), but
you may need to enable firewall access for this script first.
I will start the download in %d seconds.
(Note: if this machine does not have network access, please obtain the file
%s
and place it in this directory before rerunning this script.)
---------------------------------------------------------------------------""",
version, download_base, delay, url
); from time import sleep; sleep(delay)
log.warn("Downloading %s", url)
src = urllib2.urlopen(url)
# Read/write all in one block, so we don't create a corrupt file
# if the download is interrupted.
data = _validate_md5(egg_name, src.read())
dst = open(saveto,"wb"); dst.write(data)
finally:
if src: src.close()
if dst: dst.close()
return os.path.realpath(saveto)
def main(argv, version=DEFAULT_VERSION):
"""Install or upgrade setuptools and EasyInstall"""
try:
import setuptools
except ImportError:
egg = None
try:
egg = download_setuptools(version, delay=0)
sys.path.insert(0,egg)
from setuptools.command.easy_install import main
return main(list(argv)+[egg]) # we're done here
finally:
if egg and os.path.exists(egg):
os.unlink(egg)
else:
if setuptools.__version__ == '0.0.1':
print >>sys.stderr, (
"You have an obsolete version of setuptools installed. Please\n"
"remove it from your system entirely before rerunning this script."
)
sys.exit(2)
req = "setuptools>="+version
import pkg_resources
try:
pkg_resources.require(req)
except pkg_resources.VersionConflict:
try:
from setuptools.command.easy_install import main
except ImportError:
from easy_install import main
main(list(argv)+[download_setuptools(delay=0)])
sys.exit(0) # try to force an exit
else:
if argv:
from setuptools.command.easy_install import main
main(argv)
else:
print "Setuptools version",version,"or greater has been installed."
print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)'
def update_md5(filenames):
"""Update our built-in md5 registry"""
import re
for name in filenames:
base = os.path.basename(name)
f = open(name,'rb')
md5_data[base] = md5(f.read()).hexdigest()
f.close()
data = [" %r: %r,\n" % it for it in md5_data.items()]
data.sort()
repl = "".join(data)
import inspect
srcfile = inspect.getsourcefile(sys.modules[__name__])
f = open(srcfile, 'rb'); src = f.read(); f.close()
match = re.search("\nmd5_data = {\n([^}]+)}", src)
if not match:
print >>sys.stderr, "Internal error!"
sys.exit(2)
src = src[:match.start(1)] + repl + src[match.end(1):]
f = open(srcfile,'w')
f.write(src)
f.close()
if __name__=='__main__':
if len(sys.argv)>2 and sys.argv[1]=='--md5update':
update_md5(sys.argv[2:])
else:
main(sys.argv[1:])

55
setup.py Normal file
View File

@@ -0,0 +1,55 @@
#!/usr/bin/env/python
"""
Setup script for PuLP added by Stuart Mitchell 2007
Copyright 2007 Stuart Mitchell
"""
from ez_setup import use_setuptools
use_setuptools()
from setuptools import setup
Description = open('README').read()
License = open('LICENSE').read()
Version = open('VERSION').read().strip()
setup(name="PuLP",
version=Version,
description="""
PuLP is an LP modeler written in python. PuLP can generate MPS or LP files
and call GLPK, COIN CLP/CBC, CPLEX, and GUROBI to solve linear
problems.
""",
long_description = Description,
license = License,
keywords = ["Optimization", "Linear Programming", "Operations Research"],
author="J.S. Roy and S.A. Mitchell",
author_email="s.mitchell@auckland.ac.nz",
url="http://pulp-or.googlecode.com/",
classifiers = ['Development Status :: 5 - Production/Stable',
'Environment :: Console',
'Intended Audience :: Science/Research',
'License :: OSI Approved :: BSD License',
'Natural Language :: English',
'Programming Language :: Python',
'Topic :: Scientific/Engineering :: Mathematics',
],
#ext_modules = [pulpCOIN],
package_dir={'':'src'},
packages = ['pulp', 'pulp.solverdir'],
package_data = {'pulp' : ["AUTHORS","LICENSE",
"pulp.cfg.linux",
"pulp.cfg.win",
"LICENSE.CoinMP.txt",
"AUTHORS.CoinMP.txt",
"README.CoinMP.txt",
],
'pulp.solverdir' : ['*','*.*']},
install_requires = ['pyparsing>=1.5.2'],
entry_points = ("""
[console_scripts]
pulptest = pulp:pulpTestAll
pulpdoctest = pulp:pulpDoctest
"""
),
)

21
solvers.cfg Normal file
View File

@@ -0,0 +1,21 @@
[buildout]
extends = buildout.cfg
[glpk]
recipe = zc.recipe.cmmi
url = http://ftp.gnu.org/gnu/glpk/glpk-4.42.tar.gz
[cbc]
recipe = zc.recipe.cmmi
url = http://www.coin-or.org/download/source/Cbc/Cbc-2.4.0.tgz
[coinMP]
recipe = zc.recipe.cmmi
#url = http://pulp-or.googlecode.com/files/CoinMP-1.4.0patched.tar.gz
url = http://www.coin-or.org/download/source/CoinMP/CoinMP-1.4.0.tgz
#patch = ${buildout:directory}/patches/CoinMP-1.4.0.patch
[install-coinMP]
recipe = plone.recipe.command
command = cp ${buildout:directory}/parts/coinMP/lib/* ${buildout:directory}/src/pulp/solverdir/
update-command = cp ${buildout:directory}/parts/coinMP/lib/* ${buildout:directory}/src/pulp/solverdir/

37
src/pulp/__init__.py Normal file
View File

@@ -0,0 +1,37 @@
# PuLP : Python LP Modeler
# Version 1.20
# Copyright (c) 2002-2005, Jean-Sebastien Roy (js@jeannot.org)
# Modifications Copyright (c) 2007- Stuart Anthony Mitchell (s.mitchell@auckland.ac.nz)
# $Id: __init__.py 1791 2008-04-23 22:54:34Z smit023 $
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
Module file that imports all of the pulp functions
Copyright 2007- Stuart Mitchell (s.mitchell@auckland.ac.nz)
"""
from pulp import *
from amply import *
__doc__ = pulp.__doc__
import tests

782
src/pulp/amply.py Normal file
View File

@@ -0,0 +1,782 @@
#! /usr/bin/env python
# Amply: a GNU MathProg data-parser
# Copyright (c) 2010, Q. Lim (qlim001@aucklanduni.ac.nz)
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
Amply: a GNU MathProg data-parser
This module implements a parser for a subset of the GNU MathProg language,
namely parameter and set data records.
Amply uses the Pyparsing library to parse input:
http://pyparsing.wikispaces.com
Usage:
Create an Amply object, optionally passing in a string to parse.
>>> a = Amply("param T := 3;")
Symbols that are defined can be accessed as attributes or items.
>>> print a.T
3
>>> print a['T']
3
The load_string and load_file methods can be used to parse additional data
>>> a.load_string("set N := 1 2 3;")
>>> a.load_file(open('some_file.dat'))
An Amply object can be constructed from a file using Amply.from_file
>>> a = Amply.from_file(open('some_file.dat'))
How it works:
The Amply class parses the input using Pyparsing. This results in a list
of Stmt objects, each representing a MathProg statement. The statements
are then evaluated by calling their eval() method.
"""
try:
import pyparsing
except ImportError:
pass
else:
from pyparsing import alphas, nums, alphanums, delimitedList, oneOf
from pyparsing import Combine, Dict, Forward, Group, Literal, NotAny
from pyparsing import OneOrMore, Optional, ParseResults, QuotedString
from pyparsing import StringEnd, Suppress, Word, ZeroOrMore
from itertools import chain
__all__ = ['Amply', 'AmplyError']
class AmplyObject(object):
"""
Represents the value of some object (e.g. a Set object
or Parameter object
"""
class AmplyStmt(object):
"""
Represents a statement that has been parsed
Statements implement an eval method. When the eval method is called, the
Stmt object is responsible for modifying the Amply object that
gets passed in appropriately (i.e. by adding or modifying a symbol)
"""
def eval(self, amply): # pragma: no coverage
raise NotImplementedError()
class NoDefault(object):
"""
Sentinel
"""
class AmplyError(Exception):
"""
Amply Exception Class
"""
def chunk(it, n):
"""
Yields n-tuples from iterator
"""
c = []
for i, x in enumerate(it):
c.append(x)
if (i + 1) % n == 0:
yield tuple(c)
c = []
if c:
yield tuple(c)
def access_data(curr_dict, keys, default=NoDefault):
"""
Convenience method for walking down a series of nested dictionaries
keys is a tuple of strings
access_data(dict, ('key1', 'key2', 'key3') is equivalent to
dict['key1']['key2']['key3']
All dictionaries must exist, but the last dictionary in the hierarchy
does not have to contain the final key, if default is set.
"""
if keys in curr_dict:
return curr_dict[keys]
if isinstance(keys, tuple):
for sym in keys[:-1]:
curr_dict = curr_dict[sym]
r = curr_dict.get(keys[-1], default)
if r is not NoDefault:
return r
if default is not NoDefault:
return default
raise KeyError()
def transpose(data):
"""
Transpose a matrix represented as a dict of dicts
"""
rows = data.keys()
cols = set()
for d in data.values():
cols.update(d.keys())
d = {}
for col in cols:
d[col] = {}
for row in rows:
d[col][row] = data[row][col]
return d
class SetDefStmt(AmplyStmt):
"""
Represents a set definition statement
"""
def __init__(self, tokens):
assert (tokens[0] == 'set')
self.name = tokens[1]
self.dimen = tokens.get('dimen', None)
self.subscripts = len(tokens.get('subscripts', ()))
def __repr__(self): # pragma: no cover
return '<%s: %s[%s]>' % (self.__class__.__name__, self.name,
self.dimen)
def eval(self, amply):
set_obj = SetObject(subscripts=self.subscripts, dimen=self.dimen)
amply._addSymbol(self.name, set_obj)
class SetStmt(AmplyStmt):
"""
Represents a set statement
"""
def __init__(self, tokens):
assert(tokens[0] == 'set')
self.name = tokens[1]
self.records = tokens.get('records')
self.member = tokens.get('member', None)
def __repr__(self):
return '<%s: %s[%s] = %s>' % (self.__class__.__name__, self.name,
self.member, self.records)
def eval(self, amply):
if self.name in amply.symbols:
obj = amply.symbols[self.name]
assert isinstance(obj, SetObject)
else:
obj = SetObject()
obj.addData(self.member, self.records)
amply._addSymbol(self.name, obj)
class SliceRecord(object):
"""
Represents a parameter or set slice record
"""
def __init__(self, tokens):
self.components = tuple(tokens)
def __repr__(self):
return '<%s: %s>' % (self.__class__.__name__, self.components)
class TabularRecord(object):
"""
Represents a parameter tabular record
"""
def __init__(self, tokens):
self._columns = tokens.columns
self._data = tokens.data
self.transposed = False
def setTransposed(self, t):
self.transposed = t
def _rows(self):
c = Chunker(self._data)
while c.notEmpty():
row_label = c.chunk()
data = c.chunk(len(self._columns))
yield row_label, data
def data(self):
d = {}
for row, data in self._rows():
d[row] = {}
for col, value in zip(self._columns, data):
d[row][col] = value
if self.transposed:
return transpose(d)
else:
return d
def __repr__(self):
return '<%s: %s>' % (self.__class__.__name__, self.data())
class MatrixData(TabularRecord):
"""
Represents a set matrix data record
"""
def _rows(self):
for row in self._data:
yield row[0], row[1:]
def data(self):
d = []
for row_label, data in self._rows():
for col, value in zip(self._columns, data):
if value == '+':
if self.transposed:
d.append((col, row_label))
else:
d.append((row_label, col))
return d
class ParamStmt(AmplyStmt):
"""
Represents a parameter statement
"""
def __init__(self, tokens):
assert(tokens[0] == 'param')
self.name = tokens.name
self.records = tokens.records
self.default = tokens.get('default', 0)
def __repr__(self):
return '<%s: %s = %s>' % (self.__class__.__name__, self.name,
self.records)
def eval(self, amply):
if self.name in amply.symbols:
obj = amply.symbols[self.name]
assert isinstance(obj, ParamObject)
else:
obj = ParamObject()
if obj.subscripts == 0:
assert len(self.records) == 1
assert len(self.records[0]) == 1
amply._addSymbol(self.name, self.records[0][0])
else:
obj.addData(self.records.asList(), default=self.default)
amply._addSymbol(self.name, obj)
class Chunker(object):
"""
Chunker class - used to consume tuples from
an iterator
"""
def __init__(self, it):
"""
it is a sequence or iterator
"""
self.it = iter(it)
self.empty = False
self.next = None
self._getNext()
def _getNext(self):
"""
basically acts as a 1 element buffer so that
we can detect if we've reached the end of the
iterator
"""
old = self.next
try:
self.next = self.it.next()
except StopIteration:
self.empty = True
return old
def notEmpty(self):
"""
Test if the iterator has reached the end
"""
return not self.empty
def chunk(self, n=None):
"""
Return a list with the next n elements from the iterator,
or the next element if n is None
"""
if n is None:
return self._getNext()
return [self._getNext() for i in range(n)]
class ParamTabbingStmt(AmplyStmt):
"""
Represents a parameter tabbing data statement
"""
def __init__(self, tokens):
assert(tokens[0] == 'param')
self.default = tokens.get('default', 0)
self.params = tokens.params
self.data = tokens.data
def eval(self, amply):
for i, param_name in enumerate(self.params):
if param_name in amply.symbols:
obj = amply.symbols[param_name]
else:
raise AmplyError("Param %s not previously defined" %
param_name)
for subs, data in self._rows(obj.subscripts):
obj.setValue(subs, data[i])
def _rows(self, n_subscripts):
c = Chunker(self.data)
while c.notEmpty():
subscripts = c.chunk(n_subscripts)
data = c.chunk(len(self.params))
yield (subscripts, data)
class ParamDefStmt(AmplyStmt):
"""
Represents a parameter definition
"""
def __init__(self, tokens):
assert(tokens[0] == 'param')
self.name = tokens[1]
self.subscripts = tokens.get('subscripts')
self.default = tokens.get('default', NoDefault)
def eval(self, amply):
def _getDimen(symbol):
s = amply[symbol]
if s is None or s.dimen is None:
return 1
return s.dimen
num_subscripts = sum(_getDimen(s) for s in self.subscripts)
amply._addSymbol(self.name, ParamObject(num_subscripts, self.default))
class ParamObject(AmplyObject):
def __init__(self, subscripts=0, default=NoDefault):
self.subscripts = subscripts
self.default = default
self.data = {}
# initial slice is all *'s
self._setSlice(SliceRecord(['*'] * self.subscripts))
def addData(self, data, default=0):
def _v(v):
if v == '.':
return default
return v
for record in data:
if isinstance(record, SliceRecord):
self._setSlice(record)
elif isinstance(record, list):
# a plain data record
rec_len = len(self.free_indices) + 1
if len(record) % rec_len != 0:
raise AmplyError("Incomplete data record, expecting %d"
" subscripts per value" %
len(self.free_indices))
for c in chunk(record, len(self.free_indices) + 1):
self.setValue(c[:-1], _v(c[-1]))
elif isinstance(record, TabularRecord):
record_data = record.data()
for row_symbol in record_data:
for col_symbol, value in record_data[row_symbol].items():
self.setValue((row_symbol, col_symbol), _v(value))
def _setSlice(self, slice):
self.current_slice = list(slice.components) #copy
self.free_indices = [i for i, v in enumerate(self.current_slice)
if v == '*']
def setValue(self, symbols, value):
if value == '.':
value = self.default
assert len(symbols) == len(self.free_indices)
symbol_path = self.current_slice
for index, symbol in zip(self.free_indices, symbols):
symbol_path[index] = symbol
curr_dict = self.data
for symbol in symbol_path[:-1]:
if symbol not in curr_dict:
curr_dict[symbol] = {}
curr_dict = curr_dict[symbol]
curr_dict[symbol_path[-1]] = value
def __getitem__(self, key):
return access_data(self.data, key, self.default)
def __repr__(self):
return '<%s: %s>' % (self.__class__.__name__, self.data)
def __eq__(self, other):
return self.data == other
def __ne__(self, other):
return self.data != other
class SetObject(AmplyObject):
def __init__(self, subscripts=0, dimen=None):
self.dimen = dimen
self.subscripts = subscripts
if self.subscripts == 0:
self.data = []
else:
self.data = {}
self.current_slice = None
def addData(self, member, data):
dest_list = self._memberList(member)
if self.dimen is not None and self.current_slice is None:
self._setSlice(['*'] * self.dimen)
for record in data:
if isinstance(record, SliceRecord):
self._setSlice(record.components)
elif isinstance(record, MatrixData):
if self.dimen is None:
self.dimen = 2
self._setSlice(['*'] * 2)
d = record.data()
for v in d:
self._addValue(dest_list, v)
else: # simple-data
self._addSimpleData(dest_list, record)
def _setSlice(self, slice):
self.current_slice = slice
self.free_indices = [i for i, v in enumerate(self.current_slice)
if v == '*']
def _memberList(self, member):
if member is None:
return self.data
assert len(member) == self.subscripts
curr_dict = self.data
for symbol in member[:-1]:
if symbol not in curr_dict:
curr_dict[symbol] = {}
curr_dict = curr_dict[symbol]
if member[-1] not in curr_dict:
curr_dict[member[-1]] = []
return curr_dict[member[-1]]
def _dataLen(self, d):
if isinstance(d, (tuple, list)):
return len(d)
return 1
def _addSimpleData(self, data_list, data):
if isinstance(data[0], ParseResults):
inferred_dimen = len(data[0])
else:
inferred_dimen = 1
if self.dimen == None:
# infer dimension from records
self.dimen = inferred_dimen
if self.current_slice == None:
self._setSlice(tuple(['*'] * self.dimen))
if len(self.free_indices) == inferred_dimen:
for d in data.asList():
self._addValue(data_list, d)
elif len(self.free_indices) > 1 and inferred_dimen:
for c in chunk(data, len(self.free_indices)):
self._addValue(data_list, c)
else:
raise AmplyError("Dimension of elements (%d) does not match "
"declared dimension, (%d)" %
(inferred_dimen, self.dimen))
def _addValue(self, data_list, item):
if self.dimen == 1:
data_list.append(item)
else:
assert len(self.free_indices) == self._dataLen(item)
to_add = list(self.current_slice)
if isinstance(item, (tuple, list)):
for index, value in zip(self.free_indices, item):
to_add[index] = value
else:
assert len(self.free_indices) == 1
to_add[self.free_indices[0]] = item
data_list.append(tuple(to_add))
def __getitem__(self, key):
if not self.subscripts:
return self.data[key]
return access_data(self.data, key)
def __len__(self):
return len(self.data)
def __iter__(self):
return iter(self.data)
def __contains__(self, item):
return item in self.data
def __eq__(self, other):
return self.data == other
def __ne__(self, other):
return self.data != other
def __repr__(self):
return '<%s: %s>' % (self.__class__.__name__, self.data)
def mark_transposed(tokens):
tokens[0].setTransposed(True)
return tokens
# What follows is a Pyparsing description of the grammar
symbol = Word(alphas, alphanums + "_")
sign = Optional(oneOf("+ -"))
integer = Combine(sign + Word(nums)).setParseAction(lambda t: int(t[0]))
number = Combine(Word( "+-"+nums, nums) +
Optional("." + Optional(Word(nums))) +
Optional(oneOf("e E") + Word("+-"+nums, nums)))\
.setParseAction(lambda t: float(t[0]))
LPAREN = Suppress('(')
RPAREN = Suppress(')')
LBRACE = Suppress('{')
RBRACE = Suppress('}')
LBRACKET = Suppress('[')
RBRACKET = Suppress(']')
END = Suppress(';')
PLUS = Literal('+')
MINUS = Literal('-')
single = number | symbol | QuotedString('"') | QuotedString("'")
tuple_ = Group(LPAREN + delimitedList(single) + RPAREN)
subscript_domain = LBRACE + Group(delimitedList(symbol)) \
.setResultsName('subscripts') + RBRACE
data = single | tuple_
# should not match a single (tr)
simple_data = Group(NotAny('(tr)') + data + ZeroOrMore(Optional(Suppress(',')) + data))
# the first element of a set data record cannot be 'dimen', or else
# these would match set_def_stmts
non_dimen_simple_data = ~Literal('dimen') + simple_data
matrix_row = Group(single + OneOrMore(PLUS | MINUS))
matrix_data = ":" + OneOrMore(single).setResultsName('columns') \
+ ":=" + OneOrMore(matrix_row).setResultsName('data')
matrix_data.setParseAction(MatrixData)
tr_matrix_data = Suppress("(tr)") + matrix_data
tr_matrix_data.setParseAction(mark_transposed)
set_slice_component = number | symbol | '*'
set_slice_record = LPAREN + NotAny('tr') + delimitedList(set_slice_component) + RPAREN
set_slice_record.setParseAction(SliceRecord)
_set_record = set_slice_record | matrix_data | tr_matrix_data | Suppress(":=")
set_record = simple_data | _set_record
non_dimen_set_record = non_dimen_simple_data | _set_record
set_def_stmt = "set" + symbol + Optional(subscript_domain) + \
Optional("dimen" + integer.setResultsName('dimen')) + END
set_def_stmt.setParseAction(SetDefStmt)
set_member = LBRACKET + delimitedList(data) + RBRACKET
set_stmt = "set" + symbol + Optional(set_member).setResultsName("member") + \
Group(non_dimen_set_record + ZeroOrMore(Optional(Suppress(',')) + set_record)) \
.setResultsName("records") + END
set_stmt.setParseAction(SetStmt)
subscript = single
param_data = data | '.'
plain_data = param_data | subscript + ZeroOrMore(Optional(Suppress(',')) +
subscript) + param_data
# should not match a single (tr)
plain_data_record = Group(NotAny('(tr)') + plain_data + NotAny(plain_data) | \
plain_data + OneOrMore(plain_data) + NotAny(plain_data))
tabular_record = ":" + OneOrMore(single).setResultsName("columns") \
+ ":=" + OneOrMore(single | '.').setResultsName('data')
tabular_record.setParseAction(TabularRecord)
tr_tabular_record = Suppress("(tr)") + tabular_record
tr_tabular_record.setParseAction(mark_transposed)
param_slice_component = number | symbol | '*'
param_slice_record = LBRACKET + delimitedList(param_slice_component) + RBRACKET
param_slice_record.setParseAction(SliceRecord)
param_record = param_slice_record | plain_data_record | tabular_record | \
tr_tabular_record | Suppress(":=")
param_default = Optional("default" + data.setResultsName('default'))
param_stmt = "param" + symbol.setResultsName('name') + param_default + \
Group(OneOrMore(param_record)).setResultsName('records') + END
param_stmt.setParseAction(ParamStmt)
param_tabbing_stmt = "param" + param_default + ':' + Optional(symbol + ':') + \
OneOrMore(data).setResultsName('params') \
+ ':=' + OneOrMore(single).setResultsName('data') + END
param_tabbing_stmt.setParseAction(ParamTabbingStmt)
param_def_stmt = "param" + symbol + Optional(subscript_domain) + \
param_default + END
param_def_stmt.setParseAction(ParamDefStmt)
stmts = set_stmt | set_def_stmt | param_stmt | param_def_stmt | \
param_tabbing_stmt
grammar = ZeroOrMore(stmts) + StringEnd()
class Amply(object):
"""
Data parsing interface
"""
def __init__(self, s=""):
"""
Create an Amply parser instance
@param s (default ""): initial string to parse
"""
self.symbols = {}
self.load_string(s)
def __getitem__(self, key):
"""
Override so that symbols can be accessed using
[] subscripts
"""
if key in self.symbols:
return self.symbols[key]
def __getattr__(self, name):
"""
Override so that symbols can be accesed as attributes
"""
if name in self.symbols:
return self.symbols[name]
return super(Amply, self).__getattr__(name)
def _addSymbol(self, name, value):
"""
Adds a symbol to this instance.
Typically, this class is called by objects created by
the parser, and should not need to be called by users
directly
"""
self.symbols[name] = value
def load_string(self, string):
"""
Load and parse string
@param string string to parse
"""
for obj in grammar.parseString(string):
obj.eval(self)
def load_file(self, f):
"""
Load and parse file
@param f file-like object
"""
self.load_string(f.read())
@staticmethod
def from_file(f):
"""
Create a new Amply instance from file (factory method)
@param f file-like object
"""
return Amply(f.read())

Some files were not shown because too many files have changed in this diff Show More