├── ChangeLog ├── LICENSE ├── README.rst ├── checkld.sh ├── docs ├── Makefile ├── advanced.rst ├── appendix.rst ├── basic.py ├── conf.py ├── constr.rst ├── deltest.py ├── dynaqueens.py ├── elimsubt.py ├── game2p.py ├── gassign.py ├── gbasic.py ├── gkkt.py ├── index.rst ├── indices.rst ├── intro.rst ├── job_sched.py ├── magic.py ├── make.bat ├── misc.rst ├── pars.rst ├── revman_jobs.py ├── saves.py ├── sensana.py ├── setup.rst ├── solvopt.rst ├── ssudoku.py ├── subtour.rst ├── sudoku.py ├── tsp.py ├── tutorial.rst ├── vars.rst └── zebra.py ├── html ├── .buildinfo ├── _sources │ ├── advanced.rst.txt │ ├── appendix.rst.txt │ ├── constr.rst.txt │ ├── index.rst.txt │ ├── indices.rst.txt │ ├── intro.rst.txt │ ├── misc.rst.txt │ ├── pars.rst.txt │ ├── setup.rst.txt │ ├── solvopt.rst.txt │ ├── subtour.rst.txt │ ├── tutorial.rst.txt │ └── vars.rst.txt ├── _static │ ├── agogo.css │ ├── ajax-loader.gif │ ├── basic.css │ ├── bgfooter.png │ ├── bgtop.png │ ├── comment-bright.png │ ├── comment-close.png │ ├── comment.png │ ├── doctools.js │ ├── down-pressed.png │ ├── down.png │ ├── file.png │ ├── jquery-3.1.0.js │ ├── jquery.js │ ├── minus.png │ ├── plus.png │ ├── pygments.css │ ├── searchtools.js │ ├── underscore-1.3.1.js │ ├── underscore.js │ ├── up-pressed.png │ ├── up.png │ └── websupport.js ├── advanced.html ├── appendix.html ├── constr.html ├── genindex.html ├── index.html ├── indices.html ├── intro.html ├── misc.html ├── objects.inv ├── pars.html ├── search.html ├── searchindex.js ├── setup.html ├── solvopt.html ├── subtour.html ├── tutorial.html └── vars.html ├── models ├── +capdist.py ├── +caplp.py ├── +cvt_capdist.py ├── +dynaqueens.py ├── +lotsp.py ├── +picomagic.py ├── +piconstruct.py ├── +picorotate.py ├── +picosudoku.py ├── +revman_jobs.py ├── +sched_jobs.py ├── +sched_rev.py ├── MST.py ├── __init__.py ├── assign.py ├── basic.py ├── begins.py ├── capdist.csv ├── comagic.py ├── dectree.py ├── dectree2.py ├── deltest.py ├── dualtest.py ├── dynaqueens.py ├── fastssudoku.py ├── game2p.py ├── gassign.py ├── gassign2.py ├── gbasic.py ├── groups.py ├── job_sched.py ├── knapsack.py ├── magic.py ├── maptest.py ├── monopath.py ├── newgbasic.py ├── newsyntax.py ├── panmagic.py ├── picore.py ├── picosudokun.py ├── queens.py ├── racing.py ├── revman_jobs.py ├── saves.py ├── sensana.py ├── ssudoku.py ├── subsetsum.py ├── subtelim.py ├── sudoku.py ├── test.py ├── tsp.py └── zebra.py ├── pushgit.sh ├── pymprog.py ├── setup.py ├── tests.py ├── topypi.sh ├── upldoc.sh └── zipgit.sh /ChangeLog: -------------------------------------------------------------------------------- 1 | 12.12.2016: pymprog 1.0.0: production/stable release 2 | new syntax for constraints 3 | grouping constraints with group class 4 | deleting variables/constraints 5 | sensitivity report out of the box 6 | switching dependence to swiglpk 7 | supporting both Python 2 and 3 8 | improved documentation 9 | simpified installation with pip 10 | 29.12.2009: pymprog 0.4.2: bug fix -- x <= None 11 | 19.12.2009: pymprog 0.4.1: bug fix -- kind=bool 12 | new examples and documentation update 13 | pyglpk 0.3.3: cleaner set_kind 14 | 07.11.2009: pymprog 0.4.0: performance boost 15 | (by using [] instead of {} for linexp.mat) 16 | 25.9.2009: pymprog 0.3.1: Support for Mac OS X 17 | 17.9.2009: pymprog 0.3.0: KKT condition and Environment access 18 | integrated installation with pyglpk0.3.2 19 | 06.9.2009: pymprog 0.2.3: rich solver options. 20 | 27.8.2009: pymprog 0.2.2: test primal, dual values for constraints. 21 | 16.6.2009: pymprog 0.2.1: new documentations, one small bug. 22 | 15.6.2009: pymprog 0.2.0, everything is better 23 | now supports parameterized bounds (auto update). 24 | now supports primal/dual values of constraints. 25 | 80% of the codes are replaced! Crystal clear. 26 | now support st (a <= var <= b) or st (var == b). 27 | 08.6.2009: pymprog 0.1.4, added mechanism to set options. 28 | cleaned up direct access to glpk.LPX(). 29 | start planning to support other solvers. 30 | added new methods to solve problems: 31 | exact, interior, advanced B&B. 32 | More general support of indices. 33 | 09.5.2009: pymprog 0.1.3, maintanance code changes. 34 | added example gassign2.py to illustrate 35 | new way of creating parameters. 36 | 08.5.2009: pymprog 0.1.2, modelling interface enhancement 37 | 1. now you can specify variable types at 38 | variable creation (float, int, or bool), 39 | and variable bounds. 40 | 2. now the par(val) is recursive, so it will 41 | take the structure of the given raw values 42 | in 'val', which can be a single int or float, 43 | or a composition of list, tuple, dict, 44 | or generator of ints and floats. 45 | 17.4.2009: pymprog 0.1.1, added new example dynaqueens.py 46 | 16.4.2009: initial release, pymprog 0.1 47 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | PyMathProg 2 | ========== 3 | 4 | *An easy and flexible mathematical programming environment for Python.* 5 | 6 | Description 7 | ~~~~~~~~~~~ 8 | 9 | PyMathProg is a pythonic reincarnation of AMPL and GNU MathProg 10 | modeling language, implemented in pure Python, connecting to GLPK via 11 | swiglpk. Create, optimize, report, change and re-optimize your model 12 | with Python, easily integrate database, plotting, etc. 13 | 14 | PyMathProg provides an easy and flexible modelling syntax 15 | using *Python* to create and optimize mathematical programming models. 16 | Optimization is done by open source optimization packages such as 17 | the *GNU Linear Programming Kit (GLPK)* that is made available 18 | to PyMathProg by swiglpk. 19 | 20 | Great features offered by PyMathProg include: 21 | 22 | - Ergonomic syntax for modelling 23 | - Friendly interactive session 24 | - Sensitivity report 25 | - Advanced solver options 26 | - Automatic model update on parameter changes 27 | - Parameters sharable between models 28 | - Deleting variables/constraints 29 | - Supporting both Python 2 and 3 30 | - Supporting all major platforms 31 | 32 | 33 | Installation 34 | ~~~~~~~~~~~~ 35 | 36 | Assuming you already have Python 2 or Python 3 installed, now open a 37 | terminal window (also known as a command window), and type in this 38 | line of command and hit return:: 39 | 40 | python -m pip install pymprog 41 | 42 | That's it. Since it is a pure Python project that only depends on swiglpk, 43 | it can be installed this way wherever swiglpk can be installed. 44 | Currently, swiglpk comes with binary wheels for Windows, Mac, and Linux. 45 | If you'd like to have PyMathProg installed on other platforms, 46 | the only hurdle to overcome is to get swiglpk installed there first. 47 | 48 | Example 49 | ~~~~~~~ 50 | 51 | Below is a small example taken from the dive-in turorial 52 | in the `PyMathProg Documentation 53 | `__: 54 | 55 | :: 56 | 57 | from pymprog import * 58 | begin('bike production') 59 | x, y = var('x, y') # variables 60 | maximize(15 * x + 10 * y, 'profit') 61 | x <= 3 # mountain bike limit 62 | y <= 4 # racer production limit 63 | x + y <= 5 # metal finishing limit 64 | solve() 65 | 66 | Help in the following ways are more than welcome: 67 | 68 | 1. tutorials and samples. 69 | 2. bug reports 70 | 3. feature requests 71 | 4. code contribution 72 | 73 | I hope you will find it useful. 74 | -------------------------------------------------------------------------------- /checkld.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | python setup.py --long-description | rst2html.py > temp_output.html 3 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = pymprog 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /docs/advanced.rst: -------------------------------------------------------------------------------- 1 | .. Advanced examples by Yingjie Lan, May 2009. 2 | 3 | More Advanced Examples 4 | ======================= 5 | 6 | Here some more advanced examples are presented 7 | with brief explanations. There are dozens more 8 | examples in the *models* folder of the project 9 | if you downloaded the zipped source files from 10 | its `sourceforge project page 11 | `__. 12 | The source code of all the examples here are placed 13 | in the *docs* folder of this project. 14 | Some of these examples were originally written in 15 | GNU MathProg by Andrew Makhorin (thanks to Andrew), 16 | and were adopted into PyMathProg. 17 | 18 | #. :ref:`assign` 19 | #. :ref:`dynaqueens` 20 | #. :ref:`tsp` 21 | #. :ref:`zebra` 22 | #. :ref:`magic` 23 | #. :ref:`sudoku` 24 | #. :ref:`sudoku2` 25 | #. :ref:`jobsched` 26 | #. :ref:`revman` 27 | 28 | .. _assign: 29 | 30 | Re-optimize the assignment 31 | -------------------------- 32 | 33 | In this example, we first solve the assignment problem, 34 | then we change one of the cost parameter, and 35 | re-optimize. See how easily this is done in PyMathProg. 36 | 37 | .. literalinclude:: ./gassign.py 38 | :linenos: 39 | 40 | Below is the output, note how the optimal assignment 41 | changed after increasing the cost, and how the second 42 | time of solving the problem seems much easier. 43 | 44 | .. code-block:: none 45 | 46 | GLPK Simplex Optimizer, v4.60 47 | 16 rows, 64 columns, 128 non-zeros 48 | 0: obj = 0.000000000e+00 inf = 8.000e+00 (8) 49 | 8: obj = 1.750000000e+02 inf = 0.000e+00 (0) 50 | * 24: obj = 7.600000000e+01 inf = 0.000e+00 (0) 51 | OPTIMAL LP SOLUTION FOUND 52 | Total Cost = 76 53 | Agent 0 gets Task 0 54 | Agent 1 gets Task 7 55 | Agent 2 gets Task 6 56 | Agent 3 gets Task 4 57 | Agent 4 gets Task 1 58 | Agent 5 gets Task 5 59 | Agent 6 gets Task 3 60 | Agent 7 gets Task 2 61 | Set cost c[0, 0] to higher value 23 62 | GLPK Simplex Optimizer, v4.60 63 | 16 rows, 64 columns, 128 non-zeros 64 | * 25: obj = 7.800000000e+01 inf = 0.000e+00 (0) 65 | OPTIMAL LP SOLUTION FOUND 66 | Total Cost = 78 67 | Agent 0 gets Task 7 68 | Agent 1 gets Task 0 69 | Agent 2 gets Task 6 70 | Agent 3 gets Task 4 71 | Agent 4 gets Task 1 72 | Agent 5 gets Task 5 73 | Agent 6 gets Task 3 74 | Agent 7 gets Task 2 75 | 76 | 77 | .. _dynaqueens: 78 | 79 | Interaction with queens 80 | ----------------------- 81 | 82 | This example will randomly pick a board size and then 83 | randomly place some queens on the board. 84 | The rest of the queens are then placed 85 | by the LP program to see if all queens 86 | can be placed without attaching each other. 87 | If that's impossible, then the random positions 88 | are said to be bad. Note that in that LP, 89 | no objective function is needed. 90 | 91 | .. literalinclude:: ./dynaqueens.py 92 | :linenos: 93 | 94 | .. _tsp: 95 | 96 | The traveling sales man(TSP) 97 | ------------------------------ 98 | 99 | Really, the too well known problem. 100 | 101 | .. literalinclude:: ./tsp.py 102 | :linenos: 103 | 104 | .. _zebra: 105 | 106 | Zebra: using string as index 107 | ---------------------------- 108 | 109 | A logic puzzle, see how strings are 110 | used as indices here to enhance the 111 | readability of the model. 112 | 113 | .. literalinclude:: ./zebra.py 114 | :linenos: 115 | 116 | 117 | .. _magic: 118 | 119 | Magic numbers 120 | -------------- 121 | 122 | Magic numbers, a very interesting problem. 123 | 124 | .. literalinclude:: ./magic.py 125 | :linenos: 126 | 127 | .. _sudoku: 128 | 129 | Sudoku: want some AI? 130 | --------------------- 131 | 132 | Do you play Sudoku? 133 | This program might help you crack a Sudoku in seconds ;). 134 | 135 | .. literalinclude:: ./sudoku.py 136 | :linenos: 137 | 138 | .. _sudoku2: 139 | 140 | Super Sudoku 141 | ------------ 142 | 143 | This example will provide a sample Super Sudoku: 144 | in addition to satisfying all the requirements 145 | of Sudoku, Super Sudoku also requires that the 146 | elements in each diagonal must be distinct, 147 | below is a sample Super Sudoku obtained by the 148 | code provided afterwards:: 149 | 150 | +-------+-------+-------+ 151 | | 2 1 3 | 4 6 5 | 8 7 9 | 152 | | 4 5 6 | 7 8 9 | 2 1 3 | 153 | | 7 8 9 | 2 1 3 | 4 5 6 | 154 | +-------+-------+-------+ 155 | | 1 2 4 | 3 5 6 | 7 9 8 | 156 | | 3 6 8 | 9 7 2 | 5 4 1 | 157 | | 9 7 5 | 8 4 1 | 3 6 2 | 158 | +-------+-------+-------+ 159 | | 8 4 2 | 1 9 7 | 6 3 5 | 160 | | 6 3 1 | 5 2 4 | 9 8 7 | 161 | | 5 9 7 | 6 3 8 | 1 2 4 | 162 | +-------+-------+-------+ 163 | 164 | 165 | .. literalinclude:: ./ssudoku.py 166 | :linenos: 167 | 168 | 169 | 170 | .. _jobsched: 171 | 172 | 173 | Machine Scheduling 174 | ------------------ 175 | 176 | Schedule jobs on a single machine, given the duration, 177 | earliest start time, and the latest finish time 178 | of each job. Jobs can not be interrupted, and 179 | the machine can only handle one job at one time. 180 | 181 | .. literalinclude:: ./job_sched.py 182 | :linenos: 183 | 184 | The output schedule is as follows (your run 185 | may not be the same, as the data is randomly 186 | generated -- you might even get infeasible 187 | problem instances):: 188 | 189 | Duration [7, 4, 1, 7] 190 | Earliest [13, 17, 19, 12] 191 | Latest [22, 41, 28, 38] 192 | status: opt 193 | schedule: 194 | job 0: 13.0 20.0 195 | job 1: 20.0 24.0 196 | job 2: 27.0 28.0 197 | job 3: 31.0 38.0 198 | 199 | 200 | .. _revman: 201 | 202 | Scheduling for Revenue 203 | ----------------------- 204 | 205 | The same scheduling problem as above, but add a new 206 | spin to the problem: when it is not feasible to 207 | accept all jobs, try accept those that will maximize 208 | the total revenue. 209 | 210 | .. literalinclude:: ./revman_jobs.py 211 | :linenos: 212 | 213 | -------------------------------------------------------------------------------- /docs/appendix.rst: -------------------------------------------------------------------------------- 1 | Appendix: Installing MinGW on Windows 2 | ===================================== 3 | 4 | Note: this appendix is for the setup of the OLDER versions of PyMathProg. 5 | 6 | 1. Download the MinGW installer from 7 | http://www.mingw.org/wiki/HOWTO_Install_the_MinGW_GCC_Compiler_Suite. 8 | (As of this 9 | writing, the download link is a bit difficult to find; it's under 10 | "About" in the menu on the left-hand side). You want the file 11 | entitled "Automated MinGW Installer" (currently version 5.1.4). 12 | 2. Run it and install MinGW. Only the basic package is strictly 13 | needed for Cython, although you might want to grab at least the 14 | C++ compiler as well. 15 | 3. You need to set up Windows' "PATH" environment variable so that 16 | includes e.g. "c:\\mingw\\bin" (if you installed MinGW to 17 | "c:\\mingw"). The following web-page describes the procedure 18 | in Windows XP (the Vista procedure is similar): 19 | http://support.microsoft.com/kb/310519 20 | 4. Finally, tell Python to use MinGW as the default compiler 21 | (otherwise it will try for Visual C). If Python is installed to 22 | "c:\\Python26", create a file named 23 | "c:\\Python26\\Lib\\distutils\\distutils.cfg" containing:: 24 | 25 | [build] 26 | compiler = mingw32 27 | 28 | The [WinInst]_ wiki page contains updated information about this 29 | procedure. Any contributions towards making the Windows install 30 | process smoother is welcomed; it is an unfortunate fact that none of 31 | the regular Cython developers have convenient access to Windows. 32 | 33 | .. [WinInst] http://wiki.cython.org/InstallingOnWindows 34 | -------------------------------------------------------------------------------- /docs/basic.py: -------------------------------------------------------------------------------- 1 | from pymprog import model 2 | c = (10, 6, 4) 3 | A = [ ( 1, 1, 1), 4 | ( 9, 4, 5), 5 | ( 2, 2, 6) ] 6 | b = (10, 60, 30) 7 | p = model('basic') 8 | p.verbose(True) 9 | x = p.var('x', 3) 10 | p.maximize(sum(c[i]*x[i] for i in range(3))) 11 | for i in range(3): 12 | sum(A[i][j]*x[j] for j in range(3)) <= b[i] 13 | p.solve() # solve the model 14 | p.sensitivity() # sensitivity report 15 | p.end() 16 | -------------------------------------------------------------------------------- /docs/constr.rst: -------------------------------------------------------------------------------- 1 | Working with Constraints 2 | ======================== 3 | 4 | Defining constraints in PyMathProg is very easy. 5 | After creating the model, 6 | you can use two different styles to add constraints to it. 7 | 8 | 1. the implicit way(the more natural way): 9 | simply use inequalities(comparisons using '<=', '==', '>=') 10 | to construct constraints. 11 | 12 | 2. the explicit way: use the *st(.)* method to add constraints. 13 | This way is a little more combersome, and it is the old way. 14 | 15 | Which way to go entirely depends on your taste, the results are the same. 16 | 17 | #. :ref:`single` 18 | #. :ref:`double` 19 | #. :ref:`group` 20 | 21 | .. _single: 22 | 23 | With a single comparison 24 | ---------------------------- 25 | 26 | Let's first use the more natural way to add constraints:: 27 | 28 | begin('illustration') 29 | x = var('x', 3) 30 | y = var('y') 31 | c = [6,4,3] 32 | sum(p*q for p,q in zip(c,x)) <= y 33 | x[0] + x[2] >= x[1] 34 | x[0] + x[2] <= 1 35 | 36 | Now the equivalence in the explicit way is as follows:: 37 | 38 | begin('illustration') 39 | x = var('x', 3) 40 | y = var('y') 41 | c = [6,4,3] 42 | st( sum(p*q for p,q in zip(c,x)) <= y ) 43 | st( x[0] + x[2] >= x[1] ) 44 | st( x[0] + x[2] <= 1 ) 45 | 46 | Surely, we can use index sets to make the model 47 | more easy to read and write, as seen in this more 48 | interesting model for diet optimization:: 49 | 50 | from pymprog import * 51 | fruits = ('apple', 'pear', 'orange') 52 | nutrit = ('fat', 'fibre', 'vitamin') 53 | ntrmin = ( 10, 50, 30 ) # min nutrition intake 54 | #nutrition ingredients of each fruit 55 | ingred = ('apple': (3,4,5), 'pear': (2,4,1), 56 | 'orange': (2,3,4)) 57 | diet = var('diet', fruits, int) 58 | for n, ntr in enumerate(nutrit): 59 | sum( diet[frt] * ingred[frt][n] 60 | for frt in fruits) >= ntrmin[n] 61 | 62 | Those constraints are perfectly fine: they just have 63 | one comparison. Now let's get a little more sophisticated. 64 | 65 | .. _double: 66 | 67 | With double comparisons 68 | ---------------------------- 69 | 70 | Things get more interesting when we use continuous comparisons in 71 | Python to specify both the lower and upper bounds in one expression:: 72 | 73 | >>> begin('model') 74 | >>> x, y = var('x, y') 75 | >>> 3 <= 4 * x + 5 * y <= 6 76 | 77 | The new thing appears on the last line. 78 | It bounds the linear expression in the middle 79 | by both a lower and upper bound using continuous comparison. 80 | 81 | Before we move on, let's do a little side talk on 82 | the similarity of variable bounds and constraints. 83 | We already know how to bound a variable *x*:: 84 | 85 | >>> x <= 100 86 | >>> 1 <= x <= 5 87 | 88 | From pure mathematical sense, bounds are just a special case of 89 | constraints. And PyMathProg honors that sense, in that 90 | the effect is the same as if it were a constraint. 91 | Yet, in terms of how things gets done inside, that 92 | simply adds to the list of bounds for *x*, so that 93 | all the bounds are simultaneous (adding a bound to a variable 94 | does *not* cancle any of its previous bounds), 95 | just like constraints do. 96 | 97 | More than two continuous comparisons are not encouraged in 98 | PyMathProg. The major purpose of continuous comparisons 99 | is to set both the lower and the upper bounds for a row, 100 | in which case the two bounds must not contain variables. 101 | However, it is entirely legal to use as many comparisons 102 | as you like, the only caution is that in Python, if 103 | any of the comparison evaluates to a False, all the 104 | later comparisons will not be evaluated, and thus 105 | won't take any effect (i.e. they would not produce 106 | constraints). 107 | 108 | 109 | .. _group: 110 | 111 | 112 | Grouping constraints 113 | -------------------- 114 | 115 | Sometimes, it is necessary to use a Python variable to 116 | hold a constraint for later use, for example, to 117 | obtain the dual value for a constraint. This is simple: 118 | 119 | >>> begin('model') 120 | model('model') is the default model. 121 | >>> x, y = var('x, y') 122 | >>> R = 3 * x + y >= 5 123 | >>> R 124 | R1: 3 * x + y >= 5 125 | >>> R.name 126 | 'R1' 127 | >>> R.name = 'Sugar Level' 128 | >>> R 129 | Sugar Level: 3 * x + y >= 5 130 | 131 | Upon creation, a constraint is given a default name 132 | like this: *R#*, where *#* is the serial number. 133 | Sometimes, it is desirable to change to a more meaningful 134 | name, which can be done by assigning to the *name* property of 135 | a constraint. Of course, it can also be done by employing 136 | the *st(...)* function with the argument for *name* supplied. 137 | Use *help(st)* in an interactive session for more information. 138 | Another more elegant solution is to use a group object to 139 | group desired constraints together. The cool thing about 140 | group objects is that they can automatically name the 141 | constraints by the group name with the index used as subscript. 142 | Here is an illustration of using group objects:: 143 | 144 | from pymprog import * 145 | minlev = group('minlev') 146 | fruits = ('apple', 'pear', 'orange') 147 | nutrit = ('fat', 'fibre', 'vitamin') 148 | ntrmin = ( 10, 50, 30 ) # min nutrition intake 149 | #nutrition ingredients of each fruit 150 | ingred = ('apple': (3,4,5), 'pear': (2,4,1), 151 | 'orange': (2,3,4)) 152 | diet = var('diet', fruits, int) 153 | for n, ntr in enumerate(nutrit): 154 | minlev[ntr] = sum( diet[frt] * ingred[frt][n] 155 | for frt in fruits) >= ntrmin[n] 156 | 157 | The new stuff here is that we use a group called 'minlev' 158 | to hold the constraints, so *minlev['fat']* would hold 159 | the constraint for minimal level of fat, and that 160 | constraint is also given an informative name "minlev['fat']". 161 | The resultant model would be much easier to understand. 162 | 163 | -------------------------------------------------------------------------------- /docs/deltest.py: -------------------------------------------------------------------------------- 1 | from pymprog import * 2 | begin('trader') 3 | x = var('x', 3) 4 | c = par('c', [100, 300, 50]) 5 | b = par('b', [93000, 101, 201]) 6 | maximize(sum(c[i]*x[i] for i in range(3)), 'Profit') 7 | 8 | 300*x[0] + 1200*x[1] + 120*x[2] <= b[0] 9 | 0.5*x[0] + x[1] + 0.5*x[2] <= b[1] 10 | r = x[0] + x[1] + x[2] <= b[2] 11 | 12 | solve() 13 | sensitivity() 14 | 15 | r.delete() 16 | # deleting a basic varriable destroys the basis 17 | x[1].delete() 18 | # restore the standard basis 19 | std_basis() 20 | solve() 21 | sensitivity() 22 | 23 | end() 24 | -------------------------------------------------------------------------------- /docs/dynaqueens.py: -------------------------------------------------------------------------------- 1 | from pymprog import model, iprod, glpk 2 | 3 | # The Queens Problem is to place as many queens as possible on the nxn 4 | # chess board in a way that they do not fight 5 | # each other. This problem is probably as old as the chess game itself, 6 | # and thus its origin is not known, but it is known that Gauss studied 7 | # this problem. 8 | 9 | def queens(n): # n: size of the chess board 10 | p = model('queens') 11 | iboard = iprod(range(n), range(n)) #create indices 12 | x = p.var('x', iboard, bool) #create variables 13 | sum(x[t] for t in iboard) == n 14 | for i in range(n): # row-wise 15 | sum(x[i,j] for j in range(n)) <= 1 16 | for j in range(n): # column-wise 17 | sum(x[i,j] for i in range(n)) <= 1 18 | for k in range(2-n, n-1): # diagonal '\' wise 19 | sum(x[i,j] for i,j in iboard if i-j == k) <= 1 20 | for k in range(1, n+n-2): # anti-diagonal '/' wise 21 | sum(x[i,j] for i,j in iboard if i+j == k) <= 1 22 | return p,x 23 | 24 | import random 25 | n = random.randint(6, 11) 26 | print("Board size: %i X %i"%(n,n)) 27 | def randpair(): 28 | m = random.randint(0, n*n-1) 29 | return m%n, m//n 30 | def randpos(k): 31 | while True: 32 | pos = [randpair() for i in range(k)] 33 | if len(set(i for i,j in pos))= 1 51 | solve() #solve the IP problem again 52 | end() 53 | -------------------------------------------------------------------------------- /docs/game2p.py: -------------------------------------------------------------------------------- 1 | from pymprog import * 2 | 3 | #####Solve this 2-player zero-sum game: 4 | ## 5 | ## Gain for player 1 6 | ## (Loss for player 2) 7 | ## 8 | ## || Player 2 9 | ## Player 1 || B1 B2 10 | ## A1 || 5 9 11 | ## A2 || 8 6 12 | ## 13 | ############################## 14 | 15 | begin('game') 16 | # gain of player 1, a free variable 17 | v = var('game_value', bounds=(None,None)) 18 | # mixed strategy of player 2 19 | p = var('p', 2) 20 | # probability sums to 1 21 | sum(p) == 1 22 | # player 2 chooses p to minimize v 23 | minimize(v) 24 | # player 1 chooses the better value 25 | r1 = v >= 5*p[0] + 9*p[1] 26 | r2 = v >= 8*p[0] + 6*p[1] 27 | solve() 28 | print('Game value: %g'% v.primal) 29 | print("Mixed Strategy for player 1:") 30 | print("A1: %g, A2: %g"%(r1.dual, r2.dual)) 31 | print("Mixed Strategy for player 2:") 32 | print("B1: %g, B2: %g"%(p[0].primal, p[1].primal)) 33 | end() 34 | -------------------------------------------------------------------------------- /docs/gassign.py: -------------------------------------------------------------------------------- 1 | # Assignment Problem 2 | # Written in pymprog by Yingjie Lan 3 | 4 | # The assignment problem is one of the fundamental combinatorial 5 | # optimization problems. 6 | 7 | # In its most general form, the problem is as follows: 8 | 9 | # There are a number of agents and a number of tasks. Any agent can be 10 | # assigned to perform any task, incurring some cost that may vary 11 | # depending on the agent-task assignment. It is required to perform all 12 | # tasks by assigning exactly one agent to each task in such a way that 13 | # the total cost of the assignment is minimized. 14 | # (From Wikipedia, the free encyclopedia.) 15 | 16 | #problem data 17 | m = 8 # agents 18 | M = range(m) #set of agents 19 | n = 8 # tasks 20 | N = range(n) #set of tasks 21 | c = [ #cost 22 | (13,21,20,12,8,26,22,11), 23 | (12,36,25,41,40,11,4,8), 24 | (35,32,13,36,26,21,13,37), 25 | (34,54,7,8,12,22,11,40), 26 | (21,6,45,18,24,34,12,48), 27 | (42,19,39,15,14,16,28,46), 28 | (16,34,38,3,34,40,22,24), 29 | (26,20,5,17,45,31,37,43)] 30 | 31 | from pymprog import * 32 | 33 | begin("assign") 34 | #verbose(True) # for model output 35 | A = iprod(M, N) # Descartan product 36 | x = var('x', A) # assignment decisions 37 | # use parameters for automatic model update 38 | c = par('c', c) # when their values change 39 | minimize(sum(c[i][j]*x[i,j] for i,j in A)) 40 | # each agent is assigned to at most one task 41 | for k in M: sum(x[k,j] for j in N)<=1 42 | # each task must be assigned to somebody 43 | for k in N: sum(x[i,k] for i in M)==1 44 | 45 | def report(): 46 | print("Total Cost = %g"%vobj()) 47 | assign = [(i,j) for i in M for j in N 48 | if x[i,j].primal>0.5] 49 | for i,j in assign: 50 | print("Agent %d gets Task %d"%(i, j)) 51 | return assign 52 | 53 | solve() 54 | assign = report() 55 | i,j = assign[0] 56 | # model will be updated for the value change 57 | c[i][j].value += 10 58 | print("Set cost c%r to higher value %r"% 59 | ([i,j],c[i][j].value)) 60 | 61 | solve() 62 | report() 63 | end() 64 | -------------------------------------------------------------------------------- /docs/gbasic.py: -------------------------------------------------------------------------------- 1 | from pymprog import * 2 | c = (10, 6, 4) 3 | A = [ ( 1, 1, 1), 4 | ( 9, 4, 5), 5 | ( 2, 2, 6) ] 6 | b = (10, 60, 30) 7 | begin('basic') # begin modelling 8 | verbose(True) # be verbose 9 | x = var('x', 3) #create 3 variables 10 | maximize(sum(c[i]*x[i] for i in range(3))) 11 | for i in range(3): 12 | sum(A[i][j]*x[j] for j in range(3)) <= b[i] 13 | solve() # solve the model 14 | sensitivity() # sensitivity report 15 | end() #Good habit: do away with the model 16 | -------------------------------------------------------------------------------- /docs/gkkt.py: -------------------------------------------------------------------------------- 1 | from pymprog import * 2 | c = (10, 6, 4) 3 | A = [ ( 1, 1, 1), 4 | ( 9, 4, 5), 5 | ( 2, 2, 6) ] 6 | b = (10, 60, 30) 7 | begin('basic') # begin modelling 8 | verbose(True) # be verbose 9 | x = var('x', 3) #create 3 variables 10 | maximize(sum(c[i]*x[i] for i in range(3))) 11 | for i in range(3): 12 | sum(A[i][j]*x[j] for j in range(3)) <= b[i] 13 | solve() # solve the model 14 | print(KKT()) 15 | end() #Good habit: do away with the model 16 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. pymprog documentation master file, created by 2 | sphinx-quickstart on Thu May 21 09:19:35 2009. 3 | 4 | Welcome to PyMathProg 5 | ===================== 6 | 7 | This is the documentation for **PyMathProg**. 8 | PyMathProg is an easy and flexible mathematical programming 9 | environment for Python. It makes modelling, solving, analyzing, 10 | modifying and manipulating linear programs 11 | super easy and highly flexible in Python. 12 | 13 | Great features offered by PyMathProg include: 14 | 15 | - Ergonomic syntax for modelling 16 | - Friendly interactive session 17 | - Sensitivity report 18 | - Advanced solver options 19 | - Automatic model update on parameter changes 20 | - Parameters sharable between models 21 | - Deleting variables/constraints 22 | - Supporting both Python 2 and 3 23 | - Supporting all major platforms 24 | 25 | 26 | Quick installation 27 | ================== 28 | 29 | Assuming you already have Python 2 or Python 3 installed, now open a 30 | terminal window (also known as a command window), and type in this 31 | line of command and hit return:: 32 | 33 | pip install pymprog 34 | 35 | That's it! In a couple of minutes it will download the necessary packages 36 | and install them, and you should be all set. 37 | Since pymprog is a pure Python project that only depends on swiglpk, 38 | it can be installed this way wherever swiglpk can be installed. 39 | Currently, swiglpk comes with binary wheels for Windows, Mac, and Linux. 40 | If you'd like to have pymprog installed on other exotic platforms, 41 | the only hurdle to overcome is to get swiglpk installed there first. 42 | 43 | Quick example 44 | =============== 45 | 46 | Below is a small example taken from :ref:`whetting`:: 47 | 48 | from pymprog import * 49 | begin('bike production') 50 | x, y = var('x, y') # variables 51 | maximize(15 * x + 10 * y, 'profit') 52 | x <= 3 # mountain bike limit 53 | y <= 4 # racer production limit 54 | x + y <= 5 # metal finishing limit 55 | solve() 56 | 57 | 58 | Getting Help 59 | ================ 60 | 61 | There are several ways to get some help: 62 | 63 | #. Project homepage: http://pymprog.sf.net/ 64 | 65 | #. Email list: https://lists.sourceforge.net/mailman/listinfo/pymprog-help 66 | 67 | #. Forum: https://sourceforge.net/forum/forum.php?forum_id=942151 68 | 69 | 70 | .. raw:: html 71 | 72 |

More PyMathProg resources at 73 | 74 | SourceForge.net Logo

76 | 77 | Contents: 78 | ================ 79 | 80 | .. toctree:: 81 | :maxdepth: 2 82 | 83 | intro 84 | setup 85 | tutorial 86 | vars 87 | indices 88 | pars 89 | constr 90 | solvopt 91 | misc 92 | advanced 93 | subtour 94 | appendix 95 | 96 | Indices and tables 97 | ================== 98 | 99 | * :ref:`genindex` 100 | * :ref:`search` 101 | 102 | -------------------------------------------------------------------------------- /docs/indices.rst: -------------------------------------------------------------------------------- 1 | 2 | Working with Indices 3 | ====================== 4 | 5 | Without indices, it is really hard to work with big models: 6 | one has to give each variable a different name. In modern 7 | modeling languages such as AMPL and GMPL, index sets 8 | are an essential building instrument, which is really not 9 | surprising, as we see indices almost everywhere in advanced 10 | mathematics. PyMathProg also provides a solution 11 | to indexing into variables and constraints. 12 | 13 | #. :ref:`natural` 14 | #. :ref:`combined` 15 | #. :ref:`varidx` 16 | 17 | 18 | .. _natural: 19 | 20 | Natural indices 21 | ---------------- 22 | 23 | Python already provides some natural indices: 24 | tuples, lists, sets(such as the keys in a dict), 25 | or anything iterable(e.g. a generator, a sequence, etc.). 26 | For an object to become an legitimate index, it has 27 | to be immutable, which means that its value should 28 | not change (in the sense defined by the magic method of 29 | *__eq__*) during its entire life cycle. By the tacit 30 | contract between objects in Python, objects of equal 31 | value should also produce the same hash code, thus 32 | immutable objects have a constant hash code, 33 | which is used for indexing purposes to quickly locate 34 | an object in a set or dict. 35 | 36 | PyMathProg trusts you to provide a list, a tuple, a set, 37 | a dict (in such case only the keys are used for indices), 38 | or any iterable as a set of indices without repeated elements. 39 | They can be utilized in PyMathProg as indices 40 | for variables, constraints, and parameters. 41 | If your put duplicate indices there, things could get lost. 42 | 43 | .. _combined: 44 | 45 | Combined indices 46 | ---------------- 47 | 48 | PyMathProg also provides a way to combine smaller index sets 49 | into bigger ones by the concept of set product. Given two sets 50 | A and B, the product of A * B is defined as:: 51 | 52 | A * B = { (a,b) : a in A, b in B } 53 | 54 | In the pymprog module, there is a class to achieve this:: 55 | 56 | >>> from pymprog import * 57 | >>> A = (1, 2, 3) 58 | >>> B = (6, 7, 8) 59 | >>> C = iprod(A,B) 60 | >>> for c in C: print(c) 61 | ... 62 | (1, 6) (1, 7) (1, 8) (2, 6) (2, 7) (2, 8) (3, 6) (3, 7) (3, 8) 63 | 64 | Well, that's about it. By the way, the constructor *iprod(...)* 65 | can take as many iterables (sets, lists, tuples, or sequences) 66 | as you want -- that's the cool part of it. 67 | 68 | 69 | .. _varidx: 70 | 71 | Use index with variables 72 | ------------------------- 73 | 74 | It is simple to create many variables over some indices in PyMathProg. 75 | Let's continue the Python session above:: 76 | 77 | >>> begin('test') 78 | >>> x = var('x', C) 79 | >>> type(x) 80 | 81 | >>> x[2,7].name 82 | 'x[2,7]' 83 | 84 | So we use the combined set C as the index set to create variables, 85 | the major variable name is 'X'. 86 | Out of curiosity, we want to know the type of the python object 87 | referenced by 'x': it turns out to be a dict type -- probably that 88 | is not that important anyway, what is more important is how easily 89 | and intuitively it is indexed, as shown in the next line. 90 | 91 | -------------------------------------------------------------------------------- /docs/intro.rst: -------------------------------------------------------------------------------- 1 | .. pymprog documentation master file, created by 2 | sphinx-quickstart on Thu May 21 09:19:35 2009. 3 | 4 | ########################## 5 | Introduction to PyMathProg 6 | ########################## 7 | 8 | .. _whetting: 9 | 10 | Whetting your appetite 11 | ====================== 12 | 13 | If you'd like to have a powerful linear program(LP) solver 14 | wrapping around your little finger, then **PyMathProg** 15 | is the thing for you. 16 | PyMathProg provides an easy and flexible modelling syntax 17 | using *Python* to create and optimize mathematical programming models. 18 | It is kind of a reincarnation of AMPL and GNU MathProg in Python. 19 | To illustrate, we will solve this tiny LP model here:: 20 | 21 | maximize 15 x + 10 y # profit 22 | S.T. 23 | x <= 3 # mountain bike limit 24 | y <= 4 # racer limit 25 | x + y <= 5 # frame limit 26 | x >=0, y >=0 # non-negative 27 | 28 | Believed it or not, we can almost literally transcribe it 29 | into PyMathProg as follows:: 30 | 31 | from pymprog import * 32 | begin('bike production') 33 | x, y = var('x, y') # variables 34 | maximize(15 * x + 10 * y, 'profit') 35 | x <= 3 # mountain bike limit 36 | y <= 4 # racer production limit 37 | x + y <= 5 # metal finishing limit 38 | solve() 39 | 40 | For now, let's fire up an interactive python session, maybe in a 41 | terminal or a Python IDLE window, and try it all out like this: 42 | 43 | >>> from pymprog import * 44 | >>> begin('bike production') 45 | model('bikes production') is the default model. 46 | >>> x, y = var('x, y') # create variables 47 | >>> x, y # take a look at them 48 | (0 <= x continuous, 0 <= y continuous) 49 | >>> maximize(15*x + 10*y, 'profit') 50 | Max profit: 15 * x + 10 * y 51 | >>> x <= 3 52 | 0 <= x <= 3 continuous 53 | >>> y <= 4 54 | 0 <= y <= 4 continuous 55 | >>> x + y <= 5 56 | R1: x + y <= 5 57 | >>> solve() 58 | GLPK Simplex Optimizer, v4.60 59 | 1 row, 2 columns, 2 non-zeros 60 | * 0: obj = -0.000000000e+00 inf = 0.000e+00 (2) 61 | * 2: obj = 6.500000000e+01 inf = 0.000e+00 (0) 62 | OPTIMAL LP SOLUTION FOUND 63 | 64 | That's just the beginning. A lot of more sophisticated 65 | examples are included in other parts of this documentation. 66 | We can also do many other interesing things to 67 | a model, even after solving it, for example: 68 | 69 | - Conduct sensitivity analysis 70 | - Change the value of a parameter 71 | - Fix a variable to an arbitrary value 72 | - Manage the bounds of a variable or constraint 73 | - Change the type of a variable 74 | - Adding/deleting variables or constraints 75 | 76 | When we solve it again, the solver usually takes 77 | advantage of results from the latest solution. 78 | 79 | .. _dream: 80 | 81 | The Dream of PyMathProg 82 | ======================= 83 | 84 | The dream of **PyMathProg** is to do math programming in *Python* 85 | with ease and flexibility, to create, optimize, report, 86 | change and re-optimize your model without breaking a sweat. 87 | To make that dream come true, a few things are essential: 88 | 89 | - a good modelling language, and we've got Python 90 | - a powerful and flexible solver, and there is GLPK 91 | - an integrated toolset, with database, plotting, etc. 92 | - an interactive modelling enviromment for easy learning 93 | 94 | and for the last two, the answer is stiell "we've got Python". 95 | Being embedded in Python, you can take advantage of all the good 96 | stuff available in Python: such as easy database access, 97 | graphical presentation of your solution, statistical analysis, 98 | or use pymprog for artificial intelligence in games, etc. 99 | Interactive sessions enable us to get immediate feedback for 100 | each small step we take. We can also conveniently obtain help 101 | right within the session by using the 'help(.)' function. 102 | And we may quickly get an answer to our questions by conducting 103 | small experiments, or test out some ideas that arise at the occasion. 104 | Interactivity can make learning **PyMathProg** easy and fun. 105 | 106 | .. _features: 107 | 108 | Exciting new features 109 | ====================== 110 | 111 | Exciting new freatures offered by PyMathProg *v1.0* are as follows: 112 | 113 | - New syntax to make modelling easy and intuitive 114 | - Sensitivity analysis report 115 | - Deletion of variables/constraints 116 | - Improved solver options 117 | - Friendly interactive session 118 | - Arbitrary parameter changes update model automatically 119 | - Parameters can be shared among many models 120 | 121 | The underlying solver is still GLPK, but now it is 122 | made available to PyMathProg by swiglpk, which has enabled: 123 | 124 | - Super easy setup of PyMathProg with one single command 125 | - Support of both Python 2 and 3 126 | - Support of the newest version of GLPK (v4.60) 127 | 128 | Therefore, this is indeed an exciting new version of **PyMathProg**! 129 | 130 | .. _compatability: 131 | 132 | A word on compatability 133 | ========================= 134 | 135 | This new version (*v1.0*) of PyMathProg is *fully compatable* 136 | with previous versions. However, some functions are deprecated and 137 | won't be fully supported in futre versions. Here is a list of them: 138 | 139 | - var(.) now takes the name of variable(s) as the first argument 140 | - par(.) now takes the name of parameter(s) as the first argument 141 | - beginModel(.) is simplified into begin(.) 142 | - endModel(.) is simplified into end(.) 143 | 144 | If you used really old PyMathProg, you might need to be aware that 145 | the default bounds for variables created by var(...) is non-negative, 146 | instead of being unbounded, as pointed out by Julia Helmecke on sourceforge. 147 | -------------------------------------------------------------------------------- /docs/job_sched.py: -------------------------------------------------------------------------------- 1 | n = 3 2 | N = range(n) 3 | M = [(i,j) for i in N for j in N if i overlap betw jobs i,j 17 | y = var('y', M ) 18 | #w[i,j]: the 'OR' for |T[i]-x[j]-MD[i,j]| 19 | w = var('w', M, kind=bool) 20 | # z[i,j] >= MD[i,j] - y[i,j] 21 | z = var('z', M ) 22 | 23 | minimize( sum(z[i,j] for i,j in M) ) 24 | 25 | for i,j in M: 26 | ((D[i]+D[j])/2.0 - (x[i]+D[i] - x[j]) + 27 | (U[i]-L[j]) * w[i,j] >= y[i,j]) 28 | 29 | ((x[i]+D[i] - x[j]) - (D[i]+D[j])/2.0 + 30 | (U[j]-L[i])*(1-w[i,j]) >= y[i,j]) 31 | 32 | (D[i]+D[j])/2.0 - y[i,j] <= z[i,j] 33 | 34 | #set bounds on x 35 | for i in N: 36 | L[i] <= x[i] <= U[i] - D[i] 37 | 38 | #another way to enforce no overlapping: 39 | # 40 | # x[i] >= T[j] or x[j] >= T[i] 41 | # 42 | # Which can be formulated as: 43 | # 44 | # x[i] + (U[j]-L[i])*w[i,j]>= x[j]+D[j] 45 | # x[j] + (U[i]-L[j])*(1-w[i,j]) >= x[i]+D[i] 46 | 47 | solve() 48 | 49 | print("status: %r"% status()) 50 | print( "overlap: %r"% vobj()) 51 | print( "schedule:") 52 | 53 | for i in N: 54 | start = x[i].primal 55 | print("job %i: %r, %r"%(i, start, start+D[i])) 56 | 57 | -------------------------------------------------------------------------------- /docs/magic.py: -------------------------------------------------------------------------------- 1 | """In recreational mathematics, a magic square of order n is an 2 | arrangement of n^2 numbers, usually distinct integers, in a square, 3 | such that n numbers in all rows, all columns, and both diagonals sum 4 | to the same constant. A normal magic square contains the integers 5 | from 1 to n^2. 6 | 7 | (From Wikipedia, the free encyclopedia.) 8 | 9 | When n=5, we have: 10 | 11 | the magic sum must be: 65 12 | solver status: opt 13 | 10 2 4 24 25 14 | 9 11 20 6 19 15 | 22 14 16 8 5 16 | 23 17 7 15 3 17 | 1 21 18 12 13""" 18 | 19 | # square order 20 | n = 4 21 | # set of numbers 22 | N = range(1,n*n+1) 23 | #the magic sum: 24 | s = sum(t for t in N)//n 25 | print("the magic sum must be: %r "% s) 26 | 27 | from pymprog import model, iprod 28 | S = iprod(range(1,n+1), range(1,n+1)) 29 | T = iprod(range(1,n+1), range(1,n+1), N) 30 | p = model('magic') 31 | x = p.var('x', T, bool) 32 | #x[i,j,k] = 1 means that cell (i,j) contains integer k 33 | 34 | #each cell must be assigned exactly one integer 35 | for i,j in S: sum(x[i,j,k] for k in N)==1 36 | 37 | #each integer must be assigned exactly to one cell 38 | for k in N: sum(x[i,j,k] for i,j in S)==1 39 | 40 | #the sum in each row must be the magic sum 41 | for i in range(1, n+1): 42 | sum(k*x[i,j,k] for j in range(1,n+1) for k in N)==s 43 | 44 | #the sum in each column must be the magic sum 45 | for j in range(1, n+1): 46 | sum(k*x[i,j,k] for i in range(1,n+1) for k in N)==s 47 | 48 | #the sum in the diagonal must be the magic sum 49 | sum(k*x[i,i,k] for i in range(1,n+1) for k in N)==s 50 | 51 | #the sum in the co-diagonal must be the magic sum 52 | sum(k*x[i,n+1-i,k] for i in range(1,n+1) for k in N)==s 53 | 54 | p.solve() 55 | print("solver status: %s"% p.status()) 56 | for i in range(1,n+1): 57 | print(' '.join("%2g"%sum(x[i,j,k].primal*k for k in N) 58 | for j in range(1,n+1))) 59 | p.end() 60 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | set SPHINXPROJ=pymprog 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 20 | echo.installed, then set the SPHINXBUILD environment variable to point 21 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 22 | echo.may add the Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /docs/misc.rst: -------------------------------------------------------------------------------- 1 | Miscellaneous functions 2 | ======================== 3 | 4 | There are some msscellaneous functions provided in PyMathProg 5 | for less common tasks. Here we will talk about: 6 | 7 | #. :ref:`delete` 8 | #. :ref:`save` 9 | #. :ref:`kkt` 10 | 11 | .. _delete: 12 | 13 | Deleting model elements 14 | ----------------------- 15 | 16 | Deleting variables and/or constraints from a model 17 | is done by invoking the *delete()* method on a 18 | variable or constraint. 19 | 20 | .. literalinclude:: ./deltest.py 21 | :linenos: 22 | 23 | The output is as follows: 24 | 25 | .. code-block:: none 26 | 27 | GLPK Simplex Optimizer, v4.60 28 | 3 rows, 3 columns, 9 non-zeros 29 | * 0: obj = -0.000000000e+00 inf = 0.000e+00 (3) 30 | * 2: obj = 2.560000000e+04 inf = 0.000e+00 (0) 31 | OPTIMAL LP SOLUTION FOUND 32 | 33 | PyMathProg 1.0 Sensitivity Report Created: 2016/12/11 Sun 09:05AM 34 | ================================================================================ 35 | Variable Activity Dual.Value Obj.Coef Range.From Range.Till 36 | -------------------------------------------------------------------------------- 37 | *x[0] 94 0 100 87.5 150 38 | *x[1] 54 0 300 200 366.667 39 | x[2] 0 -20 50 -inf 70 40 | ================================================================================ 41 | ================================================================================ 42 | Constraint Activity Dual.Value Lower.Bnd Upper.Bnd RangeLower RangeUpper 43 | -------------------------------------------------------------------------------- 44 | R1 93000 0.166667 -inf 93000 61200 121200 45 | R2 101 100 -inf 101 77.5 118.667 46 | *R3 148 0 -inf 201 148 148 47 | ================================================================================ 48 | GLPK Simplex Optimizer, v4.60 49 | 2 rows, 2 columns, 4 non-zeros 50 | * 3: obj = 2.020000000e+04 inf = 0.000e+00 (0) 51 | OPTIMAL LP SOLUTION FOUND 52 | 53 | PyMathProg 1.0 Sensitivity Report Created: 2016/12/11 Sun 09:05AM 54 | ================================================================================ 55 | Variable Activity Dual.Value Obj.Coef Range.From Range.Till 56 | -------------------------------------------------------------------------------- 57 | *x[0] 202 0 100 50 1.79769e+308 58 | x[2] 0 -50 50 -inf 100 59 | ================================================================================ 60 | ================================================================================ 61 | Constraint Activity Dual.Value Lower.Bnd Upper.Bnd RangeLower RangeUpper 62 | -------------------------------------------------------------------------------- 63 | *R1 60600 0 -inf 93000 60600 60600 64 | R2 101 200 -inf 101 0 155 65 | ================================================================================ 66 | 67 | 68 | .. _save: 69 | 70 | Saving model and solution 71 | ------------------------- 72 | 73 | It is possible to save the model and/or the solution to a text file. The example 74 | below shows how to do that through the global function *save(...)*. 75 | 76 | .. literalinclude:: ./saves.py 77 | :linenos: 78 | 79 | Note that the sensitivity report just saved is produced by GLPK. 80 | The format is not the same as the report produced by the global 81 | function *sensitivity()* in PyMathProg. 82 | 83 | .. _kkt: 84 | 85 | Karush-Kuhn-Tucker conditions 86 | ----------------------------- 87 | 88 | The KKT condition tells how much error are there 89 | in terms of satisfying the constraints. Errors 90 | may be measured both absolutely or relatively. 91 | To produce KKT conditions, just call the routine KKT() 92 | after solving a model. 93 | 94 | .. literalinclude:: ./gkkt.py 95 | :linenos: 96 | 97 | The produced output is as follows: 98 | 99 | .. code-block:: none 100 | 101 | Max : 10 * x[0] + 6 * x[1] + 4 * x[2] 102 | R1: x[0] + x[1] + x[2] <= 10 103 | R2: 9 * x[0] + 4 * x[1] + 5 * x[2] <= 60 104 | R3: 2 * x[0] + 2 * x[1] + 6 * x[2] <= 30 105 | GLPK Simplex Optimizer, v4.60 106 | 3 rows, 3 columns, 9 non-zeros 107 | * 0: obj = -0.000000000e+00 inf = 0.000e+00 (3) 108 | * 2: obj = 7.600000000e+01 inf = 0.000e+00 (0) 109 | OPTIMAL LP SOLUTION FOUND 110 | Karush-Kuhn-Tucker optimality conditions: 111 | ========================================= 112 | Solver used for this solution: simplex 113 | 114 | 1) Error for Primal Equality Constraints: 115 | ---------------------------------------- 116 | Largest absolute error: 0.000000 (row id: 0) 117 | Largest relative error: 0.000000 (row id: 0) 118 | 119 | 2) Error for Primal Inequality Constraints: 120 | ------------------------------------------- 121 | Largest absolute error: 0.000000 (row id: 0) 122 | Largest relative error: 0.000000 (row id: 0) 123 | 124 | 1) Error for Dual Equality Constraints: 125 | ---------------------------------------- 126 | Largest absolute error: 0.000000 (var id: 0) 127 | Largest relative error: 0.000000 (var id: 0) 128 | 129 | 2) Error for Dual Inequality Constraints: 130 | ------------------------------------------- 131 | Largest absolute error: 0.000000 (var id: 0) 132 | Largest relative error: 0.000000 (var id: 0) 133 | 134 | __del__ is deleting problem: basic 135 | 136 | -------------------------------------------------------------------------------- /docs/revman_jobs.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | n = 20 3 | N = range(n) 4 | 5 | from random import Random 6 | 7 | rand = Random() 8 | 9 | D = [rand.randint(1,10) for i in N] # processing time 10 | R = [rand.randint(1,10) for i in N] # revenue 11 | tt = sum(D); tt -= tt//5 12 | L = [rand.randint(1,tt) for i in N] # arrival time 13 | U = [L[i]+D[i]+rand.randint(10,30) for i in N] # duedate 14 | 15 | #overlapping jobs 16 | M = [(i,j) for i in N for j in N 17 | if i overlap betw jobs i,j 34 | y = var('y', M) 35 | #w[i,j]: the 'OR' for |T[i]-x[j]-MD[i,j]| 36 | w = var('w', M, bool) 37 | # z[i,j] >= MD[i,j] - y[i,j] 38 | z = var('z', M ) 39 | #u[i] = 1 iff job i is scheduled. 40 | u = var('u', N, bool) 41 | 42 | maximize(sum(R[i]*u[i] for i in N), 'revenue') 43 | 44 | sum(z[i,j] for i,j in M) == 0 #single 45 | #w[i,j]=0 ==> 46 | #x[j] + D[j]/2 >= x[i]+D[i]/2 + y[i,j] 47 | #That is: i preceed j when w[i,j]=0. 48 | for i,j in M: 49 | ((D[i]+D[j])/2.0 - (x[i]+D[i]- x[j]) + 50 | (U[i]-L[j]) * w[i,j] >= y[i,j]) 51 | 52 | ((x[i]+D[i]- x[j]) - (D[i]+D[j])/2.0 + 53 | (U[j]-L[i])*(1-w[i,j]) >= y[i,j]) 54 | 55 | ((D[i]+D[j])/2.0 - y[i,j] <= z[i,j] + 56 | (2-u[i]-u[j])*(D[i]+D[j])/2.0) 57 | 58 | if (i,j) in P or (j,i) in P: 59 | w[i,j] == (0 if (i,j) in P else 1) 60 | 61 | #set bounds on x 62 | for i in N: 63 | L[i] <= x[i] <= U[i] - D[i] 64 | 65 | 66 | solver(int, 67 | #this branching option often helps 68 | br_tech=glpk.GLP_BR_PCH, 69 | ) 70 | solve() 71 | 72 | print("status:", status()) 73 | print("revenue:", vobj(), 'max:', sum(R)) 74 | print("schedule:") 75 | for i in N: 76 | start = x[i].primal 77 | used = u[i].primal 78 | print("job %i:"%i, start, start+D[i], 79 | "Accept" if used else "Reject", 80 | R[i]/float(D[i])) 81 | -------------------------------------------------------------------------------- /docs/saves.py: -------------------------------------------------------------------------------- 1 | from pymprog import * 2 | begin('save') 3 | x = var('x', 3) 4 | x[2].kind = int 5 | c = par('c', [100, 300, 50]) 6 | b = par('b', [93000, 101, 201]) 7 | maximize(sum(c[i]*x[i] for i in range(3)), 'Profit') 8 | 9 | 300*x[0] + 1200*x[1] + 120*x[2] <= b[0] 10 | 0.5*x[0] + x[1] + 0.5*x[2] <= b[1] 11 | r = x[0] + x[1] + x[2] <= b[2] 12 | 13 | solve() 14 | 15 | save(mps='_save.mps', sol='_save.sol', 16 | clp='_save.clp', glp='_save.glp', 17 | sen='_save.sen', ipt='_save.ipt', 18 | mip='_save.mip') 19 | 20 | end() 21 | -------------------------------------------------------------------------------- /docs/sensana.py: -------------------------------------------------------------------------------- 1 | from pymprog import * 2 | begin('trader') 3 | x = var('x', 3) 4 | c = par('c', [100, 300, 50]) 5 | b = par('b', [93000, 101, 201]) 6 | maximize(sum(c[i]*x[i] for i in range(3)), 'Profit') 7 | 8 | 300*x[0] + 1200*x[1] + 120*x[2] <= b[0] 9 | 0.5*x[0] + x[1] + 0.5*x[2] <= b[1] 10 | 11 | solve() 12 | sensitivity() 13 | 14 | b[1].value += 1 15 | solve() 16 | 17 | end() 18 | -------------------------------------------------------------------------------- /docs/solvopt.rst: -------------------------------------------------------------------------------- 1 | 2 | Using Solver Options 3 | ==================== 4 | 5 | Currently, there are four solvers available for use, 6 | and each solver comes with a set of options for 7 | the parameters that control its behavior. 8 | Use the global function *solver(...)* 9 | to select the solver for your model, 10 | and to set the desired options for the 11 | chosen solver. 12 | 13 | .. _setgetopt: 14 | 15 | Setting/getting options 16 | ---------------------------- 17 | 18 | The four solvers available for use are 19 | each given a name: 'simplex', 'exact', 'interior', 20 | and 'intopt'. Note that the first three 21 | are good for solving linear programs, whereas the 22 | last ('intopt') is good for solving integer programs. 23 | That means we only have choices (at least for now) 24 | when we have a linear program to solve. 25 | To select a particular solver, just do: 26 | 27 | >>> solver('interior') 28 | 29 | That would set 'interior' solver as the solver for linear programming. 30 | You may also provide options at the same time: 31 | 32 | >>> solver('interior', msg_lev=glpk.GLP_MSG_OFF) 33 | {'msg_lev': 0} 34 | 35 | which would turn off message output from the solver, 36 | and return the current values for options explicitly set. 37 | For options whose value is not set, they would take the default value. 38 | For information on what options and values are available for a 39 | particular solver, try something like this: 40 | 41 | >>> solver(help = 'interior') 42 | 43 | To find out which solver will be used for linear programming: 44 | 45 | >>> solver(float) 46 | 'simplex' 47 | 48 | To find out which solver will be used for integer programming: 49 | 50 | >>> solver(int) 51 | 'intopt' 52 | 53 | To set options on one of the default solvers, for example, use 54 | 55 | >>> solver(int, br_tech=glpk.GLP_BR_PCH) 56 | 57 | to select the hybrid pseudo-cost heuristic(PCH) 58 | branching technique for the integer optimizer. 59 | These options will be used when you call the solve() 60 | funtion/method. Note that you may also provide such 61 | options as keyword arguments to the solve() function/method, 62 | which will take precedence over the options you set in 63 | the solver(...) call. 64 | 65 | 66 | .. _delopts: 67 | 68 | Deleting an option 69 | ------------------- 70 | 71 | To delete an option, simply set it to *None*. 72 | 73 | >>> solver(int, msg_lev=None) 74 | 75 | This would delete the explicit value for the 'msg_lev' option, 76 | and leave it at its default value. 77 | 78 | Note: if you need to warm start your simplex method 79 | (that is, start the simplex method with the optimal 80 | basis from your last invocation, which is often used 81 | when employing row generation and/or column generation), 82 | please don't turn on the 'presolve' option. 83 | 84 | -------------------------------------------------------------------------------- /docs/ssudoku.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | """Sudoku, also known as Number Place, is a logic-based placement 3 | puzzle. The aim of the canonical puzzle is to enter a numerical 4 | digit from 1 through 9 in each cell of a 9x9 grid made up of 3x3 5 | subgrids (called "regions"), starting with various digits given in 6 | some cells (the "givens"). Each row, column, and region must contain 7 | only one instance of each numeral. 8 | 9 | (From Wikipedia, the free encyclopedia.) 10 | 11 | This example will provide a sample Super Sudoku: 12 | in addition to satisfying all the requirements 13 | of Sudoku, Super Sudoku also requires that the 14 | elements in each diagonal must be distinct.""" 15 | 16 | from pymprog import * # Import the module 17 | begin("ssudoku") 18 | I = range(1,10) 19 | J = range(1,10) 20 | K = range(1,10) 21 | T = iprod(I,J,K) #create Indice tuples 22 | #x[i,j,k] = 1 means cell [i,j] is assigned number k 23 | x = var('x', T, bool) #binary vars 24 | #each cell must be assigned exactly one number 25 | [sum(x[i,j,k] for k in K)==1 for i in I for j in J] 26 | #cells in the same row must be assigned distinct numbers 27 | [sum(x[i,j,k] for j in J)==1 for i in I for k in K] 28 | #cells in the same column must be assigned distinct numbers 29 | [sum(x[i,j,k] for i in I)==1 for j in J for k in K] 30 | #cells in the same region must be assigned distinct numbers 31 | [sum(x[i,j,k] for i in range(r,r+3) for j in range(c, c+3))==1 32 | for r in range(1,10,3) for c in range(1,10,3) for k in K] 33 | 34 | #cells in \-diagonal 35 | [sum(x[i,i,k] for i in I)==1 for k in K] 36 | 37 | #cells in /-diagonal 38 | [sum(x[i,10-i,k] for i in I)==1 for k in K] 39 | 40 | #there is no need for an objective function here 41 | 42 | solve() 43 | 44 | for i in I: 45 | if i in range(1,10,3): 46 | print(" +-------+-------+-------+") 47 | print('', end=' ') 48 | for j in J: 49 | if j in range(1,10,3): print("|", end=' ') 50 | print("%g"%sum(x[i,j,k].primal*k for k in K), end=' ') 51 | if j==9: print("|") 52 | if i == 9: 53 | print(" +-------+-------+-------+") 54 | 55 | end() 56 | -------------------------------------------------------------------------------- /docs/subtour.rst: -------------------------------------------------------------------------------- 1 | .. A simple tutorial by Yingjie Lan, May 2009. 2 | 3 | Case: Subtour Elimination 4 | ========================= 5 | 6 | This example is adapted from the presentation 7 | titled *Mathematical Programming: Modeling and 8 | Applications* by Giacomo Nannicini. Thanks are 9 | hereby extended to him. 10 | 11 | #. :ref:`intro_tsp` 12 | #. :ref:`subtelim` 13 | #. :ref:`implement` 14 | 15 | .. _intro_tsp: 16 | 17 | Brief Introduction 18 | ------------------- 19 | 20 | The Traveling Salesman Problem (TSP) is 21 | a very well known problem in the literature. 22 | Applications of TSP include: logistics, 23 | crane control, placing circuits on a board 24 | minimizing the required time, and many more. 25 | Unfortunately, it is a very difficult problem. 26 | For not too large instances, it can be done 27 | on a desktop machine. 28 | 29 | 30 | Here is a definition of a TSP problem: 31 | A salesman must visit all cities to see his customers, 32 | and return to the starting point. 33 | He wants to minimize the total travel distance. 34 | Here are are going to play with a small example 35 | of TSP, assuming that the distance between any 36 | two cities is symmetric. 37 | 38 | .. _subtelim: 39 | 40 | Subtour Elimination 41 | ------------------- 42 | 43 | A subtour is also a round tour that returns back 44 | to where you start, but does not visit all the cities. 45 | A formulation of TSP is this: 46 | 47 | #. enter each city exactly once. 48 | 49 | #. leave each city excatly once. 50 | 51 | #. make sure there is no subtour. 52 | 53 | To make sure there is no subtour, we must consider 54 | *all* subset of cities, and make sure that there 55 | is an arc leaving a city in the subset and entering 56 | a city NOT in the subset. So there are exponential 57 | number of subtour elimination constraints. 58 | Obviously, only a small number of them will be 59 | actually needed to eliminate subtours. 60 | The idea is to start out without them and then 61 | add those violated ones gradually, 62 | until the solution contains no subtour. 63 | For a more detailed discussion on TSP, please see 64 | http://www.tsp.gatech.edu/methods/opt/subtour.htm 65 | 66 | .. _implement: 67 | 68 | Implementation 69 | -------------- 70 | 71 | This is how I have it implemented using PyMathProg: 72 | 73 | .. literalinclude:: ./elimsubt.py 74 | :linenos: 75 | 76 | And here is the output:: 77 | 78 | there are 7 cities 79 | New subtour: [0, 4, 2] 80 | New subtour: [0, 6, 3, 2, 4] 81 | Optimal tour length: 153.0 82 | Optimal tour: 83 | [0, 5, 1, 6, 3, 2, 4] 84 | 85 | -------------------------------------------------------------------------------- /docs/sudoku.py: -------------------------------------------------------------------------------- 1 | # SUDOKU, Number Placement Puzzle 2 | from __future__ import print_function 3 | """Sudoku, also known as Number Place, is a logic-based placement 4 | puzzle. The aim of the canonical puzzle is to enter a numerical 5 | digit from 1 through 9 in each cell of a 9x9 grid made up of 3x3 6 | subgrids (called "regions"), starting with various digits given in 7 | some cells (the "givens"). Each row, column, and region must contain 8 | only one instance of each numeral. 9 | 10 | Example: 11 | 12 | +-------+-------+-------+ 13 | | 5 3 . | . 7 . | . . . | 14 | | 6 . . | 1 9 5 | . . . | 15 | | . 9 8 | . . . | . 6 . | 16 | +-------+-------+-------+ 17 | | 8 . . | . 6 . | . . 3 | 18 | | 4 . . | 8 . 3 | . . 1 | 19 | | 7 . . | . 2 . | . . 6 | 20 | +-------+-------+-------+ 21 | | . 6 . | . . . | 2 8 . | 22 | | . . . | 4 1 9 | . . 5 | 23 | | . . . | . 8 . | . 7 9 | 24 | +-------+-------+-------+ 25 | 26 | (From Wikipedia, the free encyclopedia.)""" 27 | 28 | # the "givens" 29 | g =( 30 | (5,3,0,0,7,0,0,0,0), 31 | (6,0,0,1,9,5,0,0,0), 32 | (0,9,8,0,0,0,0,6,0), 33 | (8,0,0,0,6,0,0,0,3), 34 | (4,0,0,8,0,3,0,0,1), 35 | (7,0,0,0,2,0,0,0,6), 36 | (0,6,0,0,0,0,2,8,0), 37 | (0,0,0,4,1,9,0,0,5), 38 | (0,0,0,0,8,0,0,7,9)) 39 | 40 | from pymprog import model, iprod 41 | p = model("sudoku") 42 | I = range(1,10) 43 | J = range(1,10) 44 | K = range(1,10) 45 | T = iprod(I,J,K) #create Indice tuples 46 | x = p.var('x', T, bool) 47 | #x[i,j,k] = 1 means cell [i,j] is assigned number k 48 | #assign pre-defined numbers using the "givens" 49 | [x[i,j,k] == (1 if g[i-1][j-1] == k else 0) 50 | for (i,j,k) in T if g[i-1][j-1] > 0 ] 51 | 52 | #each cell must be assigned exactly one number 53 | [sum(x[i,j,k] for k in K)==1 for i in I for j in J] 54 | 55 | #cells in the same row must be assigned distinct numbers 56 | [sum(x[i,j,k] for j in J)==1 for i in I for k in K] 57 | 58 | #cells in the same column must be assigned distinct numbers 59 | [sum(x[i,j,k] for i in I)==1 for j in J for k in K] 60 | 61 | #cells in \-diagonal 62 | #p.st([sum(x[i,i,k] for i in I)==1 for k in K]) 63 | 64 | #cells in /-diagonal 65 | #p.st([sum(x[i,10-i,k] for i in I)==1 for k in K]) 66 | 67 | #cells in the same region must be assigned distinct numbers 68 | [sum(x[i,j,k] for i in range(r,r+3) for j in range(c, c+3))==1 69 | for r in range(1,10,3) for c in range(1,10,3) for k in K] 70 | 71 | #there is no need for an objective function here 72 | 73 | p.solve() 74 | 75 | for i in I: 76 | if i in range(1,10,3): 77 | print(" +-------+-------+-------+") 78 | print('', end=' ') 79 | for j in J: 80 | if j in range(1,10,3): print("|", end=' ') 81 | print("%g"%sum(x[i,j,k].primal*k for k in K), end=' ') 82 | if j==9: print("|") 83 | if i == 9: 84 | print(" +-------+-------+-------+") 85 | p.end() 86 | -------------------------------------------------------------------------------- /docs/tsp.py: -------------------------------------------------------------------------------- 1 | # TSP, Traveling Salesman Problem 2 | # Model in pymprog by Yingjie Lan 3 | 4 | # The Traveling Salesman Problem (TSP) is a famous problem. 5 | # Let a directed graph G = (V, E) be given, where V = {1, ..., n} is 6 | # a set of nodes, E V x V is a set of arcs. Let also each arc 7 | # e = (i,j) be assigned a number c[i,j], which is the length of the 8 | # arc e. The problem is to find a closed path of minimal length going 9 | # through each node of G exactly once. 10 | from __future__ import print_function 11 | 12 | n = 16 #number of nodes 13 | V = range(1,n+1) #set of notes 14 | #cost or each arc, format: (start, end):cost 15 | c = {(1,2):509, (1,3):501, (1,4):312, (1,5):1019, (1,6):736, (1,7):656, 16 | (1,8): 60, (1,9):1039, (1,10):726, (1,11):2314, (1,12):479, 17 | (1,13):448, (1,14):479, (1,15):619, (1,16):150, 18 | (2,1):509, (2,3):126, (2,4):474, (2,5):1526, (2,6):1226, (2,7):1133, 19 | (2,8):532, (2,9):1449, (2,10):1122, (2,11):2789, (2,12):958, 20 | (2,13):941, (2,14):978, (2,15):1127, (2,16):542, 21 | (3,1):501, (3,2):126, (3,4):541, (3,5):1516, (3,6):1184, (3,7):1084, 22 | (3,8):536, (3,9):1371, (3,10):1045, (3,11):2728, (3,12):913, 23 | (3,13):904, (3,14):946, (3,15):1115, (3,16):499, 24 | (4,1):312, (4,2):474, (4,3):541, (4,5):1157, (4,6):980, (4,7):919, 25 | (4,8):271, (4,9):1333, (4,10):1029, (4,11):2553, (4,12):751, 26 | (4,13):704, (4,14):720, (4,15):783, (4,16):455, 27 | (5,1):1019, (5,2):1526, (5,3):1516, (5,4):1157, (5,6):478, (5,7):583, 28 | (5,8):996, (5,9):858, (5,10):855, (5,11):1504, (5,12):677, 29 | (5,13):651, (5,14):600, (5,15):401, (5,16):1033, 30 | (6,1):736, (6,2):1226, (6,3):1184, (6,4):980, (6,5):478, (6,7):115, 31 | (6,8):740, (6,9):470, (6,10):379, (6,11):1581, (6,12):271, 32 | (6,13):289, (6,14):261, (6,15):308, (6,16):687, 33 | (7,1):656, (7,2):1133, (7,3):1084, (7,4):919, (7,5):583, (7,6):115, 34 | (7,8):667, (7,9):455, (7,10):288, (7,11):1661, (7,12):177, 35 | (7,13):216, (7,14):207, (7,15):343, (7,16):592, 36 | (8,1): 60, (8,2):532, (8,3):536, (8,4):271, (8,5):996, (8,6):740, 37 | (8,7):667, (8,9):1066, (8,10):759, (8,11):2320, (8,12):493, 38 | (8,13):454, (8,14):479, (8,15):598, (8,16):206, 39 | (9,1):1039, (9,2):1449, (9,3):1371, (9,4):1333, (9,5):858, (9,6):470, 40 | (9,7):455, (9,8):1066, (9,10):328, (9,11):1387, (9,12):591, 41 | (9,13):650, (9,14):656, (9,15):776, (9,16):933, 42 | (10,1):726, (10,2):1122, (10,3):1045, (10,4):1029, (10,5):855, 43 | (10,6):379, (10,7):288, (10,8):759, (10,9):328, (10,11):1697, 44 | (10,12):333, (10,13):400, (10,14):427, (10,15):622, (10,16):610, 45 | (11,1):2314, (11,2):2789, (11,3):2728, (11,4):2553, (11,5):1504, 46 | (11,6):1581, (11,7):1661, (11,8):2320, (11,9):1387, (11,10):1697, 47 | (11,12):1838, (11,13):1868, (11,14):1841, (11,15):1789, (11,16):2248, 48 | (12,1):479, (12,2):958, (12,3):913, (12,4):751, (12,5):677, (12,6):271, 49 | (12,7):177, (12,8):493, (12,9):591, (12,10):333, (12,11):1838, 50 | (12,13): 68, (12,14):105, (12,15):336, (12,16):417, 51 | (13,1):448, (13,2):941, (13,3):904, (13,4):704, (13,5):651, (13,6):289, 52 | (13,7):216, (13,8):454, (13,9):650, (13,10):400, (13,11):1868, 53 | (13,12): 68, (13,14): 52, (13,15):287, (13,16):406, 54 | (14,1):479, (14,2):978, (14,3):946, (14,4):720, (14,5):600, (14,6):261, 55 | (14,7):207, (14,8):479, (14,9):656, (14,10):427, (14,11):1841, 56 | (14,12):105, (14,13): 52, (14,15):237, (14,16):449, 57 | (15,1):619, (15,2):1127, (15,3):1115, (15,4):783, (15,5):401, (15,6):308, 58 | (15,7):343, (15,8):598, (15,9):776, (15,10):622, (15,11):1789, 59 | (15,12):336, (15,13):287, (15,14):237, (15,16):636, 60 | (16,1):150, (16,2):542, (16,3):499, (16,4):455, (16,5):1033, (16,6):687, 61 | (16,7):592, (16,8):206, (16,9):933, (16,10):610, (16,11):2248, 62 | (16,12):417, (16,13):406, (16,14):449, (16,15):636} 63 | #set of arcs: (i,j) repr an arc from i to j 64 | E = c.keys() 65 | 66 | from pymprog import model 67 | p = model("tsp") 68 | x = p.var('x', E, bool) # created over E. 69 | #minize the total travel distance 70 | p.min(sum(c[t]*x[t] for t in E), 'totaldist') 71 | #subject to: leave each city exactly once 72 | for k in V: sum(x[k,j] for j in V if (k,j) in E)==1 73 | #subject to: enter each city exactly once 74 | for k in V: sum(x[i,k] for i in V if (i,k) in E)==1 75 | 76 | #some flow constraints to eliminate subtours. 77 | #y: the number of cars carried: city 1 has n cars. 78 | #exactly one car will be distributed to each city. 79 | y=p.var('y', E) 80 | for t in E: (n-1)*x[t] >= y[t] 81 | for k in V: ( 82 | sum(y[k,j] for j in V if (k,j) in E) # cars out 83 | - sum(y[i,k] for i in V if (i,k) in E) # cars in 84 | == (n if k==1 else 0) - 1 ) 85 | 86 | p.solve(float) #solve as LP only. 87 | print("simplex done: %r"% p.status()) 88 | p.solve(int) #solve the IP problem 89 | print(p.vobj()) 90 | tour = [t for t in E if x[t].primal>.5] 91 | cat, car = 1, n 92 | print("This is the optimal tour with [cars carried]:") 93 | for k in V: 94 | print(cat, end='') 95 | for i,j in tour: 96 | if i==cat: 97 | print("[%g]"%y[i,j].primal, end='') 98 | cat=j 99 | break 100 | print(cat) 101 | p.end() 102 | 103 | -------------------------------------------------------------------------------- /docs/vars.rst: -------------------------------------------------------------------------------- 1 | Working with variables 2 | ====================== 3 | 4 | Once the model is created, you need variables 5 | to make the objective and the constraints. 6 | In this section we talk about 7 | how to create and work with variables. 8 | 9 | #. :ref:`create` 10 | #. :ref:`bounds` 11 | 12 | .. _create: 13 | 14 | Creating variables 15 | -------------------- 16 | 17 | The routine *var(...)* is the only tool to create variables. 18 | Yet there are quite a few different ways to do so, depending 19 | on the modelling situation. For the sake of communication, 20 | a variable name must be provided when you create them. 21 | You can create a single variable, or quite a few, 22 | or even a huge group of variables at once. 23 | These scenarios are illustrated below: 24 | 25 | >>> from pymprog import * 26 | >>> begin('test') 27 | model('test') is the default model. 28 | >>> X = var('X') 29 | >>> X # by default, it is non-negative and continuous 30 | X >= 0 continuous 31 | >>> x, y = var('x, y') # many names -> many vars 32 | >>> x, y 33 | (x >= 0 continuous, y >= 0 continuous) 34 | >>> z = var('z', 3) 35 | >>> z # an array of 3 variables 36 | [z[0] >= 0 continuous, z[1] >= 0 continuous, z[2] >= 0 continuous] 37 | >>> z[2] # access the third variable 38 | z[2] >= 0 continuous 39 | >>> v = var('v', kind=bool) # 0/1 variable 40 | >>> v 41 | 0 <= v <= 1 binary 42 | >>> w = var('w', bounds=(0,5)) # specify the bounds 43 | >>> w 44 | 0 <= w <= 5 continuous 45 | >>> colors = ('red', 'green', 'blue') # index set 46 | >>> clr = var('color', colors, bool) # using an index set 47 | >>> clr # a dictionary with keys from the index set 48 | {'blue': 0 <= color['blue'] <= 1 binary, 'green': 0 <= color['green'] <= 1 binary, 49 | 'red': 0 <= color['red'] <= 1 binary} 50 | >>> clr['green'] 51 | 0 <= color['green'] <= 1 binary 52 | 53 | That interactive session demostrates different ways to use 54 | the function *var(...)* to create variables. Of course 55 | you may combine those ways to get things done efficiently. 56 | Basically, there are three conventions for variable creation: 57 | 58 | 1. provide all the names literally, in a single string using 59 | commas to separate them, to manually create 60 | a few variables, usally for small models. 61 | 2. provide one single name, and a positive integer, to 62 | create an array of variables indexed by integers. 63 | 3. provide one single name, and a set of indices, to 64 | create a dictionary with keys from the index set. 65 | 66 | Once you decide to follow one convention, then you may 67 | further customize the variables by furnishing values 68 | for the other arguments to the function call: 69 | 70 | - kind: specify what kind of variable to make. 71 | admissable values are: 72 | 73 | 1. float (default): continuous 74 | 2. int: integer 75 | 3. bool: binary, side-effect: reset bounds to (0,1) 76 | 77 | - bounds: a pair of numbers, for the lower and upper bounds. 78 | If None is used, it means unbounded. The default value 79 | is (0, None), so the lower bound is 0, upper bound is none. 80 | 81 | Note, you may also obtain help within the Python session by: 82 | 83 | >>> help(var) # obtain help on this function 84 | 85 | .. _bounds: 86 | 87 | Change bounds and kind 88 | ---------------------- 89 | 90 | Once you have created variables, you may further explicitly set 91 | bounds on some variables, it is qutie straight forward: 92 | 93 | >>> begin('test') 94 | model('test') is the default model. 95 | >>> x = var('x') 96 | >>> x <= 5 97 | 0 <= x <= 5 continuous 98 | >>> x >= 2 99 | 2 <= x <= 5 continuous 100 | >>> x == 3 101 | (x==3) continuous 102 | >>> x == 4 103 | 4 <= x <= 3 continuous 104 | >>> z = var('z', 3) 105 | >>> b = [5, 8, 3] 106 | >>> for i in range(3): z[i] <= b[i] 107 | ... 108 | 0 <= z[0] <= 5 continuous 109 | 0 <= z[1] <= 8 continuous 110 | 0 <= z[2] <= 3 continuous 111 | 112 | The most important thing to remember is this: bounds added 113 | by using '<=', '>=', and '==' are accumulative. Later bounds 114 | won't invalidate former bounds. That is why when we set 115 | *x == 4* after *x == 3* we got infeasibility: *4 <= x <= 3*. 116 | It is possble to cancle all previous bounds and start anew. 117 | Continue from where we have left off in the last Python session: 118 | 119 | >>> x.reset(1, 5) 120 | 1 <= x <= 5 continuous 121 | >>> x <= 10 122 | 1 <= x <= 5 continuous 123 | >>> x.reset() 124 | x >= 0 continuous 125 | >>> x <= 10 126 | 0 <= x <= 10 continuous 127 | 128 | This new interactive session below shows the interaction between 129 | kinds and bounds. The key concept behind all this is that a binary 130 | variable is *defined* as an integer variable between 0 and 1. 131 | 132 | >>> begin('test') 133 | model('test') is the default model. 134 | >>> x = var('x') 135 | >>> x.kind = int 136 | >>> x 137 | x >= 0 integer 138 | >>> x <= 1 139 | 0 <= x <= 1 binary 140 | >>> x.reset(0, 5) 141 | 0 <= x <= 5 integer 142 | >>> x.kind = bool 143 | >>> x 144 | 0 <= x <= 1 binary 145 | >>> x.kind = int 146 | >>> x 147 | 0 <= x <= 1 binary 148 | >>> x.kind = float 149 | >>> x 150 | 0 <= x <= 1 continuous 151 | 152 | We may also use parameters for bounds, in such case, when the 153 | parameters change value, the bounds get updated automatically. 154 | 155 | >>> p = par('p', 3) 156 | >>> p 157 | (p:3) 158 | >>> begin('test') 159 | model('test') is the default model. 160 | >>> x = var('x') 161 | >>> x <= p 162 | 0 <= x <= (p:3) continuous 163 | >>> p.value = 4 164 | >>> x 165 | 0 <= x <= (p:4) continuous 166 | >>> x.bounds 167 | (0, 4) 168 | 169 | The last line of code obtains the numerical value of bounds. 170 | We will discuss parameters in more details in a later section. 171 | -------------------------------------------------------------------------------- /html/.buildinfo: -------------------------------------------------------------------------------- 1 | # Sphinx build info version 1 2 | # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. 3 | config: fa1ee9ab31b463f40e7445d938ce5590 4 | tags: 645f666f9bcd5a90fca523b33c5a78b7 5 | -------------------------------------------------------------------------------- /html/_sources/appendix.rst.txt: -------------------------------------------------------------------------------- 1 | Appendix: Installing MinGW on Windows 2 | ===================================== 3 | 4 | Note: this appendix is for the setup of the OLDER versions of PyMathProg. 5 | 6 | 1. Download the MinGW installer from 7 | http://www.mingw.org/wiki/HOWTO_Install_the_MinGW_GCC_Compiler_Suite. 8 | (As of this 9 | writing, the download link is a bit difficult to find; it's under 10 | "About" in the menu on the left-hand side). You want the file 11 | entitled "Automated MinGW Installer" (currently version 5.1.4). 12 | 2. Run it and install MinGW. Only the basic package is strictly 13 | needed for Cython, although you might want to grab at least the 14 | C++ compiler as well. 15 | 3. You need to set up Windows' "PATH" environment variable so that 16 | includes e.g. "c:\\mingw\\bin" (if you installed MinGW to 17 | "c:\\mingw"). The following web-page describes the procedure 18 | in Windows XP (the Vista procedure is similar): 19 | http://support.microsoft.com/kb/310519 20 | 4. Finally, tell Python to use MinGW as the default compiler 21 | (otherwise it will try for Visual C). If Python is installed to 22 | "c:\\Python26", create a file named 23 | "c:\\Python26\\Lib\\distutils\\distutils.cfg" containing:: 24 | 25 | [build] 26 | compiler = mingw32 27 | 28 | The [WinInst]_ wiki page contains updated information about this 29 | procedure. Any contributions towards making the Windows install 30 | process smoother is welcomed; it is an unfortunate fact that none of 31 | the regular Cython developers have convenient access to Windows. 32 | 33 | .. [WinInst] http://wiki.cython.org/InstallingOnWindows 34 | -------------------------------------------------------------------------------- /html/_sources/constr.rst.txt: -------------------------------------------------------------------------------- 1 | Working with Constraints 2 | ======================== 3 | 4 | Defining constraints in PyMathProg is very easy. 5 | After creating the model, 6 | you can use two different styles to add constraints to it. 7 | 8 | 1. the implicit way(the more natural way): 9 | simply use inequalities(comparisons using '<=', '==', '>=') 10 | to construct constraints. 11 | 12 | 2. the explicit way: use the *st(.)* method to add constraints. 13 | This way is a little more combersome, and it is the old way. 14 | 15 | Which way to go entirely depends on your taste, the results are the same. 16 | 17 | #. :ref:`single` 18 | #. :ref:`double` 19 | #. :ref:`group` 20 | 21 | .. _single: 22 | 23 | With a single comparison 24 | ---------------------------- 25 | 26 | Let's first use the more natural way to add constraints:: 27 | 28 | begin('illustration') 29 | x = var('x', 3) 30 | y = var('y') 31 | c = [6,4,3] 32 | sum(p*q for p,q in zip(c,x)) <= y 33 | x[0] + x[2] >= x[1] 34 | x[0] + x[2] <= 1 35 | 36 | Now the equivalence in the explicit way is as follows:: 37 | 38 | begin('illustration') 39 | x = var('x', 3) 40 | y = var('y') 41 | c = [6,4,3] 42 | st( sum(p*q for p,q in zip(c,x)) <= y ) 43 | st( x[0] + x[2] >= x[1] ) 44 | st( x[0] + x[2] <= 1 ) 45 | 46 | Surely, we can use index sets to make the model 47 | more easy to read and write, as seen in this more 48 | interesting model for diet optimization:: 49 | 50 | from pymprog import * 51 | fruits = ('apple', 'pear', 'orange') 52 | nutrit = ('fat', 'fibre', 'vitamin') 53 | ntrmin = ( 10, 50, 30 ) # min nutrition intake 54 | #nutrition ingredients of each fruit 55 | ingred = ('apple': (3,4,5), 'pear': (2,4,1), 56 | 'orange': (2,3,4)) 57 | diet = var('diet', fruits, int) 58 | for n, ntr in enumerate(nutrit): 59 | sum( diet[frt] * ingred[frt][n] 60 | for frt in fruits) >= ntrmin[n] 61 | 62 | Those constraints are perfectly fine: they just have 63 | one comparison. Now let's get a little more sophisticated. 64 | 65 | .. _double: 66 | 67 | With double comparisons 68 | ---------------------------- 69 | 70 | Things get more interesting when we use continuous comparisons in 71 | Python to specify both the lower and upper bounds in one expression:: 72 | 73 | >>> begin('model') 74 | >>> x, y = var('x, y') 75 | >>> 3 <= 4 * x + 5 * y <= 6 76 | 77 | The new thing appears on the last line. 78 | It bounds the linear expression in the middle 79 | by both a lower and upper bound using continuous comparison. 80 | 81 | Before we move on, let's do a little side talk on 82 | the similarity of variable bounds and constraints. 83 | We already know how to bound a variable *x*:: 84 | 85 | >>> x <= 100 86 | >>> 1 <= x <= 5 87 | 88 | From pure mathematical sense, bounds are just a special case of 89 | constraints. And PyMathProg honors that sense, in that 90 | the effect is the same as if it were a constraint. 91 | Yet, in terms of how things gets done inside, that 92 | simply adds to the list of bounds for *x*, so that 93 | all the bounds are simultaneous (adding a bound to a variable 94 | does *not* cancle any of its previous bounds), 95 | just like constraints do. 96 | 97 | More than two continuous comparisons are not encouraged in 98 | PyMathProg. The major purpose of continuous comparisons 99 | is to set both the lower and the upper bounds for a row, 100 | in which case the two bounds must not contain variables. 101 | However, it is entirely legal to use as many comparisons 102 | as you like, the only caution is that in Python, if 103 | any of the comparison evaluates to a False, all the 104 | later comparisons will not be evaluated, and thus 105 | won't take any effect (i.e. they would not produce 106 | constraints). 107 | 108 | 109 | .. _group: 110 | 111 | 112 | Grouping constraints 113 | -------------------- 114 | 115 | Sometimes, it is necessary to use a Python variable to 116 | hold a constraint for later use, for example, to 117 | obtain the dual value for a constraint. This is simple: 118 | 119 | >>> begin('model') 120 | model('model') is the default model. 121 | >>> x, y = var('x, y') 122 | >>> R = 3 * x + y >= 5 123 | >>> R 124 | R1: 3 * x + y >= 5 125 | >>> R.name 126 | 'R1' 127 | >>> R.name = 'Sugar Level' 128 | >>> R 129 | Sugar Level: 3 * x + y >= 5 130 | 131 | Upon creation, a constraint is given a default name 132 | like this: *R#*, where *#* is the serial number. 133 | Sometimes, it is desirable to change to a more meaningful 134 | name, which can be done by assigning to the *name* property of 135 | a constraint. Of course, it can also be done by employing 136 | the *st(...)* function with the argument for *name* supplied. 137 | Use *help(st)* in an interactive session for more information. 138 | Another more elegant solution is to use a group object to 139 | group desired constraints together. The cool thing about 140 | group objects is that they can automatically name the 141 | constraints by the group name with the index used as subscript. 142 | Here is an illustration of using group objects:: 143 | 144 | from pymprog import * 145 | minlev = group('minlev') 146 | fruits = ('apple', 'pear', 'orange') 147 | nutrit = ('fat', 'fibre', 'vitamin') 148 | ntrmin = ( 10, 50, 30 ) # min nutrition intake 149 | #nutrition ingredients of each fruit 150 | ingred = ('apple': (3,4,5), 'pear': (2,4,1), 151 | 'orange': (2,3,4)) 152 | diet = var('diet', fruits, int) 153 | for n, ntr in enumerate(nutrit): 154 | minlev[ntr] = sum( diet[frt] * ingred[frt][n] 155 | for frt in fruits) >= ntrmin[n] 156 | 157 | The new stuff here is that we use a group called 'minlev' 158 | to hold the constraints, so *minlev['fat']* would hold 159 | the constraint for minimal level of fat, and that 160 | constraint is also given an informative name "minlev['fat']". 161 | The resultant model would be much easier to understand. 162 | 163 | -------------------------------------------------------------------------------- /html/_sources/index.rst.txt: -------------------------------------------------------------------------------- 1 | .. pymprog documentation master file, created by 2 | sphinx-quickstart on Thu May 21 09:19:35 2009. 3 | 4 | Welcome to PyMathProg 5 | ===================== 6 | 7 | This is the documentation for **PyMathProg**. 8 | PyMathProg is an easy and flexible mathematical programming 9 | environment for Python. It makes modelling, solving, analyzing, 10 | modifying and manipulating linear programs 11 | super easy and highly flexible in Python. 12 | 13 | Great features offered by PyMathProg include: 14 | 15 | - Ergonomic syntax for modelling 16 | - Friendly interactive session 17 | - Sensitivity report 18 | - Advanced solver options 19 | - Automatic model update on parameter changes 20 | - Parameters sharable between models 21 | - Deleting variables/constraints 22 | - Supporting both Python 2 and 3 23 | - Supporting all major platforms 24 | 25 | 26 | Quick installation 27 | ================== 28 | 29 | Assuming you already have Python 2 or Python 3 installed, now open a 30 | terminal window (also known as a command window), and type in this 31 | line of command and hit return:: 32 | 33 | pip install pymprog 34 | 35 | That's it! In a couple of minutes it will download the necessary packages 36 | and install them, and you should be all set. 37 | Since pymprog is a pure Python project that only depends on swiglpk, 38 | it can be installed this way wherever swiglpk can be installed. 39 | Currently, swiglpk comes with binary wheels for Windows, Mac, and Linux. 40 | If you'd like to have pymprog installed on other exotic platforms, 41 | the only hurdle to overcome is to get swiglpk installed there first. 42 | 43 | Quick example 44 | =============== 45 | 46 | Below is a small example taken from :ref:`whetting`:: 47 | 48 | from pymprog import * 49 | begin('bike production') 50 | x, y = var('x, y') # variables 51 | maximize(15 * x + 10 * y, 'profit') 52 | x <= 3 # mountain bike limit 53 | y <= 4 # racer production limit 54 | x + y <= 5 # metal finishing limit 55 | solve() 56 | 57 | 58 | Getting Help 59 | ================ 60 | 61 | There are several ways to get some help: 62 | 63 | #. Project homepage: http://pymprog.sf.net/ 64 | 65 | #. Email list: https://lists.sourceforge.net/mailman/listinfo/pymprog-help 66 | 67 | #. Forum: https://sourceforge.net/forum/forum.php?forum_id=942151 68 | 69 | 70 | .. raw:: html 71 | 72 |

More PyMathProg resources at 73 | 74 | SourceForge.net Logo

76 | 77 | Contents: 78 | ================ 79 | 80 | .. toctree:: 81 | :maxdepth: 2 82 | 83 | intro 84 | setup 85 | tutorial 86 | vars 87 | indices 88 | pars 89 | constr 90 | solvopt 91 | misc 92 | advanced 93 | subtour 94 | appendix 95 | 96 | Indices and tables 97 | ================== 98 | 99 | * :ref:`genindex` 100 | * :ref:`search` 101 | 102 | -------------------------------------------------------------------------------- /html/_sources/indices.rst.txt: -------------------------------------------------------------------------------- 1 | 2 | Working with Indices 3 | ====================== 4 | 5 | Without indices, it is really hard to work with big models: 6 | one has to give each variable a different name. In modern 7 | modeling languages such as AMPL and GMPL, index sets 8 | are an essential building instrument, which is really not 9 | surprising, as we see indices almost everywhere in advanced 10 | mathematics. PyMathProg also provides a solution 11 | to indexing into variables and constraints. 12 | 13 | #. :ref:`natural` 14 | #. :ref:`combined` 15 | #. :ref:`varidx` 16 | 17 | 18 | .. _natural: 19 | 20 | Natural indices 21 | ---------------- 22 | 23 | Python already provides some natural indices: 24 | tuples, lists, sets(such as the keys in a dict), 25 | or anything iterable(e.g. a generator, a sequence, etc.). 26 | For an object to become an legitimate index, it has 27 | to be immutable, which means that its value should 28 | not change (in the sense defined by the magic method of 29 | *__eq__*) during its entire life cycle. By the tacit 30 | contract between objects in Python, objects of equal 31 | value should also produce the same hash code, thus 32 | immutable objects have a constant hash code, 33 | which is used for indexing purposes to quickly locate 34 | an object in a set or dict. 35 | 36 | PyMathProg trusts you to provide a list, a tuple, a set, 37 | a dict (in such case only the keys are used for indices), 38 | or any iterable as a set of indices without repeated elements. 39 | They can be utilized in PyMathProg as indices 40 | for variables, constraints, and parameters. 41 | If your put duplicate indices there, things could get lost. 42 | 43 | .. _combined: 44 | 45 | Combined indices 46 | ---------------- 47 | 48 | PyMathProg also provides a way to combine smaller index sets 49 | into bigger ones by the concept of set product. Given two sets 50 | A and B, the product of A * B is defined as:: 51 | 52 | A * B = { (a,b) : a in A, b in B } 53 | 54 | In the pymprog module, there is a class to achieve this:: 55 | 56 | >>> from pymprog import * 57 | >>> A = (1, 2, 3) 58 | >>> B = (6, 7, 8) 59 | >>> C = iprod(A,B) 60 | >>> for c in C: print(c) 61 | ... 62 | (1, 6) (1, 7) (1, 8) (2, 6) (2, 7) (2, 8) (3, 6) (3, 7) (3, 8) 63 | 64 | Well, that's about it. By the way, the constructor *iprod(...)* 65 | can take as many iterables (sets, lists, tuples, or sequences) 66 | as you want -- that's the cool part of it. 67 | 68 | 69 | .. _varidx: 70 | 71 | Use index with variables 72 | ------------------------- 73 | 74 | It is simple to create many variables over some indices in PyMathProg. 75 | Let's continue the Python session above:: 76 | 77 | >>> begin('test') 78 | >>> x = var('x', C) 79 | >>> type(x) 80 | 81 | >>> x[2,7].name 82 | 'x[2,7]' 83 | 84 | So we use the combined set C as the index set to create variables, 85 | the major variable name is 'X'. 86 | Out of curiosity, we want to know the type of the python object 87 | referenced by 'x': it turns out to be a dict type -- probably that 88 | is not that important anyway, what is more important is how easily 89 | and intuitively it is indexed, as shown in the next line. 90 | 91 | -------------------------------------------------------------------------------- /html/_sources/intro.rst.txt: -------------------------------------------------------------------------------- 1 | .. pymprog documentation master file, created by 2 | sphinx-quickstart on Thu May 21 09:19:35 2009. 3 | 4 | ########################## 5 | Introduction to PyMathProg 6 | ########################## 7 | 8 | .. _whetting: 9 | 10 | Whetting your appetite 11 | ====================== 12 | 13 | If you'd like to have a powerful linear program(LP) solver 14 | wrapping around your little finger, then **PyMathProg** 15 | is the thing for you. 16 | PyMathProg provides an easy and flexible modelling syntax 17 | using *Python* to create and optimize mathematical programming models. 18 | It is kind of a reincarnation of AMPL and GNU MathProg in Python. 19 | To illustrate, we will solve this tiny LP model here:: 20 | 21 | maximize 15 x + 10 y # profit 22 | S.T. 23 | x <= 3 # mountain bike limit 24 | y <= 4 # racer limit 25 | x + y <= 5 # frame limit 26 | x >=0, y >=0 # non-negative 27 | 28 | Believed it or not, we can almost literally transcribe it 29 | into PyMathProg as follows:: 30 | 31 | from pymprog import * 32 | begin('bike production') 33 | x, y = var('x, y') # variables 34 | maximize(15 * x + 10 * y, 'profit') 35 | x <= 3 # mountain bike limit 36 | y <= 4 # racer production limit 37 | x + y <= 5 # metal finishing limit 38 | solve() 39 | 40 | For now, let's fire up an interactive python session, maybe in a 41 | terminal or a Python IDLE window, and try it all out like this: 42 | 43 | >>> from pymprog import * 44 | >>> begin('bike production') 45 | model('bikes production') is the default model. 46 | >>> x, y = var('x, y') # create variables 47 | >>> x, y # take a look at them 48 | (0 <= x continuous, 0 <= y continuous) 49 | >>> maximize(15*x + 10*y, 'profit') 50 | Max profit: 15 * x + 10 * y 51 | >>> x <= 3 52 | 0 <= x <= 3 continuous 53 | >>> y <= 4 54 | 0 <= y <= 4 continuous 55 | >>> x + y <= 5 56 | R1: x + y <= 5 57 | >>> solve() 58 | GLPK Simplex Optimizer, v4.60 59 | 1 row, 2 columns, 2 non-zeros 60 | * 0: obj = -0.000000000e+00 inf = 0.000e+00 (2) 61 | * 2: obj = 6.500000000e+01 inf = 0.000e+00 (0) 62 | OPTIMAL LP SOLUTION FOUND 63 | 64 | That's just the beginning. A lot of more sophisticated 65 | examples are included in other parts of this documentation. 66 | We can also do many other interesing things to 67 | a model, even after solving it, for example: 68 | 69 | - Conduct sensitivity analysis 70 | - Change the value of a parameter 71 | - Fix a variable to an arbitrary value 72 | - Manage the bounds of a variable or constraint 73 | - Change the type of a variable 74 | - Adding/deleting variables or constraints 75 | 76 | When we solve it again, the solver usually takes 77 | advantage of results from the latest solution. 78 | 79 | .. _dream: 80 | 81 | The Dream of PyMathProg 82 | ======================= 83 | 84 | The dream of **PyMathProg** is to do math programming in *Python* 85 | with ease and flexibility, to create, optimize, report, 86 | change and re-optimize your model without breaking a sweat. 87 | To make that dream come true, a few things are essential: 88 | 89 | - a good modelling language, and we've got Python 90 | - a powerful and flexible solver, and there is GLPK 91 | - an integrated toolset, with database, plotting, etc. 92 | - an interactive modelling enviromment for easy learning 93 | 94 | and for the last two, the answer is stiell "we've got Python". 95 | Being embedded in Python, you can take advantage of all the good 96 | stuff available in Python: such as easy database access, 97 | graphical presentation of your solution, statistical analysis, 98 | or use pymprog for artificial intelligence in games, etc. 99 | Interactive sessions enable us to get immediate feedback for 100 | each small step we take. We can also conveniently obtain help 101 | right within the session by using the 'help(.)' function. 102 | And we may quickly get an answer to our questions by conducting 103 | small experiments, or test out some ideas that arise at the occasion. 104 | Interactivity can make learning **PyMathProg** easy and fun. 105 | 106 | .. _features: 107 | 108 | Exciting new features 109 | ====================== 110 | 111 | Exciting new freatures offered by PyMathProg *v1.0* are as follows: 112 | 113 | - New syntax to make modelling easy and intuitive 114 | - Sensitivity analysis report 115 | - Deletion of variables/constraints 116 | - Improved solver options 117 | - Friendly interactive session 118 | - Arbitrary parameter changes update model automatically 119 | - Parameters can be shared among many models 120 | 121 | The underlying solver is still GLPK, but now it is 122 | made available to PyMathProg by swiglpk, which has enabled: 123 | 124 | - Super easy setup of PyMathProg with one single command 125 | - Support of both Python 2 and 3 126 | - Support of the newest version of GLPK (v4.60) 127 | 128 | Therefore, this is indeed an exciting new version of **PyMathProg**! 129 | 130 | .. _compatability: 131 | 132 | A word on compatability 133 | ========================= 134 | 135 | This new version (*v1.0*) of PyMathProg is *fully compatable* 136 | with previous versions. However, some functions are deprecated and 137 | won't be fully supported in futre versions. Here is a list of them: 138 | 139 | - var(.) now takes the name of variable(s) as the first argument 140 | - par(.) now takes the name of parameter(s) as the first argument 141 | - beginModel(.) is simplified into begin(.) 142 | - endModel(.) is simplified into end(.) 143 | 144 | -------------------------------------------------------------------------------- /html/_sources/misc.rst.txt: -------------------------------------------------------------------------------- 1 | Miscellaneous functions 2 | ======================== 3 | 4 | There are some msscellaneous functions provided in PyMathProg 5 | for less common tasks. Here we will talk about: 6 | 7 | #. :ref:`delete` 8 | #. :ref:`save` 9 | #. :ref:`kkt` 10 | 11 | .. _delete: 12 | 13 | Deleting model elements 14 | ----------------------- 15 | 16 | Deleting variables and/or constraints from a model 17 | is done by invoking the *delete()* method on a 18 | variable or constraint. 19 | 20 | .. literalinclude:: ./deltest.py 21 | :linenos: 22 | 23 | The output is as follows: 24 | 25 | .. code-block:: none 26 | 27 | GLPK Simplex Optimizer, v4.60 28 | 3 rows, 3 columns, 9 non-zeros 29 | * 0: obj = -0.000000000e+00 inf = 0.000e+00 (3) 30 | * 2: obj = 2.560000000e+04 inf = 0.000e+00 (0) 31 | OPTIMAL LP SOLUTION FOUND 32 | 33 | PyMathProg 1.0 Sensitivity Report Created: 2016/12/11 Sun 09:05AM 34 | ================================================================================ 35 | Variable Activity Dual.Value Obj.Coef Range.From Range.Till 36 | -------------------------------------------------------------------------------- 37 | *x[0] 94 0 100 87.5 150 38 | *x[1] 54 0 300 200 366.667 39 | x[2] 0 -20 50 -inf 70 40 | ================================================================================ 41 | ================================================================================ 42 | Constraint Activity Dual.Value Lower.Bnd Upper.Bnd RangeLower RangeUpper 43 | -------------------------------------------------------------------------------- 44 | R1 93000 0.166667 -inf 93000 61200 121200 45 | R2 101 100 -inf 101 77.5 118.667 46 | *R3 148 0 -inf 201 148 148 47 | ================================================================================ 48 | GLPK Simplex Optimizer, v4.60 49 | 2 rows, 2 columns, 4 non-zeros 50 | * 3: obj = 2.020000000e+04 inf = 0.000e+00 (0) 51 | OPTIMAL LP SOLUTION FOUND 52 | 53 | PyMathProg 1.0 Sensitivity Report Created: 2016/12/11 Sun 09:05AM 54 | ================================================================================ 55 | Variable Activity Dual.Value Obj.Coef Range.From Range.Till 56 | -------------------------------------------------------------------------------- 57 | *x[0] 202 0 100 50 1.79769e+308 58 | x[2] 0 -50 50 -inf 100 59 | ================================================================================ 60 | ================================================================================ 61 | Constraint Activity Dual.Value Lower.Bnd Upper.Bnd RangeLower RangeUpper 62 | -------------------------------------------------------------------------------- 63 | *R1 60600 0 -inf 93000 60600 60600 64 | R2 101 200 -inf 101 0 155 65 | ================================================================================ 66 | 67 | 68 | .. _save: 69 | 70 | Saving model and solution 71 | ------------------------- 72 | 73 | It is possible to save the model and/or the solution to a text file. The example 74 | below shows how to do that through the global function *save(...)*. 75 | 76 | .. literalinclude:: ./saves.py 77 | :linenos: 78 | 79 | Note that the sensitivity report just saved is produced by GLPK. 80 | The format is not the same as the report produced by the global 81 | function *sensitivity()* in PyMathProg. 82 | 83 | .. _kkt: 84 | 85 | Karush-Kuhn-Tucker conditions 86 | ----------------------------- 87 | 88 | The KKT condition tells how much error are there 89 | in terms of satisfying the constraints. Errors 90 | may be measured both absolutely or relatively. 91 | To produce KKT conditions, just call the routine KKT() 92 | after solving a model. 93 | 94 | .. literalinclude:: ./gkkt.py 95 | :linenos: 96 | 97 | The produced output is as follows: 98 | 99 | .. code-block:: none 100 | 101 | Max : 10 * x[0] + 6 * x[1] + 4 * x[2] 102 | R1: x[0] + x[1] + x[2] <= 10 103 | R2: 9 * x[0] + 4 * x[1] + 5 * x[2] <= 60 104 | R3: 2 * x[0] + 2 * x[1] + 6 * x[2] <= 30 105 | GLPK Simplex Optimizer, v4.60 106 | 3 rows, 3 columns, 9 non-zeros 107 | * 0: obj = -0.000000000e+00 inf = 0.000e+00 (3) 108 | * 2: obj = 7.600000000e+01 inf = 0.000e+00 (0) 109 | OPTIMAL LP SOLUTION FOUND 110 | Karush-Kuhn-Tucker optimality conditions: 111 | ========================================= 112 | Solver used for this solution: simplex 113 | 114 | 1) Error for Primal Equality Constraints: 115 | ---------------------------------------- 116 | Largest absolute error: 0.000000 (row id: 0) 117 | Largest relative error: 0.000000 (row id: 0) 118 | 119 | 2) Error for Primal Inequality Constraints: 120 | ------------------------------------------- 121 | Largest absolute error: 0.000000 (row id: 0) 122 | Largest relative error: 0.000000 (row id: 0) 123 | 124 | 1) Error for Dual Equality Constraints: 125 | ---------------------------------------- 126 | Largest absolute error: 0.000000 (var id: 0) 127 | Largest relative error: 0.000000 (var id: 0) 128 | 129 | 2) Error for Dual Inequality Constraints: 130 | ------------------------------------------- 131 | Largest absolute error: 0.000000 (var id: 0) 132 | Largest relative error: 0.000000 (var id: 0) 133 | 134 | __del__ is deleting problem: basic 135 | 136 | -------------------------------------------------------------------------------- /html/_sources/pars.rst.txt: -------------------------------------------------------------------------------- 1 | 2 | Working with parameters 3 | ======================= 4 | 5 | Note that the sole purpose of parameters is to change its value 6 | later and see how it would impact the model and its solution. 7 | When the value of a parameter changes, the model will be updated 8 | automatically. 9 | If the value of a parameter never changes, it is better to 10 | get rid of it for the sake of efficiency. However, you may still 11 | want to use it for more meaningful representation of the model. 12 | 13 | Creating parameters 14 | ------------------- 15 | 16 | It is very simple to create parameters. 17 | Let's illustrate with an interactive Python session: 18 | 19 | >>> from pymprog import * 20 | >>> k = par('k', [2, 3, 4]) 21 | >>> type(k) 22 | 23 | >>> k[1] 24 | (k[1]:3) 25 | 26 | There is one important property of parameter creation: the original indexing 27 | of the raw values you passed in remains unchanged for the created parameters. 28 | Let's continue with this live illustration: 29 | 30 | >>> p = par('P', {'east':0, 'west':2, 'south':3, 'north':1}) 31 | >>> type(p) 32 | 33 | >>> p['east'] 34 | (P['east']:0) 35 | 36 | From these examples we see that the function *par(...)* can 37 | create parameters according to the index scheme of the value argument. 38 | It is nice to use *help(par)* to find out the following information:: 39 | 40 | Arguments: 41 | 42 | name(required): a str for the name of the parameter(s). 43 | 44 | val(default 0): may take the following types of values: 45 | 46 | 1. a single value in (int, long, float) 47 | -> a single parameter with the given name a value. 48 | 2. a list/tuple of values -> a list of parameters, 49 | with names indicating the position index. 50 | 3. a dict of values -> a dict of parameters, 51 | with names indicating the key index into the dict. 52 | 4. an iterable of values -> same as type 2. 53 | 54 | The cool thing about it is that it is recursive: 55 | 56 | >>> r = [{(3,4):3, (1,2):4}, 5] 57 | >>> R = par('R', r) 58 | >>> R 59 | [{(1, 2): (R[0][1,2]:4), (3, 4): (R[0][3,4]:3)}, (R[1]:5)] 60 | >>> r[0][3,4] 61 | 3 62 | >>> R[0][3,4] 63 | (R[0][3,4]:3) 64 | >>> r[1] 65 | 5 66 | >>> R[1] 67 | (R[1]:5) 68 | 69 | Folks, that's pretty much there is to it! 70 | 71 | 72 | Changing the value 73 | ------------------ 74 | 75 | From a user's point of view, changing the value of a parameter 76 | is very, very simple, you just do something like this: 77 | 78 | >>> p.value = new_value 79 | 80 | Since now you are already an insider to PyMathProg, 81 | we'd like to share with you the things done 82 | on the backstage for this value change to take effect 83 | throughout the entire model. When this value 84 | change happens, all related elements that depend 85 | on this value are informed of this change. These 86 | elements will then request the model for update. 87 | The model would simply queque up the requests 88 | until the last moment when the update is needed. 89 | This is done for the sake of performance, 90 | for updating too eagerly would endup updating 91 | one elements several times if several related 92 | parameters changes over time. Sometimes 93 | updating could be quite expensive. 94 | This approach is known as lazy update. 95 | 96 | There are two distinct kinds of situations 97 | where updates are needed: 98 | 99 | 1. the time before solving the model: 100 | all the elements in the model that 101 | needs update must be updated. 102 | 103 | 2. the time when a the value/property 104 | of an element is requested, such 105 | as in an interactive session when 106 | an object is represented to the user. 107 | In this case only the requested 108 | element is updated (if needed). 109 | 110 | An example for value change 111 | ---------------------------- 112 | 113 | Here is a small example to show the effect of value changes. 114 | Sensitivity report is also provided for you to evaluate the result. 115 | 116 | 117 | .. literalinclude:: ./sensana.py 118 | :linenos: 119 | 120 | If you run this code, the output would be something like this: 121 | 122 | .. code-block:: none 123 | 124 | GLPK Simplex Optimizer, v4.60 125 | 2 rows, 3 columns, 6 non-zeros 126 | * 0: obj = -0.000000000e+00 inf = 0.000e+00 (3) 127 | * 2: obj = 2.560000000e+04 inf = 0.000e+00 (0) 128 | OPTIMAL LP SOLUTION FOUND 129 | 130 | PyMathProg 1.0 Sensitivity Report Created: 2016/12/10 Sat 15:01PM 131 | ================================================================================ 132 | Variable Activity Dual.Value Obj.Coef Range.From Range.Till 133 | -------------------------------------------------------------------------------- 134 | *x[0] 94 0 100 87.5 150 135 | *x[1] 54 0 300 200 366.667 136 | x[2] 0 -20 50 -inf 70 137 | ================================================================================ 138 | ================================================================================ 139 | Constraint Activity Dual.Value Lower.Bnd Upper.Bnd RangeLower RangeUpper 140 | -------------------------------------------------------------------------------- 141 | R1 93000 0.166667 -inf 93000 60600 121200 142 | R2 101 100 -inf 101 77.5 155 143 | ================================================================================ 144 | GLPK Simplex Optimizer, v4.60 145 | 2 rows, 3 columns, 6 non-zeros 146 | * 2: obj = 2.570000000e+04 inf = 0.000e+00 (0) 147 | OPTIMAL LP SOLUTION FOUND 148 | 149 | -------------------------------------------------------------------------------- /html/_sources/solvopt.rst.txt: -------------------------------------------------------------------------------- 1 | 2 | Using Solver Options 3 | ==================== 4 | 5 | Currently, there are four solvers available for use, 6 | and each solver comes with a set of options for 7 | the parameters that control its behavior. 8 | Use the global function *solver(...)* 9 | to select the solver for your model, 10 | and to set the desired options for the 11 | chosen solver. 12 | 13 | .. _setgetopt: 14 | 15 | Setting/getting options 16 | ---------------------------- 17 | 18 | The four solvers available for use are 19 | each given a name: 'simplex', 'exact', 'interior', 20 | and 'intopt'. Note that the first three 21 | are good for solving linear programs, whereas the 22 | last ('intopt') is good for solving integer programs. 23 | That means we only have choices (at least for now) 24 | when we have a linear program to solve. 25 | To select a particular solver, just do: 26 | 27 | >>> solver('interior') 28 | 29 | That would set 'interior' solver as the solver for linear programming. 30 | You may also provide options at the same time: 31 | 32 | >>> solver('interior', msg_lev=glpk.GLP_MSG_OFF) 33 | {'msg_lev': 0} 34 | 35 | which would turn off message output from the solver, 36 | and return the current values for options explicitly set. 37 | For options whose value is not set, they would take the default value. 38 | For information on what options and values are available for a 39 | particular solver, try something like this: 40 | 41 | >>> solver(help = 'interior') 42 | 43 | To find out which solver will be used for linear programming: 44 | 45 | >>> solver(float) 46 | 'simplex' 47 | 48 | To find out which solver will be used for integer programming: 49 | 50 | >>> solver(int) 51 | 'intopt' 52 | 53 | To set options on one of the default solvers, for example, use 54 | 55 | >>> solver(int, br_tech=glpk.GLP_BR_PCH) 56 | 57 | to select the hybrid pseudo-cost heuristic(PCH) 58 | branching technique for the integer optimizer. 59 | 60 | 61 | 62 | .. _delopts: 63 | 64 | Deleting an option 65 | ------------------- 66 | 67 | To delete an option, simply set it to *None*. 68 | 69 | >>> solver(int, msg_lev=None) 70 | 71 | This would delete the explicit value for the 'msg_lev' option, 72 | and leave it at its default value. 73 | 74 | Note: if you need to warm start your simplex method 75 | (that is, start the simplex method with the optimal 76 | basis from your last invocation, which is often used 77 | when employing row generation and/or column generation), 78 | please don't turn on the 'presolve' option. 79 | 80 | -------------------------------------------------------------------------------- /html/_sources/subtour.rst.txt: -------------------------------------------------------------------------------- 1 | .. A simple tutorial by Yingjie Lan, May 2009. 2 | 3 | Case: Subtour Elimination 4 | ========================= 5 | 6 | This example is adapted from the presentation 7 | titled *Mathematical Programming: Modeling and 8 | Applications* by Giacomo Nannicini. Thanks are 9 | hereby extended to him. 10 | 11 | #. :ref:`intro_tsp` 12 | #. :ref:`subtelim` 13 | #. :ref:`implement` 14 | 15 | .. _intro_tsp: 16 | 17 | Brief Introduction 18 | ------------------- 19 | 20 | The Traveling Salesman Problem (TSP) is 21 | a very well known problem in the literature. 22 | Applications of TSP include: logistics, 23 | crane control, placing circuits on a board 24 | minimizing the required time, and many more. 25 | Unfortunately, it is a very difficult problem. 26 | For not too large instances, it can be done 27 | on a desktop machine. 28 | 29 | 30 | Here is a definition of a TSP problem: 31 | A salesman must visit all cities to see his customers, 32 | and return to the starting point. 33 | He wants to minimize the total travel distance. 34 | Here are are going to play with a small example 35 | of TSP, assuming that the distance between any 36 | two cities is symmetric. 37 | 38 | .. _subtelim: 39 | 40 | Subtour Elimination 41 | ------------------- 42 | 43 | A subtour is also a round tour that returns back 44 | to where you start, but does not visit all the cities. 45 | A formulation of TSP is this: 46 | 47 | #. enter each city exactly once. 48 | 49 | #. leave each city excatly once. 50 | 51 | #. make sure there is no subtour. 52 | 53 | To make sure there is no subtour, we must consider 54 | *all* subset of cities, and make sure that there 55 | is an arc leaving a city in the subset and entering 56 | a city NOT in the subset. So there are exponential 57 | number of subtour elimination constraints. 58 | Obviously, only a small number of them will be 59 | actually needed to eliminate subtours. 60 | The idea is to start out without them and then 61 | add those violated ones gradually, 62 | until the solution contains no subtour. 63 | For a more detailed discussion on TSP, please see 64 | http://www.tsp.gatech.edu/methods/opt/subtour.htm 65 | 66 | .. _implement: 67 | 68 | Implementation 69 | -------------- 70 | 71 | This is how I have it implemented using PyMathProg: 72 | 73 | .. literalinclude:: ./elimsubt.py 74 | :linenos: 75 | 76 | And here is the output:: 77 | 78 | there are 7 cities 79 | New subtour: [0, 4, 2] 80 | New subtour: [0, 6, 3, 2, 4] 81 | Optimal tour length: 153.0 82 | Optimal tour: 83 | [0, 5, 1, 6, 3, 2, 4] 84 | 85 | -------------------------------------------------------------------------------- /html/_sources/vars.rst.txt: -------------------------------------------------------------------------------- 1 | Working with variables 2 | ====================== 3 | 4 | Once the model is created, you need variables 5 | to make the objective and the constraints. 6 | In this section we talk about 7 | how to create and work with variables. 8 | 9 | #. :ref:`create` 10 | #. :ref:`bounds` 11 | 12 | .. _create: 13 | 14 | Creating variables 15 | -------------------- 16 | 17 | The routine *var(...)* is the only tool to create variables. 18 | Yet there are quite a few different ways to do so, depending 19 | on the modelling situation. For the sake of communication, 20 | a variable name must be provided when you create them. 21 | You can create a single variable, or quite a few, 22 | or even a huge group of variables at once. 23 | These scenarios are illustrated below: 24 | 25 | >>> from pymprog import * 26 | >>> begin('test') 27 | model('test') is the default model. 28 | >>> X = var('X') 29 | >>> X # by default, it is non-negative and continuous 30 | X >= 0 continuous 31 | >>> x, y = var('x, y') # many names -> many vars 32 | >>> x, y 33 | (x >= 0 continuous, y >= 0 continuous) 34 | >>> z = var('z', 3) 35 | >>> z # an array of 3 variables 36 | [z[0] >= 0 continuous, z[1] >= 0 continuous, z[2] >= 0 continuous] 37 | >>> z[2] # access the third variable 38 | z[2] >= 0 continuous 39 | >>> v = var('v', kind=bool) # 0/1 variable 40 | >>> v 41 | 0 <= v <= 1 binary 42 | >>> w = var('w', bounds=(0,5)) # specify the bounds 43 | >>> w 44 | 0 <= w <= 5 continuous 45 | >>> colors = ('red', 'green', 'blue') # index set 46 | >>> clr = var('color', colors, bool) # using an index set 47 | >>> clr # a dictionary with keys from the index set 48 | {'blue': 0 <= color['blue'] <= 1 binary, 'green': 0 <= color['green'] <= 1 binary, 49 | 'red': 0 <= color['red'] <= 1 binary} 50 | >>> clr['green'] 51 | 0 <= color['green'] <= 1 binary 52 | 53 | That interactive session demostrates different ways to use 54 | the function *var(...)* to create variables. Of course 55 | you may combine those ways to get things done efficiently. 56 | Basically, there are three conventions for variable creation: 57 | 58 | 1. provide all the names literally, in a single string using 59 | commas to separate them, to manually create 60 | a few variables, usally for small models. 61 | 2. provide one single name, and a positive integer, to 62 | create an array of variables indexed by integers. 63 | 3. provide one single name, and a set of indices, to 64 | create a dictionary with keys from the index set. 65 | 66 | Once you decide to follow one convention, then you may 67 | further customize the variables by furnishing values 68 | for the other arguments to the function call: 69 | 70 | - kind: specify what kind of variable to make. 71 | admissable values are: 72 | 73 | 1. float (default): continuous 74 | 2. int: integer 75 | 3. bool: binary, side-effect: reset bounds to (0,1) 76 | 77 | - bounds: a pair of numbers, for the lower and upper bounds. 78 | If None is used, it means unbounded. The default value 79 | is (0, None), so the lower bound is 0, upper bound is none. 80 | 81 | Note, you may also obtain help within the Python session by: 82 | 83 | >>> help(var) # obtain help on this function 84 | 85 | .. _bounds: 86 | 87 | Change bounds and kind 88 | ---------------------- 89 | 90 | Once you have created variables, you may further explicitly set 91 | bounds on some variables, it is qutie straight forward: 92 | 93 | >>> begin('test') 94 | model('test') is the default model. 95 | >>> x = var('x') 96 | >>> x <= 5 97 | 0 <= x <= 5 continuous 98 | >>> x >= 2 99 | 2 <= x <= 5 continuous 100 | >>> x == 3 101 | (x==3) continuous 102 | >>> x == 4 103 | 4 <= x <= 3 continuous 104 | >>> z = var('z', 3) 105 | >>> b = [5, 8, 3] 106 | >>> for i in range(3): z[i] <= b[i] 107 | ... 108 | 0 <= z[0] <= 5 continuous 109 | 0 <= z[1] <= 8 continuous 110 | 0 <= z[2] <= 3 continuous 111 | 112 | The most important thing to remember is this: bounds added 113 | by using '<=', '>=', and '==' are accumulative. Later bounds 114 | won't invalidate former bounds. That is why when we set 115 | *x == 4* after *x == 3* we got infeasibility: *4 <= x <= 3*. 116 | It is possble to cancle all previous bounds and start anew. 117 | Continue from where we have left off in the last Python session: 118 | 119 | >>> x.reset(1, 5) 120 | 1 <= x <= 5 continuous 121 | >>> x <= 10 122 | 1 <= x <= 5 continuous 123 | >>> x.reset() 124 | x >= 0 continuous 125 | >>> x <= 10 126 | 0 <= x <= 10 continuous 127 | 128 | This new interactive session below shows the interaction between 129 | kinds and bounds. The key concept behind all this is that a binary 130 | variable is *defined* as an integer variable between 0 and 1. 131 | 132 | >>> begin('test') 133 | model('test') is the default model. 134 | >>> x = var('x') 135 | >>> x.kind = int 136 | >>> x 137 | x >= 0 integer 138 | >>> x <= 1 139 | 0 <= x <= 1 binary 140 | >>> x.reset(0, 5) 141 | 0 <= x <= 5 integer 142 | >>> x.kind = bool 143 | >>> x 144 | 0 <= x <= 1 binary 145 | >>> x.kind = int 146 | >>> x 147 | 0 <= x <= 1 binary 148 | >>> x.kind = float 149 | >>> x 150 | 0 <= x <= 1 continuous 151 | 152 | We may also use parameters for bounds, in such case, when the 153 | parameters change value, the bounds get updated automatically. 154 | 155 | >>> p = par('p', 3) 156 | >>> p 157 | (p:3) 158 | >>> begin('test') 159 | model('test') is the default model. 160 | >>> x = var('x') 161 | >>> x <= p 162 | 0 <= x <= (p:3) continuous 163 | >>> p.value = 4 164 | >>> x 165 | 0 <= x <= (p:4) continuous 166 | >>> x.bounds 167 | (0, 4) 168 | 169 | The last line of code obtains the numerical value of bounds. 170 | We will discuss parameters in more details in a later section. 171 | -------------------------------------------------------------------------------- /html/_static/ajax-loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langit/pymprog/e8935c3fb032f0770368c022e20c94432e320cbc/html/_static/ajax-loader.gif -------------------------------------------------------------------------------- /html/_static/bgfooter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langit/pymprog/e8935c3fb032f0770368c022e20c94432e320cbc/html/_static/bgfooter.png -------------------------------------------------------------------------------- /html/_static/bgtop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langit/pymprog/e8935c3fb032f0770368c022e20c94432e320cbc/html/_static/bgtop.png -------------------------------------------------------------------------------- /html/_static/comment-bright.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langit/pymprog/e8935c3fb032f0770368c022e20c94432e320cbc/html/_static/comment-bright.png -------------------------------------------------------------------------------- /html/_static/comment-close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langit/pymprog/e8935c3fb032f0770368c022e20c94432e320cbc/html/_static/comment-close.png -------------------------------------------------------------------------------- /html/_static/comment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langit/pymprog/e8935c3fb032f0770368c022e20c94432e320cbc/html/_static/comment.png -------------------------------------------------------------------------------- /html/_static/down-pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langit/pymprog/e8935c3fb032f0770368c022e20c94432e320cbc/html/_static/down-pressed.png -------------------------------------------------------------------------------- /html/_static/down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langit/pymprog/e8935c3fb032f0770368c022e20c94432e320cbc/html/_static/down.png -------------------------------------------------------------------------------- /html/_static/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langit/pymprog/e8935c3fb032f0770368c022e20c94432e320cbc/html/_static/file.png -------------------------------------------------------------------------------- /html/_static/minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langit/pymprog/e8935c3fb032f0770368c022e20c94432e320cbc/html/_static/minus.png -------------------------------------------------------------------------------- /html/_static/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langit/pymprog/e8935c3fb032f0770368c022e20c94432e320cbc/html/_static/plus.png -------------------------------------------------------------------------------- /html/_static/pygments.css: -------------------------------------------------------------------------------- 1 | .highlight .hll { background-color: #ffffcc } 2 | .highlight { background: #eeffcc; } 3 | .highlight .c { color: #408090; font-style: italic } /* Comment */ 4 | .highlight .err { border: 1px solid #FF0000 } /* Error */ 5 | .highlight .k { color: #007020; font-weight: bold } /* Keyword */ 6 | .highlight .o { color: #666666 } /* Operator */ 7 | .highlight .ch { color: #408090; font-style: italic } /* Comment.Hashbang */ 8 | .highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */ 9 | .highlight .cp { color: #007020 } /* Comment.Preproc */ 10 | .highlight .cpf { color: #408090; font-style: italic } /* Comment.PreprocFile */ 11 | .highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */ 12 | .highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */ 13 | .highlight .gd { color: #A00000 } /* Generic.Deleted */ 14 | .highlight .ge { font-style: italic } /* Generic.Emph */ 15 | .highlight .gr { color: #FF0000 } /* Generic.Error */ 16 | .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 17 | .highlight .gi { color: #00A000 } /* Generic.Inserted */ 18 | .highlight .go { color: #333333 } /* Generic.Output */ 19 | .highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ 20 | .highlight .gs { font-weight: bold } /* Generic.Strong */ 21 | .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 22 | .highlight .gt { color: #0044DD } /* Generic.Traceback */ 23 | .highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ 24 | .highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ 25 | .highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ 26 | .highlight .kp { color: #007020 } /* Keyword.Pseudo */ 27 | .highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ 28 | .highlight .kt { color: #902000 } /* Keyword.Type */ 29 | .highlight .m { color: #208050 } /* Literal.Number */ 30 | .highlight .s { color: #4070a0 } /* Literal.String */ 31 | .highlight .na { color: #4070a0 } /* Name.Attribute */ 32 | .highlight .nb { color: #007020 } /* Name.Builtin */ 33 | .highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */ 34 | .highlight .no { color: #60add5 } /* Name.Constant */ 35 | .highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ 36 | .highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */ 37 | .highlight .ne { color: #007020 } /* Name.Exception */ 38 | .highlight .nf { color: #06287e } /* Name.Function */ 39 | .highlight .nl { color: #002070; font-weight: bold } /* Name.Label */ 40 | .highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ 41 | .highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */ 42 | .highlight .nv { color: #bb60d5 } /* Name.Variable */ 43 | .highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ 44 | .highlight .w { color: #bbbbbb } /* Text.Whitespace */ 45 | .highlight .mb { color: #208050 } /* Literal.Number.Bin */ 46 | .highlight .mf { color: #208050 } /* Literal.Number.Float */ 47 | .highlight .mh { color: #208050 } /* Literal.Number.Hex */ 48 | .highlight .mi { color: #208050 } /* Literal.Number.Integer */ 49 | .highlight .mo { color: #208050 } /* Literal.Number.Oct */ 50 | .highlight .sb { color: #4070a0 } /* Literal.String.Backtick */ 51 | .highlight .sc { color: #4070a0 } /* Literal.String.Char */ 52 | .highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */ 53 | .highlight .s2 { color: #4070a0 } /* Literal.String.Double */ 54 | .highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ 55 | .highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */ 56 | .highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */ 57 | .highlight .sx { color: #c65d09 } /* Literal.String.Other */ 58 | .highlight .sr { color: #235388 } /* Literal.String.Regex */ 59 | .highlight .s1 { color: #4070a0 } /* Literal.String.Single */ 60 | .highlight .ss { color: #517918 } /* Literal.String.Symbol */ 61 | .highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ 62 | .highlight .vc { color: #bb60d5 } /* Name.Variable.Class */ 63 | .highlight .vg { color: #bb60d5 } /* Name.Variable.Global */ 64 | .highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ 65 | .highlight .il { color: #208050 } /* Literal.Number.Integer.Long */ -------------------------------------------------------------------------------- /html/_static/up-pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langit/pymprog/e8935c3fb032f0770368c022e20c94432e320cbc/html/_static/up-pressed.png -------------------------------------------------------------------------------- /html/_static/up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langit/pymprog/e8935c3fb032f0770368c022e20c94432e320cbc/html/_static/up.png -------------------------------------------------------------------------------- /html/genindex.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Index — PyMathProg 1.0 documentation 11 | 12 | 13 | 14 | 15 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 42 | 43 |
44 |
45 |
46 | 47 |
48 |
49 |
50 | 51 | 52 |

Index

53 | 54 |
55 | 56 |
57 | 58 | 59 |
60 |
61 |
62 |
63 | 90 |
91 |
92 |
93 | 94 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /html/objects.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langit/pymprog/e8935c3fb032f0770368c022e20c94432e320cbc/html/objects.inv -------------------------------------------------------------------------------- /html/search.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Search — PyMathProg 1.0 documentation 10 | 11 | 12 | 13 | 14 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 49 | 50 |
51 |
52 |
53 | 54 |
55 |
56 |
57 | 58 |

Search

59 |
60 | 61 |

62 | Please activate JavaScript to enable the search 63 | functionality. 64 |

65 |
66 |

67 | From here you can search these documents. Enter your search 68 | words into the box below and click "search". Note that the search 69 | function will automatically search for all of the words. Pages 70 | containing fewer words won't appear in the result list. 71 |

72 |
73 | 74 | 75 | 76 |
77 | 78 |
79 | 80 |
81 | 82 |
83 |
84 |
85 |
86 | 113 |
114 |
115 |
116 | 117 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /models/+capdist.py: -------------------------------------------------------------------------------- 1 | import csv 2 | dists = csv.reader(open('capdist.csv'), 'excel') 3 | dm = [[int(k) for k in t[2:33]] 4 | for t in dists if t[1]] 5 | 6 | n = len(dm) #how many cities 7 | V = range(n) 8 | E = [(i,j) for i in V for j in V if i!=j] 9 | 10 | print "there are %d cities"%n 11 | 12 | from pymprog import * 13 | beginModel('ChinaCapitalCitiesTSP') 14 | x = var('x', E, bool) 15 | minimize( sum(dm[i][j]*x[i,j] for i,j in E), 'TravelDist' ) 16 | st([sum( x[k,j] for j in V if j!=k ) == 1 for k in V], 'leave') 17 | st([sum( x[i,k] for i in V if i!=k ) == 1 for k in V], 'enter') 18 | 19 | y = var('y', E) 20 | st([(n-1)*x[t] >= y[t] for t in E], 'cap') 21 | st([sum(y[i,k] for i in V if i!=k) + (0 if k else n) 22 | ==sum(y[k,j] for j in V if j!=k) + 1 for k in V], 'sale') 23 | 24 | solve(float) 25 | print "simplex done:", status() 26 | solvopt(integer='advanced') 27 | solveMIP() #solve the IP problem 28 | 29 | print "Optimal tour length:", vobj() 30 | tour = [t for t in E if x[t].primal>.5] 31 | cat = 0 32 | print("This is the optimal tour:") 33 | for k in V: 34 | print cat+1, '->', 35 | for i,j in tour: 36 | if i==cat: cat=j; break 37 | print cat+1 38 | -------------------------------------------------------------------------------- /models/+caplp.py: -------------------------------------------------------------------------------- 1 | import csv 2 | dists = csv.reader(open('capdist.csv'), 'excel') 3 | dm = [[int(k) for k in t[2:33]] 4 | for t in dists if t[1]] 5 | 6 | n = len(dm) #how many cities 7 | V = range(n) 8 | E = [(i,j) for i in V for j in V if i!=j] 9 | 10 | print "there are %d cities"%n 11 | 12 | from pymprog import * 13 | beginModel('ChinaCapitalCitiesTSP') 14 | x = var('x', E, bool) 15 | minimize( sum(dm[i][j]*x[i,j] for i,j in E), 'TravelDist' ) 16 | st([sum( x[k,j] for j in V if j!=k ) == 1 for k in V], 'leave') 17 | st([sum( x[i,k] for i in V if i!=k ) == 1 for k in V], 'enter') 18 | 19 | y = var('y', E, bool) #the ordering variables 20 | st([y[i,j] + y[j,i] == 1 for i in V for j in V if i>j], 21 | 'complete') 22 | st([x[i,j] + y[j,i] <= y[j,k] + y[k,i] 23 | for i in V for j in V for k in V if i!=j!=k!=i]) 24 | 25 | st([y[0,j] == 1 for j in V if j>0], 'insym') 26 | 27 | from datetime import datetime 28 | print datetime.now() 29 | solve(float) 30 | print datetime.now() 31 | print "simplex done:", status() 32 | print datetime.now() 33 | solve(float) 34 | print datetime.now() 35 | print "simplex done:", status() 36 | 37 | solvopt(integer='advanced') 38 | solveMIP() #solve the IP problem 39 | 40 | print "Optimal tour length:", vobj() 41 | tour = [t for t in E if x[t].primal>.5] 42 | cat = 0 43 | print("This is the optimal tour:") 44 | for k in V: 45 | print cat+1, '->', 46 | for i,j in tour: 47 | if i==cat: cat=j; break 48 | print cat+1 49 | -------------------------------------------------------------------------------- /models/+cvt_capdist.py: -------------------------------------------------------------------------------- 1 | import csv 2 | dists = csv.reader(open('capdist.csv'), 'excel') 3 | dm = [[int(k) for k in t[2:33]] 4 | for t in dists if t[1]] 5 | 6 | n = len(dm) #how many cities 7 | 8 | max_dist = max(max(t) for t in dm) 9 | 10 | print(n) 11 | for row in dm: 12 | print(' '.join("%r"%(max_dist - x) 13 | for x in row)) 14 | -------------------------------------------------------------------------------- /models/+dynaqueens.py: -------------------------------------------------------------------------------- 1 | import pymprog 2 | 3 | # The Queens Problem is to place as many queens as possible on the nxn 4 | # chess board in a way that they do not fight 5 | # each other. This problem is probably as old as the chess game itself, 6 | # and thus its origin is not known, but it is known that Gauss studied 7 | # this problem. 8 | 9 | def queens(n): # n: size of the chess board 10 | p = pymprog.model('queens') 11 | iboard = pymprog.iprod(range(n), range(n)) #create indices 12 | x = p.var('X', iboard, bool) #create variables 13 | #row wise: 14 | p.st([sum(x[i,j] for j in range(n)) <= 1 for i in range(n)]) 15 | #column wise: 16 | p.st([sum(x[i,j] for i in range(n)) <= 1 for j in range(n)]) 17 | #diagion '\' wise 18 | p.st([sum(x[i,j] for i,j in iboard if i-j == k) <= 1 19 | for k in range(2-n, n-1)]) 20 | #diagion '/' wise 21 | p.st([sum(x[i,j] for i,j in iboard if i+j == k) <= 1 22 | for k in range(1, n+n-2)]) 23 | p.max(sum(x[t] for t in iboard), 'queens') 24 | return p,x 25 | 26 | n = raw_input("board size = ") 27 | n = int(n) 28 | p,x = queens(n) 29 | ys = raw_input("Would you like to place a queen? [y]/n") 30 | while ys!='n': 31 | r = raw_input("row [0, %i): "%n) 32 | c = raw_input("col [0, %i): "%n) 33 | p.st(x[int(r), int(c)] == 1) 34 | ys = raw_input("Would you like to place another queen? [y]/n") 35 | 36 | p.solve() 37 | #print "solver status: ", p.p.status 38 | for i in range(n): 39 | for j in range(n): 40 | if x[i,j].primal > 0.5: print 'Q', 41 | else: print '.', 42 | print 43 | -------------------------------------------------------------------------------- /models/+lotsp.py: -------------------------------------------------------------------------------- 1 | import csv 2 | #dm = ( 3 | #( 0,86,49,57,31,69,50), 4 | #(86, 0,68,79,93,24, 5), 5 | #(49,68, 0,16, 7,72,67), 6 | #(57,79,16, 0,90,69, 1), 7 | #(31,93, 7,90, 0,86,59), 8 | #(69,24,72,69,86, 0,81), 9 | #(50, 5,67, 1,59,81, 0)) 10 | 11 | dists = csv.reader(open('capdist.csv'), 'excel') 12 | dm = [[int(k) for k in t[2:33]] 13 | for t in dists if t[1]] 14 | 15 | n = len(dm) #how many cities 16 | 17 | max_dist = max(max(t) for t in dm) + 1 18 | 19 | dm = [[max_dist-x for x in row] 20 | for row in dm] 21 | 22 | n = len(dm) #how many cities 23 | V = range(n) 24 | E = [(i,j) for i in V for j in V if i!=j] 25 | 26 | print "there are %d cities"%n 27 | 28 | from pymprog import * 29 | beginModel('ChinaCapitalCitiesTSP') 30 | x = var('x', E, bool) 31 | v = var('v', E, bool) 32 | 33 | maximize( sum(dm[i][j]*x[i,j] for i,j in E), 'AntiDist' ) 34 | st([sum( x[k,j] for j in V if j!=k ) == 1 for k in V], 'leave') 35 | st([sum( x[i,k] for i in V if i!=k ) == 1 for k in V], 'enter') 36 | st(v[i,j] + v[j,i] == 1 for i,j in E if i.5] 49 | cat = 0 50 | print("This is the optimal tour:") 51 | for k in V: 52 | print cat+1, '->', 53 | for i,j in tour: 54 | if i==cat: cat=j; break 55 | print cat+1 56 | -------------------------------------------------------------------------------- /models/+picomagic.py: -------------------------------------------------------------------------------- 1 | # MAGIC, Magic Square 2 | 3 | 4 | """In recreational mathematics, a magic square of order n is an 5 | arrangement of n^2 numbers, usually distinct integers, in a square, 6 | such that n numbers in all rows, all columns, and both diagonals sum 7 | to the same constant. A normal magic square contains the integers 8 | from 1 to n^2. 9 | 10 | (From Wikipedia, the free encyclopedia.) 11 | 12 | When n=5, we have: 13 | 14 | the magic sum must be: 65 15 | solver status: opt 16 | 10 2 4 24 25 17 | 9 11 20 6 19 18 | 22 14 16 8 5 19 | 23 17 7 15 3 20 | 1 21 18 12 13""" 21 | 22 | 23 | # square order 24 | rt= 3 #square root of n 25 | n = rt*rt 26 | # set of numbers 27 | N = range(1,n*n+1) 28 | #the magic sum: 29 | s = sum(N)//n 30 | print("the magic sum must be: %r"% s) 31 | 32 | import pymprog 33 | p = pymprog.model('magic') 34 | S = pymprog.iprod(range(1,n+1), range(1,n+1)) 35 | rtS=pymprog.iprod(range(1,rt+1), range(1,rt+1)) 36 | rtB=pymprog.iprod(range(0,n,rt), range(0,n,rt)) 37 | T = pymprog.iprod(range(1,n+1), range(1,n+1), N) 38 | x = p.var('x', T, bool) 39 | #x[i,j,k] = 1 means that cell (i,j) contains integer k 40 | 41 | #each cell must be assigned exactly one integer 42 | p.st([sum(x[i,j,k] for k in N)==1 for i,j in S]) 43 | 44 | #each integer must be assigned exactly to one cell 45 | p.st([sum(x[i,j,k] for i,j in S)==1 for k in N]) 46 | 47 | #the sum in each row must be the magic sum 48 | p.st([sum(k*x[i,j,k] for j in range(1,n+1) for k in N)==s 49 | for i in range(1, n+1)], 'row') 50 | 51 | #the sum in each column must be the magic sum 52 | p.st([sum(k*x[i,j,k] for i in range(1,n+1) for k in N)==s 53 | for j in range(1, n+1)], 'col') 54 | 55 | #the sum in the diagonal must be the magic sum 56 | p.st([sum(k*x[i,(i+j-2)%n+1,k] for i in range(1,n+1) for k in N)==s 57 | for j in range(1,n+1)], 'dia') 58 | 59 | #the sum in the co-diagonal must be the magic sum 60 | p.st([sum(k*x[i,(n-1-i+j)%n+1,k] for i in range(1,n+1) for k in N)==s 61 | for j in range(1,n+1)], 'cod') 62 | 63 | #the sum in each sub-square must be the magic sum 64 | p.st([sum(k*x[i+bi, j+bj, k] for i,j in rtS for k in N)==s 65 | for bi, bj in rtB], 'sub') 66 | 67 | #the sum of the elements occupying the same location 68 | #in the subsquares must also be the same. picomagic! 69 | p.st([sum(k*x[i+bi, j+bj, k] for i,j in rtB for k in N)==s 70 | for bi, bj in rtS], 'sub') 71 | 72 | #in each sub-square, only one number btw [i*n+1,(i+1)*n] 73 | #note: this additional constriant is to reduce complexity 74 | p.st([sum(x[i+bi, j+bj, k] for i,j in rtS 75 | for k in range(h*n+1, (h+1)*n+1))==1 76 | for h in range(n) for bi, bj in rtB], 'red') 77 | 78 | #in each col/row, only one number btw [i*n+1,(i+1)*n] 79 | #note: this additional constriant is to reduce complexity 80 | p.st([sum(x[r,c,k] for r in range(1,n+1) 81 | for k in range(h*n+1, (h+1)*n+1))==1 82 | for h in range(n) for c in range(1,n+1)], 'row1') 83 | 84 | p.st([sum(x[r,c,k] for c in range(1,n+1) 85 | for k in range(h*n+1, (h+1)*n+1))==1 86 | for h in range(n) for r in range(1,n+1)], 'col1') 87 | 88 | p.solver('intopt', 89 | #this branching option usually helps a lot 90 | br_tech=pymprog.glpk.GLP_BR_PCH, #branching technique 91 | ) 92 | p.solve() 93 | print("solver status: %s"% status()) 94 | for i in range(1,n+1): 95 | print(' '.join("%2g"%sum(x[i,j,k].primal*k for k in N) 96 | for j in range(1,n+1))) 97 | p.end() 98 | -------------------------------------------------------------------------------- /models/+piconstruct.py: -------------------------------------------------------------------------------- 1 | mat=( 2 | (2 , 2 , 7 , 2 , 7 , 9 , 8 , 3 , 5), 3 | (9 , 8 , 3 , 8 , 2 , 1 , 2 , 9 , 3), 4 | (1 , 8 , 5 , 7 , 3 , 6 , 8 , 6 , 1), 5 | (9 , 8 , 1 , 3 , 4 , 3 , 7 , 6 , 4), 6 | (1 , 5 , 5 , 8 , 4 , 9 , 5 , 5 , 3), 7 | (1 , 9 , 6 , 4 , 6 , 4 , 3 , 3 , 9), 8 | (8 , 2 , 4 , 1 , 9 , 5 , 5 , 4 , 7), 9 | (7 , 1 , 9 , 4 , 4 , 6 , 1 , 7 , 6), 10 | (7 , 2 , 5 , 8 , 6 , 2 , 6 , 2 , 7)) 11 | 12 | 13 | # square order 14 | rt= 3 #square root of n 15 | n = rt*rt 16 | # set of numbers 17 | N = range(1,n+1) 18 | # the magic sum 19 | ms = sum(N) 20 | from pymprog import * 21 | begin('magic') 22 | S = iprod(range(1,n+1), range(1,n+1)) 23 | rtS=iprod(range(1,rt+1), range(1,rt+1)) 24 | rtB=iprod(range(0,n,rt), range(0,n,rt)) 25 | T = iprod(range(1,n+1), range(1,n+1), N) 26 | x = var('x', T, bool) 27 | #x[i,j,k] = 1 means that cell (i,j) contains integer k 28 | 29 | grp = [set() for i in range(1,10)] 30 | for i in range(9): 31 | for j in range(9): 32 | grp[mat[i][j]-1].add((i+1,j+1)) 33 | 34 | #each integer must be assigned to a group once; 35 | #note that each integer is assigned n times. 36 | st([sum(x[i,j,k] for i,j in grp[p-1])>=1 37 | for p in N for k in N]) 38 | 39 | #each cell must be assigned exactly one integer 40 | st([sum(x[i,j,k] for k in N)<=1 for i,j in S]) 41 | 42 | #the sum in each row must be the magic sum 43 | st([sum(k*x[i,j,k] for j in N for k in N)==ms 44 | for i in N], 'row') 45 | 46 | #the sum in each column must be the magic sum 47 | st([sum(k*x[i,j,k] for i in N for k in N)==ms 48 | for j in N], 'col') 49 | 50 | #the sum in the diagonal must be the magic sum 51 | st([sum(k*x[i,(i+j-2)%n+1,k] for i in N for k in N)==ms 52 | for j in N], 'dia') 53 | 54 | #the sum in the co-diagonal must be the magic sum 55 | st([sum(k*x[i,(n-1-i+j)%n+1,k] for i in N for k in N)==ms 56 | for j in N], 'cod') 57 | 58 | #the sum in each sub-square must be the magic sum 59 | st([sum(k*x[i+bi, j+bj, k] for i,j in rtS for k in N)==ms 60 | for bi, bj in rtB], 'sub') 61 | 62 | #the sum of the elements occupying the same location 63 | #in the subsquares must also be the same. picomagic! 64 | st([sum(k*x[i+bi, j+bj, k] for i,j in rtB for k in N)==ms 65 | for bi, bj in rtS], 'sub') 66 | 67 | solve() 68 | print("solver status: %s"% status()) 69 | for i in range(1,n+1): 70 | print(' '.join("%2g"%sum(x[i,j,k].primal*k for k in N) 71 | for j in range(1,n+1))) 72 | end() 73 | -------------------------------------------------------------------------------- /models/+picorotate.py: -------------------------------------------------------------------------------- 1 | mat=( 2 | (2 , 2 , 7 , 2 , 7 , 9 , 8 , 3 , 5), 3 | (9 , 8 , 3 , 8 , 2 , 1 , 2 , 9 , 3), 4 | (1 , 8 , 5 , 7 , 3 , 6 , 8 , 6 , 1), 5 | (9 , 8 , 1 , 3 , 4 , 3 , 7 , 6 , 4), 6 | (1 , 5 , 5 , 8 , 4 , 9 , 5 , 5 , 3), 7 | (1 , 9 , 6 , 4 , 6 , 4 , 3 , 3 , 9), 8 | (8 , 2 , 4 , 1 , 9 , 5 , 5 , 4 , 7), 9 | (7 , 1 , 9 , 4 , 4 , 6 , 1 , 7 , 6), 10 | (7 , 2 , 5 , 8 , 6 , 2 , 6 , 2 , 7)) 11 | 12 | def test(n,m): 13 | "see if n and m are right" 14 | grp = [set() for i in n] 15 | for r1, r2 in zip(n,m): 16 | for i,j in zip(r1,r2): 17 | if j in grp[i-1]: return False 18 | grp[i-1].add(j) 19 | 20 | def flip(n): return zip(*n) 21 | 22 | def rotate(n): return [reversed(r) for r in zip(*n)] 23 | 24 | def rotout(n): 25 | if test(n,mat): return True 26 | for i in range(3): 27 | n=rotate(n) 28 | if test(n,mat): return True 29 | return False 30 | 31 | def checkout(n): 32 | if rotout(n): return True 33 | n=flip(n) 34 | return rotout(n) 35 | 36 | 37 | print(checkout(mat)) 38 | -------------------------------------------------------------------------------- /models/+picosudoku.py: -------------------------------------------------------------------------------- 1 | # SUDOKU, Number Placement Puzzle 2 | # Written in pymprog by Yingjie Lan 3 | from __future__ import print_function 4 | 5 | """Sudoku, also known as Number Place, is a logic-based placement 6 | puzzle. The aim of the canonical puzzle is to enter a numerical 7 | digit from 1 through 9 in each cell of a 9x9 grid made up of 3x3 8 | subgrids (called "regions"), starting with various digits given in 9 | some cells (the "givens"). Each row, column, and region must contain 10 | only one instance of each numeral. 11 | 12 | (From Wikipedia, the free encyclopedia.) 13 | 14 | This example will provide a sample PICO Sudoku: 15 | in addition to satisfying all the requirements 16 | of Sudoku, PICO Sudoku also requires that the 17 | elements in all wrapped diagonals must be distinct.""" 18 | 19 | from pymprog import * # Import the module 20 | begin("picosudoku") 21 | I = range(1,10) 22 | J = range(1,10) 23 | K = range(1,10) 24 | T = iprod(I,J,K) #create Indice tuples 25 | #x[i,j,k] = 1 means cell [i,j] is assigned number k 26 | x = var('x', T, bool) #binary vars 27 | #each cell must be assigned exactly one number 28 | st([sum(x[i,j,k] for k in K)==1 for i in I for j in J], 'cell') 29 | #cells in the same row must be assigned distinct numbers 30 | st([sum(x[i,j,k] for j in J)==1 for i in I for k in K], 'row') 31 | #cells in the same column must be assigned distinct numbers 32 | st([sum(x[i,j,k] for i in I)==1 for j in J for k in K], 'col') 33 | #cells in the same region must be assigned distinct numbers 34 | st([sum(x[i,j,k] for i in range(r,r+3) for j in range(c, c+3))==1 35 | for r in range(1,10,3) for c in range(1,10,3) for k in K],'reg') 36 | 37 | #cells in all wrapped \-diagonals sum up to const 38 | st([sum(k*x[i,(i+h)%9+1,k] for i in I for k in K)==sum(K) 39 | for h in range(-1,8)], 'diag') 40 | 41 | #cells in all wrapped /-diagonal sum up to const 42 | st([sum(k*x[i,(h-i)%9+1,k] for i in I for k in K)==sum(K) 43 | for h in range(9,18)], 'codi') 44 | 45 | """Note: for any given arrangement, if there is a 46 | function on 1-9 that fully maps back to 1-9, then 47 | the resultant arrangement after applying this function 48 | is still good in every aspect. Thus one can always 49 | fix the first row to 1, 2, 3, ..., 9.""" 50 | for j in J: 51 | for k in K: 52 | #protect assignment to this 53 | x[1, j, k] == (1 if j==k else 0) 54 | 55 | #there is no need for an objective function here 56 | solver('intopt', 57 | #this branching option usually helps a lot 58 | br_tech=glpk.GLP_BR_PCH, #branching technique 59 | ) 60 | 61 | solve() 62 | 63 | for i in I: 64 | if i in range(1,10,3): 65 | print(" +-------+-------+-------+") 66 | print('', end=' ') 67 | for j in J: 68 | if j in range(1,10,3): print("|", end=' ') 69 | print("%g"%sum(x[i,j,k].primal*k for k in K), end=' ') 70 | if j==9: print("|") 71 | if i == 9: 72 | print(" +-------+-------+-------+") 73 | 74 | 75 | end() 76 | -------------------------------------------------------------------------------- /models/+revman_jobs.py: -------------------------------------------------------------------------------- 1 | n = 40 2 | N = range(n) 3 | M = [(i,j) for i in N for j in N if i overlap betw jobs i,j 33 | y = var("y", M ) 34 | #w[i,j]: the 'OR' for |T[i]-x[j]-MD[i,j]| 35 | w = var("w", M, bool) 36 | # z[i,j] >= MD[i,j] - y[i,j] 37 | z = var("z", M) 38 | #u[i] = 1 iff job i is scheduled. 39 | u = var("u", N, bool) 40 | 41 | maximize(sum(R[i]*u[i] for i in N), 'revenue') 42 | 43 | st( 0 == sum(z[i,j] for i,j in M), "single" ) 44 | #w[i,j]=0 ==> 45 | #x[j] + D[j]/2 >= x[i]+D[i]/2 + y[i,j] 46 | #That is: i preceed j when w[i,j]=0. 47 | st([(D[i]+D[j])/2.0 - (x[i]+D[i]- x[j]) + 48 | (U[i]-L[j]) * w[i,j] >= y[i,j] 49 | for i,j in M], 'small') #T[i]-x[j]= y[i,j] 52 | for i,j in M], 'large') #T[i]-x[j]>MD[i,j] 53 | st([(D[i]+D[j])/2.0 - y[i,j] <= z[i,j] + 54 | (2-u[i]-u[j])*(D[i]+D[j])/2.0 55 | for i,j in M], 'overlap') 56 | 57 | st(w[i,j] == (0 if (i,j) in P else 1) 58 | for (i,j) in M if ((i,j) in P or (j,i) in P)) 59 | 60 | 61 | #set bounds on x 62 | for i in N: 63 | L[i] <= x[i] <= U[i] - D[i] 64 | 65 | solver(int, 66 | #this branching option often helps 67 | br_tech=glpk.GLP_BR_PCH, 68 | ) 69 | solve() 70 | 71 | print "status:", status() 72 | print "revenue:", vobj(), '//', sum(R) 73 | print "schedule:" 74 | for i in N: 75 | start = x[i].primal 76 | used = u[i].primal 77 | print "job %i:"%i, start, start+D[i], 78 | print "Accept" if used else "Reject", 79 | print R[i]/float(D[i]) 80 | -------------------------------------------------------------------------------- /models/+sched_jobs.py: -------------------------------------------------------------------------------- 1 | n = 25 2 | N = range(n) 3 | M = [(i,j) for i in N for j in N if i= T[j] or x[j] >= T[i] 37 | # 38 | # Which can be formulated as: 39 | # 40 | st(x[i] + (U[j]-L[i])*w[i,j]>= x[j]+D[j] 41 | for i,j in M) 42 | st(x[j] + (U[i]-L[j])*(1-w[i,j]) >= x[i]+D[i] 43 | for i,j in M) 44 | st(w[i,j] == (1 if (i,j) in P else 0) 45 | for (i,j) in M if ((i,j) in P or (j,i) in P)) 46 | 47 | #set bounds on x 48 | for i in N: 49 | L[i] <= x[i] <= U[i] - D[i] 50 | 51 | solve() 52 | 53 | print("status:", status()) 54 | print("schedule:") 55 | for i in N: 56 | start = x[i].primal 57 | endat = evaluate(x[i]+D[i]) 58 | print("job %i: %r, %r"%(i, start, endat)) 59 | end() 60 | -------------------------------------------------------------------------------- /models/+sched_rev.py: -------------------------------------------------------------------------------- 1 | n = 80 2 | N = range(n) 3 | M = [(i,j) for i in N for j in N if i= T[j] or x[j] >= T[i] 28 | # 29 | 30 | st(x[i] + (U[j]-L[i])*(w[i,j]+2-u[i]-u[j]) 31 | >= x[j]+D[j] for i,j in M) 32 | st(x[j] + (U[i]-L[j])*(3-w[i,j]-u[i]-u[j]) 33 | >= x[i]+D[i] for i,j in M) 34 | 35 | maximize( sum(u[i]*R[i] for i in N), 'revenue') 36 | 37 | #set bounds on x 38 | for i in N: 39 | L[i] <= x[i] <= U[i] - D[i] 40 | 41 | solve() 42 | 43 | print("status: %s"% status()) 44 | print("revenue:", vobj(), '//', sum(R)) 45 | print("schedule:") 46 | for i in N: 47 | start = x[i].primal 48 | used = u[i].primal 49 | print("job %i:"%i, start, start+D[i], 50 | "Accept" if used else "Reject", R[i]/float(D[i])) 51 | -------------------------------------------------------------------------------- /models/MST.py: -------------------------------------------------------------------------------- 1 | '''minimal spanning tree.''' 2 | Vname = 'ABCDEFG' 3 | V = range(7) 4 | Ename = 'AB,AC,AD,BC,BE,CE,CF,CD,DF,EF,EG,FG'.split(',') 5 | W = (2,5,4,2,7,4,3,1,4,1,5,7) 6 | 7 | from pymprog import * 8 | 9 | E = [(Vname.index(e[0]),Vname.index(e[1])) for e in Ename] 10 | A = E + [(j,i) for i,j in E] 11 | We = dict(zip(E,W)) 12 | VA = iprod(V,A) 13 | 14 | beginModel('MST') 15 | x = var('x', E) 16 | y = var('y', VA) 17 | minimize( sum(We[e]*x[e] for e in E), 'TotalWeight' ) 18 | st([ y[v,(i,j)] <= x[min(i,j), max(i,j)] for v,(i,j) in VA ], 'edge') 19 | st( sum(y[k,(s,j)] for i,j in A if i==s) 20 | - sum(y[k,(i,s)] for i,j in A if j==s) == 21 | (1 if s==0 else -1 if s==k else 0) 22 | for s in V for k in V if k>0 ) 23 | 24 | solve(float) 25 | print("simplex done:", status()) 26 | print(sum(We[e]*x[e].primal for e in E)) 27 | for e in E: print(e, x[e].primal) 28 | -------------------------------------------------------------------------------- /models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langit/pymprog/e8935c3fb032f0770368c022e20c94432e320cbc/models/__init__.py -------------------------------------------------------------------------------- /models/assign.py: -------------------------------------------------------------------------------- 1 | # Assignment Problem 2 | # Written in pymprog by Yingjie Lan 3 | 4 | # The assignment problem is one of the fundamental combinatorial 5 | # optimization problems. 6 | 7 | # In its most general form, the problem is as follows: 8 | 9 | # There are a number of agents and a number of tasks. Any agent can be 10 | # assigned to perform any task, incurring some cost that may vary 11 | # depending on the agent-task assignment. It is required to perform all 12 | # tasks by assigning exactly one agent to each task in such a way that 13 | # the total cost of the assignment is minimized. 14 | # (From Wikipedia, the free encyclopedia.) 15 | 16 | m = 8 # agents 17 | M = range(m) #set of agents 18 | n = 8 # tasks 19 | N = range(n) #set of tasks 20 | c = [ #cost 21 | (13,21,20,12,8,26,22,11), 22 | (12,36,25,41,40,11,4,8), 23 | (35,32,13,36,26,21,13,37), 24 | (34,54,7,8,12,22,11,40), 25 | (21,6,45,18,24,34,12,48), 26 | (42,19,39,15,14,16,28,46), 27 | (16,34,38,3,34,40,22,24), 28 | (26,20,5,17,45,31,37,43)] 29 | from pymprog import * 30 | p = model("assign") 31 | p.verb = True 32 | A = iprod(M, N) 33 | x = p.var('x', A) #assignment decision vars 34 | tc = par('c', c) 35 | p.min(sum(tc[i][j]*x[i,j] for i,j in A), 'totalcost') 36 | p.st([sum(x[k,j] for j in N)<=1 for k in M], 'agent') 37 | p.st([sum(x[i,k] for i in M)==1 for k in N], 'task') 38 | 39 | p.solve() 40 | pcost = p.vobj() 41 | print("Total Cost = %r"%pcost) 42 | assign = [(i,j) for i in M for j in N 43 | if x[i,j].primal>0.5] 44 | for i,j in assign: 45 | print("Agent %d gets Task %d with Cost %r"%(i, j, tc[i][j])) 46 | 47 | i,j = assign[0] 48 | tc[i][j].value += 10 49 | print("set cost to higher value %r"%(tc[i][j])) 50 | p.solve() #this takes care of model update 51 | cost2 = p.vobj() 52 | assert cost2 > pcost 53 | print("Total Cost = %r"%cost2) 54 | assign = [(i,j) for i in M for j in N 55 | if x[i,j].primal>0.5] 56 | for i,j in assign: 57 | print("Agent %d gets Task %d with Cost %r"%(i, j, tc[i][j])) 58 | p.end() 59 | -------------------------------------------------------------------------------- /models/basic.py: -------------------------------------------------------------------------------- 1 | import pymprog # Import the module 2 | # index and data 3 | cid, rid = range(3), range(3) 4 | c = (10.0, 6.0, 4.0) 5 | mat = [ (1.0, 1.0, 1.0), 6 | (10.0, 4.0, 5.0), 7 | (2.0, 2.0, 6.0)] 8 | b = (100.0, 600.0, 300.0) 9 | # Create empty problem instance 10 | p = pymprog.model('basic example') 11 | #create variables: 12 | x = p.var('X', 3) # x: dict with keys in 'cid' 13 | p.maximize(sum(c[i]*x[i] for i in cid), 'myobj') 14 | p.verbose(True) 15 | r=p.st( 16 | 0 <= sum(x[j]*mat[i][j] for j in cid) <= b[i] 17 | for i in rid) 18 | #solve and report 19 | p.solve() 20 | print('Z = %g;' % p.vobj()) # obj value 21 | # Print struct variable names and primal values 22 | print(';\n'.join('%s = %g {dual: %g}' % ( 23 | x[i].name, x[i].primal, x[i].dual) 24 | for i in cid)) 25 | print(';\n'.join('%s = %g {dual: %g}' % ( 26 | r[i].name, r[i].primal, r[i].dual) 27 | for i in rid)) 28 | 29 | p.sensitivity() 30 | p.end() 31 | -------------------------------------------------------------------------------- /models/begins.py: -------------------------------------------------------------------------------- 1 | from pymprog import * 2 | 3 | m = begin('a') 4 | assert model._prob_ is m 5 | x = var('x') 6 | end() 7 | assert model._prob_ is None 8 | 9 | m = model('b') 10 | # can't use global functions 11 | try: 12 | x = var('x') 13 | except: pass 14 | else: 15 | raise Exception("Can't reach here!") 16 | 17 | try: end() 18 | except: pass 19 | else: 20 | raise Exception("Can't reach here!") 21 | -------------------------------------------------------------------------------- /models/capdist.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langit/pymprog/e8935c3fb032f0770368c022e20c94432e320cbc/models/capdist.csv -------------------------------------------------------------------------------- /models/comagic.py: -------------------------------------------------------------------------------- 1 | # MAGIC, Magic Square 2 | 3 | 4 | """In recreational mathematics, a magic square of order n is an 5 | arrangement of n^2 numbers, usually distinct integers, in a square, 6 | such that n numbers in all rows, all columns, and both diagonals sum 7 | to the same constant. A normal magic square contains the integers 8 | from 1 to n^2. 9 | 10 | (From Wikipedia, the free encyclopedia.) 11 | 12 | When n=5, we have: 13 | 14 | the magic sum must be: 65 15 | solver status: opt 16 | 10 2 4 24 25 17 | 9 11 20 6 19 18 | 22 14 16 8 5 19 | 23 17 7 15 3 20 | 1 21 18 12 13""" 21 | 22 | 23 | # square order 24 | rt= 2 #square root of n 25 | n = rt*rt 26 | # set of numbers 27 | N = range(1,n*n+1) 28 | #the magic sum: 29 | s = sum(N)//n 30 | print("the magic sum must be: %r" % s) 31 | 32 | import pymprog 33 | p = pymprog.model('magic') 34 | S = pymprog.iprod(range(1,n+1), range(1,n+1)) 35 | rtS=pymprog.iprod(range(1,rt+1), range(1,rt+1)) 36 | rtB=pymprog.iprod(range(0,n,rt), range(0,n,rt)) 37 | T = pymprog.iprod(range(1,n+1), range(1,n+1), N) 38 | x = p.var('x', T, bool) 39 | #x[i,j,k] = 1 means that cell (i,j) contains integer k 40 | 41 | #each cell must be assigned exactly one integer 42 | p.st([sum(x[i,j,k] for k in N)==1 for i,j in S]) 43 | 44 | #each integer must be assigned exactly to one cell 45 | p.st([sum(x[i,j,k] for i,j in S)==1 for k in N]) 46 | 47 | #the sum in each row must be the magic sum 48 | p.st([sum(k*x[i,j,k] for j in range(1,n+1) for k in N)==s 49 | for i in range(1, n+1)], 'row') 50 | 51 | #the sum in each column must be the magic sum 52 | p.st([sum(k*x[i,j,k] for i in range(1,n+1) for k in N)==s 53 | for j in range(1, n+1)], 'col') 54 | 55 | #the sum in the diagonal must be the magic sum 56 | p.st([sum(k*x[i,(i+j-2)%n+1,k] for i in range(1,n+1) for k in N)==s 57 | for j in range(1,n+1)], 'dia') 58 | 59 | #the sum in the co-diagonal must be the magic sum 60 | p.st([sum(k*x[i,(n-1-i+j)%n+1,k] for i in range(1,n+1) for k in N)==s 61 | for j in range(1,n+1)], 'cod') 62 | 63 | #the sum in each sub-square must be the magic sum 64 | p.st([sum(k*x[i+bi, j+bj, k] for i,j in rtS for k in N)==s 65 | for bi, bj in rtB], 'sub') 66 | 67 | #in each sub-square, only one number btw [i*n+1,(i+1)*n] 68 | #note: this additional constriant is to reduce complexity 69 | p.st([sum(x[i+bi, j+bj, k] for i,j in rtS 70 | for k in range(h*n+1, (h+1)*n+1))==1 71 | for h in range(n) for bi, bj in rtB], 'red') 72 | 73 | p.solve() 74 | print("solver status: %s"% p.status()) 75 | for i in range(1,n+1): 76 | print(' '.join("%2g"%sum(x[i,j,k].primal*k for k in N) 77 | for j in range(1,n+1))) 78 | 79 | p.end() 80 | -------------------------------------------------------------------------------- /models/dectree.py: -------------------------------------------------------------------------------- 1 | #data section: 2 | S = 30 3 | D = 100 4 | 5 | decisions=['a','c','d','e'] 6 | events = ['b','f','g','h'] 7 | tree = { 8 | 'a':('b','e'), 9 | 'b':(('c',0.7),('d',0.3)), 10 | 'c':('f',90-S), 11 | 'd':('g',90-S), 12 | 'e':('h',90), 13 | 'f':((800-D-S,.143),(-D-S,.857)), 14 | 'g':((800-D-S,.5),(-D-S,.5)), 15 | 'h':((800-D,.25),(-D,.75))} 16 | tree_root='a' 17 | 18 | from pymprog import * 19 | 20 | begin("dectree") 21 | verbose(True) 22 | 23 | x = var('x', decisions, bounds = (None, None)) 24 | y = var('y', events, bounds = (None, None)) 25 | 26 | def vnode(node): 27 | if node not in tree: 28 | return node #assume a number 29 | return (y[node] if node in events else x[node]) 30 | 31 | minimize(sum(x[i] for i in decisions),'forced') 32 | decst = {} 33 | for i in decisions: 34 | decst[i] = st([ 0 <= x[i] - vnode(j) 35 | for j in tree[i]], 'dec_node[%s]'%i) 36 | 37 | st([ y[i] == sum(j[1]*vnode(j[0]) for j in tree[i]) 38 | for i in events], 'evt_nodes') 39 | 40 | solve() 41 | 42 | print(status()) 43 | 44 | for t in x: print(x[t]) 45 | for t in y: print(y[t]) 46 | 47 | print(x[tree_root].primal) 48 | 49 | print("EVSI = %r"% evaluate(y['b'] - x['e']+S)) 50 | 51 | def trace_dec(cnode): 52 | #print "tracing node", cnode 53 | if cnode in decisions: 54 | k = 0 55 | for j in tree[cnode]: 56 | if decst[cnode][k].dual: 57 | print("%s -> %s"%(cnode,str(j))) 58 | trace_dec(j) 59 | k += 1 60 | elif cnode in events: 61 | for j in tree[cnode]: 62 | trace_dec(j[0]) 63 | 64 | trace_dec(tree_root) 65 | end() 66 | -------------------------------------------------------------------------------- /models/dectree2.py: -------------------------------------------------------------------------------- 1 | #data section: 2 | S = 30 3 | D = 100 4 | 5 | decisions=['a','c','d','e'] 6 | events = ['b','f','g','h'] 7 | tree = { 8 | 'a':('b','e'), 9 | 'b':(('c',0.7),('d',0.3)), 10 | 'c':('f',90-S), 11 | 'd':('g',90-S), 12 | 'e':('h',90), 13 | 'f':((800-D-S,.143),(-D-S,.857)), 14 | 'g':((800-D-S,.5),(-D-S,.5)), 15 | 'h':((800-D,.25),(-D,.75))} 16 | tree_root='a' 17 | 18 | from pymprog import * 19 | 20 | beginModel("dectree") 21 | verbose(True) 22 | 23 | ##### 24 | # approach one 25 | ##### 26 | #suppose at any decision node, you can 27 | #randomize among the alternatives, 28 | #so for each decision node, attach 29 | #a probability to each of its choises (arcs). 30 | # 31 | # x = var('x', arcs) 32 | # 33 | #for each node, we must balance the 34 | #probabilities of in-arcs and out-arcs. 35 | # for j in nodes: 36 | # x[i,j] == sum( x[j,k] 37 | # for k in children of j) 38 | # 39 | # And a value can be assigned to each arc as well, 40 | # as this is a standard feature of decision trees. 41 | # 42 | # maximize( sum(x[i]*v[i] for i in arcs ) ) 43 | 44 | ##### 45 | # approach two 46 | ##### 47 | #each arc has a binary var (indicating if it is chosen). 48 | #if it is not chosen, then all the children of the node 49 | # that is directly reached by this arc can not be chosen. 50 | #if it is chosen, there are two cases: 51 | # 1) if it leads to a decision node, only one of its children 52 | # may be chosen; 53 | # sum(x[i] for i in arcs from dec_node) == x[arc] 54 | # 2) if it leads to an event node, all of its children are chosen. 55 | # x[i] == x[arc] for i in arcs from event_node 56 | 57 | # Also note that, for any single arc, 58 | # a probability can be assigned to it this way: 59 | # a unique path exists from the root to this arc, 60 | # and each event arc on the path has a probability, 61 | # simply find the product of those probabilities, 62 | # and assign the product to the arc in question. 63 | 64 | # And a value can be assigned to each arc as well, 65 | # as this is a standard feature of decision trees. 66 | 67 | # Then maximize( x[i] * v[i] * p[i] for i in arcs ) 68 | 69 | 70 | x = var('x', decisions, bounds = (None, None)) 71 | y = var('y', events, bounds = (None, None)) 72 | 73 | 74 | def vnode(node): 75 | if node not in tree: 76 | return node #assume a number 77 | return (y[node] if node in events else x[node]) 78 | 79 | minimize(sum(x[i] for i in decisions),'forced') 80 | decst = {} 81 | for i in decisions: 82 | decst[i] = st([ 0 <= x[i] - vnode(j) 83 | for j in tree[i]], 'dec_node[%s]'%i) 84 | 85 | st([y[i] == sum(j[1]*vnode(j[0]) for j in tree[i]) 86 | for i in events], 'evt_nodes') 87 | 88 | solve() 89 | 90 | print(status()) 91 | 92 | for t in x: print(x[t]) 93 | for t in y: print(y[t]) 94 | 95 | print(x[tree_root].primal) 96 | 97 | print("EVSI = %r"% evaluate(y['b'] - x['e']+S)) 98 | 99 | 100 | def trace_dec(cnode): 101 | if cnode in decisions: 102 | k = 0 103 | for j in tree[cnode]: 104 | if decst[cnode][k].dual: 105 | print("%s -> %s"%(cnode,str(j))) 106 | trace_dec(j) 107 | k += 1 108 | elif cnode in events: 109 | for j in tree[cnode]: 110 | trace_dec(j[0]) 111 | 112 | trace_dec(tree_root) 113 | end() 114 | -------------------------------------------------------------------------------- /models/deltest.py: -------------------------------------------------------------------------------- 1 | from pymprog import * 2 | p = begin('trader') 3 | verbose(True) 4 | x = var('x', 3) 5 | c = par('c', [100, 300, 50]) 6 | b = par('b', [93000, 101, 201]) 7 | maximize(sum(c[i]*x[i] for i in range(3)), 'Profit') 8 | 9 | 300*x[0] + 1200*x[1] + 120*x[2] <= b[0] 10 | 0.5*x[0] + x[1] + 0.5*x[2] <= b[1] 11 | r = x[0] + x[1] + x[2] <= b[2] 12 | 13 | solve() 14 | sensitivity() 15 | 16 | r.delete() 17 | # deleting a basic varriable destroys the basis 18 | x[1].delete() 19 | # restore the standard basis 20 | p.std_basis() 21 | solve() 22 | sensitivity() 23 | 24 | end() 25 | -------------------------------------------------------------------------------- /models/dualtest.py: -------------------------------------------------------------------------------- 1 | from pymprog import * 2 | 3 | beginModel('dual') 4 | 5 | x = var('x') 6 | minimize(x) 7 | r=st(+x>=5) 8 | 9 | solve() 10 | print(status()) 11 | 12 | print(r.dual) 13 | 14 | end() 15 | -------------------------------------------------------------------------------- /models/dynaqueens.py: -------------------------------------------------------------------------------- 1 | from pymprog import model, iprod, glpk 2 | 3 | # The Queens Problem is to place as many queens as possible on the nxn 4 | # chess board in a way that they do not fight 5 | # each other. This problem is probably as old as the chess game itself, 6 | # and thus its origin is not known, but it is known that Gauss studied 7 | # this problem. 8 | 9 | def queens(n): # n: size of the chess board 10 | p = model('queens') 11 | iboard = iprod(range(n), range(n)) #create indices 12 | x = p.var('x', iboard, bool) #create variables 13 | sum(x[t] for t in iboard) == n 14 | for i in range(n): # row-wise 15 | sum(x[i,j] for j in range(n)) <= 1 16 | for j in range(n): # column-wise 17 | sum(x[i,j] for i in range(n)) <= 1 18 | for k in range(2-n, n-1): # diagonal '\' wise 19 | sum(x[i,j] for i,j in iboard if i-j == k) <= 1 20 | for k in range(1, n+n-2): # anti-diagonal '/' wise 21 | sum(x[i,j] for i,j in iboard if i+j == k) <= 1 22 | return p,x 23 | 24 | import random 25 | n = random.randint(6, 11) 26 | print("Board size: %i X %i"%(n,n)) 27 | def randpair(): 28 | m = random.randint(0, n*n-1) 29 | return m%n, m//n 30 | def randpos(k): 31 | while True: 32 | pos = [randpair() for i in range(k)] 33 | if len(set(i for i,j in pos))= 5*p[1] + 9*p[2]) 27 | r2=st(v >= 8*p[1] + 6*p[2]) 28 | solve() 29 | print(v) 30 | print("Player 1's mixed Strategy:" ) 31 | print("A1: %r; A2: %r"%(r1.dual, r2.dual)) 32 | print("Player 2's mixed strategy:" ) 33 | print("B1: %r; B2: %r"%(p[1].primal, p[2].primal)) 34 | end() 35 | -------------------------------------------------------------------------------- /models/gassign.py: -------------------------------------------------------------------------------- 1 | # Assignment Problem 2 | # Written in pymprog by Yingjie Lan 3 | 4 | # The assignment problem is one of the fundamental combinatorial 5 | # optimization problems. 6 | 7 | # In its most general form, the problem is as follows: 8 | 9 | # There are a number of agents and a number of tasks. Any agent can be 10 | # assigned to perform any task, incurring some cost that may vary 11 | # depending on the agent-task assignment. It is required to perform all 12 | # tasks by assigning exactly one agent to each task in such a way that 13 | # the total cost of the assignment is minimized. 14 | # (From Wikipedia, the free encyclopedia.) 15 | 16 | #problem data 17 | m = 8 # agents 18 | M = range(m) #set of agents 19 | n = 8 # tasks 20 | N = range(n) #set of tasks 21 | c = [ #cost 22 | (13,21,20,12,8,26,22,11), 23 | (12,36,25,41,40,11,4,8), 24 | (35,32,13,36,26,21,13,37), 25 | (34,54,7,8,12,22,11,40), 26 | (21,6,45,18,24,34,12,48), 27 | (42,19,39,15,14,16,28,46), 28 | (16,34,38,3,34,40,22,24), 29 | (26,20,5,17,45,31,37,43)] 30 | 31 | from pymprog import * 32 | 33 | beginModel("assign") 34 | #verbose(True) #Turn on this for model output 35 | A = iprod(M, N) #combine index 36 | #declare variables 37 | x = var('x', A) #assignment decision vars 38 | #declare parameters: 39 | #for automatic model update if parameters change 40 | tc = par('cost', c) 41 | minimize(sum(tc[i][j]*x[i,j] for i,j in A), 'totalcost') 42 | st(#subject to: each agent works on at most one task 43 | [sum(x[k,j] for j in N)<=1 for k in M], #one for each agent 44 | 'agent') #a name for this group of constraints, optional 45 | st(#subject to: each task must be assigned to somebody 46 | [sum(x[i,k] for i in M)==1 for k in N], 'task') 47 | 48 | solve() 49 | print("Total Cost = %g"%vobj()) 50 | assign = [(i,j) for i in M for j in N 51 | if x[i,j].primal>0.5] 52 | for i,j in assign: 53 | print("Agent %d gets Task %d with Cost %g"%(i, j, tc[i][j].value)) 54 | 55 | i,j = assign[0] 56 | tc[i][j].value += 10 57 | print("set cost c%s to higher value %s"%(str([i,j]),str(tc[i][j].value))) 58 | 59 | solve() #this takes care of model update 60 | print("Total Cost = %g"%vobj()) 61 | assign = [(i,j) for i in M for j in N 62 | if x[i,j].primal>0.5] 63 | for i,j in assign: 64 | print("Agent %d gets Task %d with Cost %g"%(i, j, tc[i][j].value)) 65 | 66 | end() 67 | -------------------------------------------------------------------------------- /models/gassign2.py: -------------------------------------------------------------------------------- 1 | # Assignment Problem 2 | # Written in pymprog by Yingjie Lan 3 | 4 | # The assignment problem is one of the fundamental combinatorial 5 | # optimization problems. 6 | 7 | # In its most general form, the problem is as follows: 8 | 9 | # There are a number of agents and a number of tasks. Any agent can be 10 | # assigned to perform any task, incurring some cost that may vary 11 | # depending on the agent-task assignment. It is required to perform all 12 | # tasks by assigning exactly one agent to each task in such a way that 13 | # the total cost of the assignment is minimized. 14 | # (From Wikipedia, the free encyclopedia.) 15 | 16 | #problem data 17 | m = 8 # agents 18 | M = range(m) #set of agents 19 | n = 8 # tasks 20 | N = range(n) #set of tasks 21 | c = ( #cost 22 | (13,21,20,12,8,26,22,11), 23 | (12,36,25,41,40,11,4,8), 24 | (35,32,13,36,26,21,13,37), 25 | (34,54,7,8,12,22,11,40), 26 | (21,6,45,18,24,34,12,48), 27 | (42,19,39,15,14,16,28,46), 28 | (16,34,38,3,34,40,22,24), 29 | (26,20,5,17,45,31,37,43)) 30 | 31 | from pymprog import * 32 | 33 | begin("assign") 34 | #verbose(True) #Turn on this for model output 35 | A = iprod(M, N) #combine index 36 | #declare variables 37 | x = var('x', A) #assignment decision vars 38 | #declare parameters: 39 | #this enables automatic model update if parameters change 40 | tc = par('cost', c) #each value is replaced by a param 41 | minimize(sum(tc[i][j]*x[i,j] for i,j in A), 'totalcost') 42 | st(#subject to: each agent works on at most one task 43 | [sum(x[k,j] for j in N)<=1 for k in M], #one for each agent 44 | 'agent') #a name for this group of constraints, optional 45 | st(#subject to: each task must be assigned to somebody 46 | [sum(x[i,k] for i in M)==1 for k in N], 'task') 47 | 48 | solve() 49 | print("Total Cost = %g"%vobj()) 50 | assign = [(i,j) for i in M for j in N 51 | if x[i,j].primal>0.5] 52 | for i,j in assign: 53 | print("Agent %d gets Task %d with Cost %g"%(i, j, tc[i][j].value)) 54 | 55 | i,j = assign[0] 56 | tc[i][j].value += 10 57 | print("set cost c%s to higher value %s"%(str((i,j)),str(tc[i][j].value))) 58 | 59 | solve() #this takes care of model update 60 | print("Total Cost = %g"%vobj()) 61 | assign = [(i,j) for i in M for j in N 62 | if x[i,j].primal>0.5] 63 | for i,j in assign: 64 | print("Agent %d gets Task %d with Cost %g"%(i, j, tc[i][j].value)) 65 | 66 | end() 67 | -------------------------------------------------------------------------------- /models/gbasic.py: -------------------------------------------------------------------------------- 1 | from pymprog import * # Import the module 2 | # index and data 3 | xid, rid = range(3), range(3) 4 | c = (10.0, 6.0, 4.0) 5 | mat = [ (1.0, 1.0, 1.0), 6 | (10.0, 4.0, 5.0), 7 | (2.0, 2.0, 6.0)] 8 | b = (100.0, 600.0, 300.0) 9 | #problem definition 10 | beginModel('basic') 11 | verbose(True) 12 | x = var('X', 3) #create variables 13 | maximize( #set objective 14 | sum(c[i]*x[i] for i in xid), 'myobj' 15 | ) 16 | r=st( #set constraints 17 | sum(x[j]*mat[i][j] for j in xid) <= b[i] for i in rid 18 | ) 19 | solve() #solve and report 20 | print("Solver status:", status()) 21 | print('Z = %g;' % vobj()) # obj value 22 | #Print variable names and primal values 23 | print(';\n'.join('%s = %g {dual: %g}' % ( 24 | x[i].name, x[i].primal, x[i].dual) 25 | for i in xid)) 26 | print(';\n'.join('%s = %g {dual: %g}' % ( 27 | r[i].name, r[i].primal, r[i].dual) 28 | for i in rid)) 29 | print(KKT()) 30 | print(evaluate(sum(x[i]*(i+x[i])**2 for i in xid))) 31 | print(sum(x[i].primal*(i+x[i].primal)**2 for i in xid)) 32 | end() #Good habit: do away with the problem 33 | -------------------------------------------------------------------------------- /models/groups.py: -------------------------------------------------------------------------------- 1 | 2 | from pymprog import * 3 | 4 | r = group('r') 5 | begin('groups') 6 | x, y = var('x, y') 7 | 8 | r['abc'] = x+y <= 1 9 | assert r['abc'].name == "r['abc']" 10 | r[1,2] = 3*x+y >= 5 11 | assert r[1,2].name == "r[1,2]" 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /models/job_sched.py: -------------------------------------------------------------------------------- 1 | n = 3 2 | N = range(n) 3 | M = [(i,j) for i in N for j in N if i overlap betw jobs i,j 17 | y = var("y", M ) 18 | #w[i,j]: the 'OR' for |T[i]-x[j]-MD[i,j]| 19 | w = var("w", M, kind=bool) 20 | # z[i,j] >= MD[i,j] - y[i,j] 21 | z = var("z", M) 22 | 23 | minimize( sum(z[i,j] for i,j in M) ) 24 | 25 | st([(D[i]+D[j])/2.0 - (x[i]+D[i] - x[j]) + 26 | (U[i]-L[j]) * w[i,j] >= y[i,j] 27 | for i,j in M], 'small') 28 | st([(x[i]+D[i] - x[j]) - (D[i]+D[j])/2.0 + 29 | (U[j]-L[i])*(1-w[i,j]) >= y[i,j] 30 | for i,j in M], 'large') 31 | st([(D[i]+D[j])/2.0 - y[i,j] <= z[i,j] 32 | for i,j in M], 'overlap') 33 | 34 | #set bounds on x 35 | for i in N: 36 | L[i] <= x[i] <= U[i] - D[i] 37 | 38 | #another way to enforce no overlapping: 39 | # 40 | # x[i] >= T[j] or x[j] >= T[i] 41 | # 42 | # Which can be formulated as: 43 | # 44 | # x[i] + (U[j]-L[i])*w[i,j]>= x[j]+D[j] 45 | # x[j] + (U[i]-L[j])*(1-w[i,j]) >= x[i]+D[i] 46 | 47 | solve() 48 | 49 | print("status: %r"% status()) 50 | print( "overlap: %r"% vobj()) 51 | print( "schedule:") 52 | for i in N: 53 | start = x[i].primal 54 | print( "job %i: %r, %r"%(i, start, start+D[i])) 55 | 56 | -------------------------------------------------------------------------------- /models/knapsack.py: -------------------------------------------------------------------------------- 1 | ## TODD, a class of hard instances of zero-one knapsack problems 2 | 3 | """Chvatal describes a class of instances of zero-one knapsack problems 4 | due to Todd. He shows that a wide class of algorithms - including all 5 | based on branch and bound or dynamic programming - find it difficult 6 | to solve problems in the Todd class. More exactly, the time required 7 | by these algorithms to solve instances of problems that belong to the 8 | Todd class grows as an exponential function of the problem size. 9 | 10 | Reference: 11 | Chvatal V. (1980), Hard knapsack problems, Op. Res. 28, 1402-1411.""" 12 | 13 | ## change this parameter to choose a particular instance 14 | n = 19 # I found this is a particularly hard case. 15 | 16 | from math import * 17 | log2_n = log(n) / log(2) 18 | 19 | k = floor(log2_n) 20 | 21 | a = [2 ** (k + n + 1) + 2 ** (k + n + 1 - j) + 1 22 | for j in range(1,n+1)] 23 | 24 | b = 0.5 * floor(sum(a)); 25 | 26 | import pymprog 27 | todd = pymprog.model("todd") 28 | 29 | x = todd.var('x', range(n), bool) 30 | 31 | todd.maximize(sum(a[j]*x[j] for j in x)) 32 | todd.st(sum(a[j]*x[j] for j in x)<=b) 33 | 34 | todd.solve() 35 | print("solver status: ", todd.status()) 36 | print("obj: ", int(todd.vobj()), "b:", int(b)) 37 | V = 0 38 | print("obj, value, accum") 39 | for t in x: 40 | if x[t].primal > 0: 41 | V += int(a[t]) 42 | print("%3i, %12i, %12i"%(t, int(a[t]), V)) 43 | todd.end() 44 | -------------------------------------------------------------------------------- /models/magic.py: -------------------------------------------------------------------------------- 1 | # MAGIC, Magic Square 2 | 3 | 4 | """In recreational mathematics, a magic square of order n is an 5 | arrangement of n^2 numbers, usually distinct integers, in a square, 6 | such that n numbers in all rows, all columns, and both diagonals sum 7 | to the same constant. A normal magic square contains the integers 8 | from 1 to n^2. 9 | 10 | (From Wikipedia, the free encyclopedia.) 11 | 12 | When n=5, we have: 13 | 14 | the magic sum must be: 65 15 | solver status: opt 16 | 10 2 4 24 25 17 | 9 11 20 6 19 18 | 22 14 16 8 5 19 | 23 17 7 15 3 20 | 1 21 18 12 13""" 21 | 22 | 23 | # square order 24 | n = 4 25 | # set of numbers 26 | N = range(1,n*n+1) 27 | #the magic sum: 28 | s = sum(t for t in N)//n 29 | print("the magic sum must be: %r "% s) 30 | 31 | import pymprog 32 | p = pymprog.model('magic') 33 | S = pymprog.iprod(range(1,n+1), range(1,n+1)) 34 | T = pymprog.iprod(range(1,n+1), range(1,n+1), N) 35 | x = p.var('x', T, bool) 36 | #x[i,j,k] = 1 means that cell (i,j) contains integer k 37 | 38 | #each cell must be assigned exactly one integer 39 | p.st([sum(x[i,j,k] for k in N)==1 for i,j in S]) 40 | 41 | #each integer must be assigned exactly to one cell 42 | p.st([sum(x[i,j,k] for i,j in S)==1 for k in N]) 43 | 44 | #the sum in each row must be the magic sum 45 | p.st([sum(k*x[i,j,k] for j in range(1,n+1) for k in N)==s 46 | for i in range(1, n+1)], 'row') 47 | 48 | #the sum in each column must be the magic sum 49 | p.st([sum(k*x[i,j,k] for i in range(1,n+1) for k in N)==s 50 | for j in range(1, n+1)], 'col') 51 | 52 | #the sum in the diagonal must be the magic sum 53 | p.st(sum(k*x[i,i,k] for i in range(1,n+1) for k in N)==s, 'dia') 54 | 55 | #the sum in the co-diagonal must be the magic sum 56 | p.st(sum(k*x[i,n+1-i,k] for i in range(1,n+1) for k in N)==s, 'cod') 57 | 58 | p.solve() 59 | print("solver status: %s"% p.status()) 60 | for i in range(1,n+1): 61 | print(' '.join("%2g"%sum(x[i,j,k].primal*k for k in N) 62 | for j in range(1,n+1))) 63 | p.end() 64 | -------------------------------------------------------------------------------- /models/maptest.py: -------------------------------------------------------------------------------- 1 | from pymprog import model 2 | imap = model._idxmap 3 | 4 | def test_del1(): 5 | m = imap() 6 | a = m.add(5) 7 | assert a == 1 8 | m.del1(1) 9 | assert m.nmap == 4 10 | for i in range (5): 11 | assert m.map(1+i) == i 12 | m.del1(5) 13 | m.del1(3) 14 | assert m.map(3) == 0 15 | assert m.map(4) == 2 16 | try: 17 | m.map(5) #exception 18 | assert 1>2 19 | except: pass 20 | assert m.nmap == 2 21 | a = m.add(3) 22 | assert a == 5 23 | assert m.map(a) == 3 24 | 25 | 26 | 27 | 28 | def test_delmany(): 29 | m = imap() 30 | a = m.add(50) 31 | assert a == 1 32 | dl = [2, 9, 20, 21, 22, 48, 49, 50] 33 | m.delmany(dl) 34 | prev = 0 35 | for i, d in enumerate(dl): 36 | if d<48: 37 | #print(i, d, m.map(d-1)) 38 | assert m.map(d-1) == (d-1-i if prev+1 < d else 0) 39 | try: 40 | assert m.map(d) == 0 41 | if d<48: assert 1>2 42 | except: pass 43 | prev = d 44 | 45 | test_del1() 46 | test_delmany() 47 | -------------------------------------------------------------------------------- /models/monopath.py: -------------------------------------------------------------------------------- 1 | from pymprog import * 2 | # http://pymprog.sourceforge.net/ 3 | 4 | c = 0.05 5 | N = 30 6 | #p = [(1.0+i)/N for i in range(N)] 7 | 8 | minp = (1-1./N)**N # D* = (M-m)(1-1/T)**T 9 | #minp = 0.37 10 | print(minp) 11 | p = [minp**((N-1.0-i)/(N-1.0)) for i in range(N)] 12 | p = par('p', p) 13 | 14 | begin("monopath") 15 | x = var("x", N) 16 | y = var("y", N, bool) 17 | z = var('z', N, bool) 18 | D = var("D") 19 | 20 | minimize(D) 21 | for t in range(N): 22 | D + sum(p[i]*x[i] - c*y[i] for i in range(t+1)) - c*z[t] >= p[t] - c 23 | y[t] >= x[t] 24 | z[t] >= 1 - sum(x[:t+1]) 25 | 26 | sum(x[i] for i in range(N)) == 1 27 | 28 | # for i in range(72): x[i] == 0 29 | # x[72] == 0.3 30 | #x[16]==0.3 #coerced! 31 | 32 | solve() 33 | print(D.primal) 34 | tset = [t for t in range(N) if y[t].primal] 35 | for t in tset: 36 | print("%i\t%r\t%.5f\t%r"%(t, p[t], x[t].primal,p[t-1])) 37 | print(t, evaluate(D + sum(p[i]*x[i] - c*y[i] for i in range(t+1)) - c*z[t] - p[t]+c)) 38 | if not t: continue 39 | print(t-1, evaluate(D + sum(p[i]*x[i] - c*y[i] for i in range(t)) - c*z[t-1] - p[t-1]+c)) 40 | 41 | dd = D.primal 42 | for i in range(N): 43 | p[i].value = dd**((N-1.0-i)/(N-1.0)) 44 | 45 | solve() 46 | print(D.primal) 47 | tset = [t for t in range(N) if y[t].primal] 48 | for t in tset: 49 | print("%i\t%r\t%.5f\t%r"%(t, p[t], x[t].primal,p[t-1])) 50 | print(t, evaluate(D + sum(p[i]*x[i] - c*y[i] for i in range(t+1)) - c*z[t] - p[t]+c)) 51 | if not t: continue 52 | print(t-1, evaluate(D + sum(p[i]*x[i] - c*y[i] for i in range(t)) - c*z[t-1] - p[t-1]+c)) 53 | 54 | end() 55 | -------------------------------------------------------------------------------- /models/newgbasic.py: -------------------------------------------------------------------------------- 1 | from pymprog import * # Import the module 2 | # max { c'x: Ax <= b, x >= 0 } 3 | c = (10.0, 6.0, 4.0) 4 | A = [ (1.0, 1.0, 1.0), 5 | (10.0, 4.0, 5.0), 6 | (2.0, 2.0, 6.0)] 7 | b = (100.0, 600.0, 300.0) 8 | # index for row and variable 9 | ir, ix = range(3), range(3) 10 | 11 | begin('basic') # start problem 12 | verbose(True) # show model 13 | 14 | x = var('X', 3) # create 3 variables 15 | maximize(sum(c[i]*x[i] for i in ix)) 16 | 17 | for i in ir: 18 | sum(x[j]*A[i][j] for j in ix) <= b[i] 19 | 20 | solve() #solve the problem 21 | 22 | print("Solver status:", status()) 23 | print('Z = ', vobj()) # obj value 24 | 25 | #Print variable names and related values 26 | for i in ix: print("%s = %g; dual = %g"%( 27 | x[i].name, x[i].primal, x[i].dual)) 28 | 29 | print(KKT()) 30 | print(evaluate(sum(x[i]*(i+x[i])**2 for i in ix))) 31 | print(sum(x[i].primal*(i+x[i].primal)**2 for i in ix)) 32 | end() #Good habit: do away with the problem 33 | -------------------------------------------------------------------------------- /models/newsyntax.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | from pymprog import * 3 | begin('test') 4 | verbose(True) 5 | 6 | x = var('x', 3) 7 | r = group('R', 8 | '{name} is the bound for the sum of x[{0}]' 9 | ' and its rotating next.') 10 | 11 | for j in range(3): print(r.desc(j)) 12 | 13 | for i in range(3): 14 | r[i] = x[i] + x[ (i+1) % 3] <= 1 15 | 16 | x[0]+0.2 <= x[1] + 0.1 <= x[2] 17 | 18 | maximize(sum(x[i] for i in range(3))) 19 | 20 | #print(ncon()) 21 | 22 | solve() 23 | 24 | for j in range(3): 25 | print(x[j], r[j]) 26 | 27 | end('test') 28 | -------------------------------------------------------------------------------- /models/panmagic.py: -------------------------------------------------------------------------------- 1 | # MAGIC, Magic Square 2 | 3 | """In recreational mathematics, a magic square of order n is an 4 | arrangement of n^2 numbers, usually distinct integers, in a square, 5 | such that n numbers in all rows, all columns, and both diagonals sum 6 | to the same constant. A normal magic square contains the integers 7 | from 1 to n^2. 8 | 9 | (From Wikipedia, the free encyclopedia.) 10 | 11 | When n=5, we have: 12 | 13 | the magic sum must be: 65 14 | solver status: opt 15 | 10 2 4 24 25 16 | 9 11 20 6 19 17 | 22 14 16 8 5 18 | 23 17 7 15 3 19 | 1 21 18 12 13""" 20 | 21 | 22 | # square order 23 | n = 4 24 | # set of numbers 25 | N = range(1,n*n+1) 26 | #the magic sum: 27 | s = sum(N)//n 28 | print("the magic sum must be: %r"%s) 29 | 30 | import pymprog 31 | p = pymprog.model('magic') 32 | S = pymprog.iprod(range(1,n+1), range(1,n+1)) 33 | T = pymprog.iprod(range(1,n+1), range(1,n+1), N) 34 | x = p.var('x', T, bool) 35 | #x[i,j,k] = 1 means that cell (i,j) contains integer k 36 | 37 | #each cell must be assigned exactly one integer 38 | p.st([sum(x[i,j,k] for k in N)==1 for i,j in S]) 39 | 40 | #each integer must be assigned exactly to one cell 41 | p.st([sum(x[i,j,k] for i,j in S)==1 for k in N]) 42 | 43 | #the sum in each row must be the magic sum 44 | p.st([sum(k*x[i,j,k] for j in range(1,n+1) for k in N)==s 45 | for i in range(1, n+1)], 'row') 46 | 47 | #the sum in each column must be the magic sum 48 | p.st([sum(k*x[i,j,k] for i in range(1,n+1) for k in N)==s 49 | for j in range(1, n+1)], 'col') 50 | 51 | #the sum in the diagonal must be the magic sum 52 | p.st([sum(k*x[i,(i+j-2)%n+1,k] for i in range(1,n+1) for k in N)==s 53 | for j in range(1,n+1)], 'dia') 54 | 55 | #the sum in the co-diagonal must be the magic sum 56 | p.st([sum(k*x[i,(n-1-i+j)%n+1,k] for i in range(1,n+1) for k in N)==s 57 | for j in range(1,n+1)], 'cod') 58 | 59 | p.solve() 60 | print("solver status: %s"% p.status()) 61 | for i in range(1,n+1): 62 | print(' '.join("%2g"%sum(x[i,j,k].primal*k for k in N) 63 | for j in range(1,n+1))) 64 | p.end() 65 | -------------------------------------------------------------------------------- /models/picore.py: -------------------------------------------------------------------------------- 1 | """ 2 | Put numbers 1..n, in a nXn square, with 3 | each number repeated exactly n times, 4 | such that each row, each column, and 5 | each wrapped diagonal have the same sum. 6 | Also, n=rt*rt, so that the nXn square 7 | can be divided into exactly n rtXrt 8 | subsquares, each contains n numbers. 9 | Those subsquares also have the same sum. 10 | """ 11 | 12 | # square order 13 | rt= 2 #square root of n 14 | n = rt*rt 15 | # set of numbers 16 | N = range(1,n+1) 17 | # the magic sum 18 | ms = sum(N) 19 | from pymprog import * 20 | begin('magic') 21 | S = iprod(range(1,n+1), range(1,n+1)) 22 | rtS=iprod(range(1,rt+1), range(1,rt+1)) 23 | rtB=iprod(range(0,n,rt), range(0,n,rt)) 24 | T = iprod(range(1,n+1), range(1,n+1), N) 25 | x = var('x', T, bool) 26 | #x[i,j,k] = 1 means that cell (i,j) contains integer k 27 | 28 | #each cell must be assigned exactly one integer 29 | st([sum(x[i,j,k] for k in N)==1 for i,j in S]) 30 | 31 | #each integer must be assigned exactly to n cells 32 | st([sum(x[i,j,k] for i,j in S)==n for k in N]) 33 | 34 | #the sum in each row must be the magic sum 35 | st([sum(k*x[i,j,k] for j in N for k in N)==ms 36 | for i in N], 'row') 37 | 38 | #the sum in each column must be the magic sum 39 | st([sum(k*x[i,j,k] for i in N for k in N)==ms 40 | for j in N], 'col') 41 | 42 | #the sum in the diagonal must be the magic sum 43 | st([sum(k*x[i,(i+j-2)%n+1,k] for i in N for k in N)==ms 44 | for j in N], 'dia') 45 | 46 | #the sum in the co-diagonal must be the magic sum 47 | st([sum(k*x[i,(n-1-i+j)%n+1,k] for i in N for k in N)==ms 48 | for j in N], 'cod') 49 | 50 | #the sum in each sub-square must be the magic sum 51 | st([sum(k*x[i+bi, j+bj, k] for i,j in rtS for k in N)==ms 52 | for bi, bj in rtB], 'sub') 53 | 54 | #the sum of the elements occupying the same location 55 | #in the subsquares must also be the same. picomagic! 56 | st([sum(k*x[i+bi, j+bj, k] for i,j in rtB for k in N)==ms 57 | for bi, bj in rtS], 'sub') 58 | 59 | solve() 60 | 61 | print("solver status: %s"% status()) 62 | for i in range(1,n+1): 63 | print(' '.join("%2g"%sum(x[i,j,k].primal*k for k in N) 64 | for j in range(1,n+1))) 65 | 66 | end() 67 | -------------------------------------------------------------------------------- /models/picosudokun.py: -------------------------------------------------------------------------------- 1 | # SUDOKU, Number Placement Puzzle 2 | # Written in pymprog by Yingjie Lan 3 | from __future__ import print_function 4 | 5 | """Sudoku, also known as Number Place, is a logic-based placement 6 | puzzle. The aim of the canonical puzzle is to enter a numerical 7 | digit from 1 through 9 in each cell of a 9x9 grid made up of 3x3 8 | subgrids (called "regions"), starting with various digits given in 9 | some cells (the "givens"). Each row, column, and region must contain 10 | only one instance of each numeral. 11 | 12 | (From Wikipedia, the free encyclopedia.) 13 | 14 | This example will provide a sample PICO Sudoku: 15 | in addition to satisfying all the requirements 16 | of Sudoku, PICO Sudoku also requires that the 17 | elements in the same position of all sub-matrix must be distinct.""" 18 | 19 | from pymprog import * # Import the module 20 | begin("picosudoku") 21 | n = 4 22 | nn = n*n 23 | I = range(1,1+nn) 24 | J = range(1,1+nn) 25 | K = range(1,1+nn) 26 | T = iprod(I,J,K) #create Indice tuples 27 | #x[i,j,k] = 1 means cell [i,j] is assigned number k 28 | x = var('x', T, bool) #binary vars 29 | #each cell must be assigned exactly one number 30 | st([sum(x[i,j,k] for k in K)==1 for i in I for j in J], 'cell') 31 | #cells in the same row must be assigned distinct numbers 32 | st([sum(x[i,j,k] for j in J)==1 for i in I for k in K], 'row') 33 | #cells in the same column must be assigned distinct numbers 34 | st([sum(x[i,j,k] for i in I)==1 for j in J for k in K], 'col') 35 | #cells in the same region must be assigned distinct numbers 36 | st([sum(x[i,j,k] for i in range(r,r+n) for j in range(c, c+n))==1 37 | for r in range(1,1+nn,n) for c in range(1,1+nn,n) for k in K],'reg') 38 | 39 | #elements in the same position of all regions are distinct 40 | st([sum(x[i+ti*n,j+tj*n,k] for ti in range(n) for tj in range(n))==1 41 | for k in K for i in range(1,1+n) for j in range(1,1+n)], 'pico') 42 | 43 | """Note: for any good arrangement, a permutation of the numbers 44 | in the arrangement would give another good arrangement. 45 | Thus one can always fix the first row to 1, 2, 3, ..., 9.""" 46 | for j in J: 47 | for k in K: 48 | #protect assignment to this 49 | x[1, j, k] == (1 if j==k else 0) 50 | 51 | #there is no need for an objective function here 52 | solver('intopt', 53 | #this branching option usually helps a lot 54 | br_tech=glpk.GLP_BR_PCH, #branching technique 55 | ) 56 | 57 | solve() 58 | 59 | for i in I: 60 | if i in range(1,1+nn,n): 61 | print(" +"+("---"*(n-1)+"--+")*n) 62 | print('', end=' ') 63 | for j in J: 64 | if j == 1: print("|", end='') 65 | print("%2g"%sum(x[i,j,k].primal*k for k in K), 66 | end='|' if j in range(n,1+nn,n) else ' ') 67 | if j==nn: print() 68 | if i == nn: 69 | print(" +"+("---"*(n-1)+"--+")*n) 70 | 71 | end() 72 | -------------------------------------------------------------------------------- /models/queens.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import pymprog 3 | 4 | # The Queens Problem is to place as many queens as possible on the nxn 5 | # chess board in a way that they do not fight 6 | # each other. This problem is probably as old as the chess game itself, 7 | # and thus its origin is not known, but it is known that Gauss studied 8 | # this problem. 9 | 10 | n = 16 #size of chess board 11 | p = pymprog.model('queens') 12 | iboard = pymprog.iprod(range(n), range(n)) #create indices 13 | x = p.var('X', iboard, bool) #create variables 14 | #row wise: 15 | p.st([sum(x[i,j] for j in range(n)) <= 1 for i in range(n)]) 16 | #column wise: 17 | p.st([sum(x[i,j] for i in range(n)) <= 1 for j in range(n)]) 18 | #diagion '/' wise 19 | p.st([sum(x[i,j] for i,j in iboard if i-j == k) <= 1 20 | for k in range(2-n, n-1)]) 21 | #diagion '\' wise 22 | p.st([sum(x[i,j] for i,j in iboard if i+j == k) <= 1 23 | for k in range(1, n+n-2)]) 24 | p.maximize(sum(x[t] for t in iboard), 'queens') 25 | 26 | p.solve() 27 | for i in range(n): 28 | for j in range(n): 29 | if x[i,j].primal > 0: print('Q', end=' ') 30 | else: print('.', end=' ') 31 | print() 32 | p.end() 33 | -------------------------------------------------------------------------------- /models/racing.py: -------------------------------------------------------------------------------- 1 | king_horse_speed = (2,4,6) 2 | tian_horse_speed = (1,3,5) 3 | 4 | #the order to race the horses 5 | strats = ( 6 | (0,1,2), 7 | (0,2,1), 8 | (1,0,2), 9 | (1,2,0), 10 | (2,0,1), 11 | (2,1,0) 12 | ) 13 | 14 | bonus = (3,2,1) # traditional: (1,1,1) 15 | 16 | def gain(king_strat, tian_strat): 17 | g = 0 18 | for i,j,k in zip(king_strat, tian_strat, range(3)): 19 | if king_horse_speed[i] > tian_horse_speed[j]: 20 | g += bonus[k] 21 | else: 22 | g -= bonus[k] 23 | return g 24 | 25 | 26 | A = [ [gain(a,b) for a in strats] for b in strats ] 27 | 28 | 29 | from pymprog import * 30 | 31 | beginModel('racing') 32 | v = var('game_value', bounds=(None, None)) 33 | x = var('prob', strats) 34 | minimize(v) 35 | st( sum( x[s] for s in strats ) == 1 ) 36 | rr = st( 37 | v >= sum( A[i][j]*x[strats[j]] for j in range(6) ) 38 | for i in range(6) 39 | ) 40 | 41 | solve() 42 | print(v) 43 | for s in x: 44 | if x[s].primal > 0: print(x[s]) 45 | for r in range(6): 46 | if rr[r].dual > 0: 47 | print(strats[r], rr[r].dual) 48 | end() 49 | -------------------------------------------------------------------------------- /models/revman_jobs.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | n = 20 3 | N = range(n) 4 | 5 | from random import Random 6 | 7 | rand = Random() 8 | 9 | D = [rand.randint(1,10) for i in N] # processing time 10 | R = [rand.randint(1,10) for i in N] # revenue 11 | tt = sum(D); tt -= tt//5 12 | L = [rand.randint(1,tt) for i in N] # arrival time 13 | U = [L[i]+D[i]+rand.randint(10,30) for i in N] # duedate 14 | 15 | #overlapping jobs 16 | M = [(i,j) for i in N for j in N 17 | if i overlap betw jobs i,j 34 | y = var('y', M) 35 | #w[i,j]: the 'OR' for |T[i]-x[j]-MD[i,j]| 36 | w = var('w', M, bool) 37 | # z[i,j] >= MD[i,j] - y[i,j] 38 | z = var('z', M ) 39 | #u[i] = 1 iff job i is scheduled. 40 | u = var('u', N, bool) 41 | 42 | maximize(sum(R[i]*u[i] for i in N), 'revenue') 43 | 44 | sum(z[i,j] for i,j in M) == 0 #single 45 | #w[i,j]=0 ==> 46 | #x[j] + D[j]/2 >= x[i]+D[i]/2 + y[i,j] 47 | #That is: i preceed j when w[i,j]=0. 48 | for i,j in M: 49 | ((D[i]+D[j])/2.0 - (x[i]+D[i]- x[j]) + 50 | (U[i]-L[j]) * w[i,j] >= y[i,j]) 51 | 52 | ((x[i]+D[i]- x[j]) - (D[i]+D[j])/2.0 + 53 | (U[j]-L[i])*(1-w[i,j]) >= y[i,j]) 54 | 55 | ((D[i]+D[j])/2.0 - y[i,j] <= z[i,j] + 56 | (2-u[i]-u[j])*(D[i]+D[j])/2.0) 57 | 58 | if (i,j) in P or (j,i) in P: 59 | w[i,j] == (0 if (i,j) in P else 1) 60 | 61 | #set bounds on x 62 | for i in N: 63 | L[i] <= x[i] <= U[i] - D[i] 64 | 65 | 66 | solver(int, 67 | #this branching option often helps 68 | br_tech=glpk.GLP_BR_PCH, 69 | ) 70 | solve() 71 | 72 | print("status:", status()) 73 | print("revenue:", vobj(), 'max:', sum(R)) 74 | print("schedule:") 75 | for i in N: 76 | start = x[i].primal 77 | used = u[i].primal 78 | print("job %i:"%i, start, start+D[i], 79 | "Accept" if used else "Reject", 80 | R[i]/float(D[i])) 81 | -------------------------------------------------------------------------------- /models/saves.py: -------------------------------------------------------------------------------- 1 | from pymprog import * 2 | p = begin('save') 3 | x = var('x', 3) 4 | x[2].kind = int 5 | c = par('c', [100, 300, 50]) 6 | b = par('b', [93000, 101, 201]) 7 | maximize(sum(c[i]*x[i] for i in range(3)), 'Profit') 8 | 9 | 300*x[0] + 1200*x[1] + 120*x[2] <= b[0] 10 | 0.5*x[0] + x[1] + 0.5*x[2] <= b[1] 11 | r = x[0] + x[1] + x[2] <= b[2] 12 | 13 | solve() 14 | 15 | save(mps='_save.mps', sol='_save.sol', 16 | clp='_save.clp', glp='_save.glp', 17 | sen='_save.sen', ipt='_save.ipt', 18 | mip='_save.mip') 19 | 20 | end() 21 | -------------------------------------------------------------------------------- /models/sensana.py: -------------------------------------------------------------------------------- 1 | from pymprog import * 2 | begin('trader') 3 | verbose(True) 4 | x = var('x', 3) 5 | c = par('c', [100, 300, 50]) 6 | b = par('b', [93000, 101, 201]) 7 | #maximize(100*x[0] + 300*x[1] + 50*x[2]) 8 | maximize(sum(c[i]*x[i] for i in range(3)), 'Profit') 9 | 10 | 300*x[0] + 1200*x[1] + 120*x[2] <= b[0] 11 | 0.5*x[0] + x[1] + 0.5*x[2] <= b[1] 12 | r = x[0] + x[1] + x[2] <= b[2] 13 | 14 | solve() 15 | sensitivity() 16 | 17 | r.delete() 18 | solve() 19 | sensitivity() 20 | 21 | x[2].delete() 22 | solve() 23 | sensitivity() 24 | 25 | x[1].free() 26 | x[1] <= 30 27 | solve() 28 | sensitivity() 29 | 30 | #print(KKT()) 31 | c[1].value += 1 32 | solve() 33 | sensitivity() 34 | 35 | end() 36 | -------------------------------------------------------------------------------- /models/ssudoku.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | """Sudoku, also known as Number Place, is a logic-based placement 3 | puzzle. The aim of the canonical puzzle is to enter a numerical 4 | digit from 1 through 9 in each cell of a 9x9 grid made up of 3x3 5 | subgrids (called "regions"), starting with various digits given in 6 | some cells (the "givens"). Each row, column, and region must contain 7 | only one instance of each numeral. 8 | 9 | (From Wikipedia, the free encyclopedia.) 10 | 11 | This example will provide a sample Super Sudoku: 12 | in addition to satisfying all the requirements 13 | of Sudoku, Super Sudoku also requires that the 14 | elements in each diagonal must be distinct.""" 15 | 16 | from pymprog import * # Import the module 17 | begin("ssudoku") 18 | I = range(1,10) 19 | J = range(1,10) 20 | K = range(1,10) 21 | T = iprod(I,J,K) #create Indice tuples 22 | #x[i,j,k] = 1 means cell [i,j] is assigned number k 23 | x = var('x', T, bool) #binary vars 24 | #each cell must be assigned exactly one number 25 | [sum(x[i,j,k] for k in K)==1 for i in I for j in J] 26 | #cells in the same row must be assigned distinct numbers 27 | [sum(x[i,j,k] for j in J)==1 for i in I for k in K] 28 | #cells in the same column must be assigned distinct numbers 29 | [sum(x[i,j,k] for i in I)==1 for j in J for k in K] 30 | #cells in the same region must be assigned distinct numbers 31 | [sum(x[i,j,k] for i in range(r,r+3) for j in range(c, c+3))==1 32 | for r in range(1,10,3) for c in range(1,10,3) for k in K] 33 | 34 | #cells in \-diagonal 35 | [sum(x[i,i,k] for i in I)==1 for k in K] 36 | 37 | #cells in /-diagonal 38 | [sum(x[i,10-i,k] for i in I)==1 for k in K] 39 | 40 | #there is no need for an objective function here 41 | 42 | solve() 43 | 44 | for i in I: 45 | if i in range(1,10,3): 46 | print(" +-------+-------+-------+") 47 | print('', end=' ') 48 | for j in J: 49 | if j in range(1,10,3): print("|", end=' ') 50 | print("%g"%sum(x[i,j,k].primal*k for k in K), end=' ') 51 | if j==9: print("|") 52 | if i == 9: 53 | print(" +-------+-------+-------+") 54 | 55 | end() 56 | -------------------------------------------------------------------------------- /models/subsetsum.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | #The subset sum problem: given a multiset of integers, find 4 | #subset of it such that the sum of the subset equals a given 5 | #value. 6 | from random import * 7 | n = 40 8 | m = 90000000 9 | a=[randint(1, m) for i in range(n)] 10 | for i in range(1, n+1): print(i, a[i-1], ",", end=' ') 11 | print() 12 | print( a) 13 | s = sum(a)/2 14 | print("(n,m):", (n,m), "sum:", sum(a), " s:", s) 15 | from pymprog import * 16 | beginModel("subsetsum") 17 | x = var('x', range(n), bool) 18 | st( sum(a[i]*x[i] for i in range(n)) == s ) 19 | 20 | from datetime import datetime 21 | print(datetime.now()) 22 | solve() 23 | print(datetime.now()) 24 | print("solver status: %r" % status()) 25 | for i in range(n): 26 | if x[i].primal > 0.5: print(a[i], end=' ') 27 | print() 28 | end() 29 | -------------------------------------------------------------------------------- /models/subtelim.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | dm = ( 4 | ( 0,86,49,57,31,69,50), 5 | (86, 0,68,79,93,24, 5), 6 | (49,68, 0,16, 7,72,67), 7 | (57,79,16, 0,90,69, 1), 8 | (31,93, 7,90, 0,86,59), 9 | (69,24,72,69,86, 0,81), 10 | (50, 5,67, 1,59,81, 0)) 11 | 12 | n = len(dm) #how many cities 13 | V = range(n) 14 | E = [(i,j) for i in V for j in V if i!=j] 15 | 16 | print("there are %d cities"%n) 17 | 18 | from pymprog import * 19 | beginModel('TSP by row generation') 20 | x = var('x', E, bool) 21 | minimize( sum(dm[i][j]*x[i,j] for i,j in E), 'TravelDist' ) 22 | st([sum( x[k,j] for j in V if j!=k ) == 1 for k in V], 'leave') 23 | st([sum( x[i,k] for i in V if i!=k ) == 1 for k in V], 'enter') 24 | 25 | solver(float, msg_lev=glpk.GLP_MSG_OFF) 26 | solver(int, msg_lev=glpk.GLP_MSG_OFF) 27 | 28 | solve() #solve the IP problem 29 | 30 | def subtour(x): 31 | "find a subtour in current solution" 32 | succ = 0 33 | subt = [succ] #start from node 0 34 | while True: 35 | succ=sum(x[succ,j].primal*j 36 | for j in V if j!=succ) 37 | if succ == 0: break #tour found 38 | subt.append(int(succ+0.5)) 39 | return subt 40 | 41 | while True: 42 | subt = subtour(x) 43 | if len(subt) == n: 44 | print("Optimal tour length:", vobj()) 45 | print("Optimal tour:\n", subt) 46 | break 47 | print("New subtour: ", subt) 48 | if len(subt) == 1: break #something wrong 49 | #now add a subtour elimination constraint: 50 | nots = [j for j in V if j not in subt] 51 | st( sum(x[i,j] for i in subt for j in nots) >= 1 ) 52 | solve() #solve the IP problem again 53 | end() 54 | -------------------------------------------------------------------------------- /models/sudoku.py: -------------------------------------------------------------------------------- 1 | # SUDOKU, Number Placement Puzzle 2 | # Written in pymprog by Yingjie Lan 3 | from __future__ import print_function 4 | """Sudoku, also known as Number Place, is a logic-based placement 5 | puzzle. The aim of the canonical puzzle is to enter a numerical 6 | digit from 1 through 9 in each cell of a 9x9 grid made up of 3x3 7 | subgrids (called "regions"), starting with various digits given in 8 | some cells (the "givens"). Each row, column, and region must contain 9 | only one instance of each numeral. 10 | 11 | Example: 12 | 13 | +-------+-------+-------+ 14 | | 5 3 . | . 7 . | . . . | 15 | | 6 . . | 1 9 5 | . . . | 16 | | . 9 8 | . . . | . 6 . | 17 | +-------+-------+-------+ 18 | | 8 . . | . 6 . | . . 3 | 19 | | 4 . . | 8 . 3 | . . 1 | 20 | | 7 . . | . 2 . | . . 6 | 21 | +-------+-------+-------+ 22 | | . 6 . | . . . | 2 8 . | 23 | | . . . | 4 1 9 | . . 5 | 24 | | . . . | . 8 . | . 7 9 | 25 | +-------+-------+-------+ 26 | 27 | (From Wikipedia, the free encyclopedia.)""" 28 | 29 | # the "givens" 30 | g =( 31 | (5,3,0,0,7,0,0,0,0), 32 | (6,0,0,1,9,5,0,0,0), 33 | (0,9,8,0,0,0,0,6,0), 34 | (8,0,0,0,6,0,0,0,3), 35 | (4,0,0,8,0,3,0,0,1), 36 | (7,0,0,0,2,0,0,0,6), 37 | (0,6,0,0,0,0,2,8,0), 38 | (0,0,0,4,1,9,0,0,5), 39 | (0,0,0,0,8,0,0,7,9)) 40 | 41 | import pymprog 42 | p = pymprog.model("sudoku") 43 | I = range(1,10) 44 | J = range(1,10) 45 | K = range(1,10) 46 | T = pymprog.iprod(I,J,K) #create Indice tuples 47 | x = p.var('x', T, bool) 48 | #x[i,j,k] = 1 means cell [i,j] is assigned number k 49 | #assign pre-defined numbers using the "givens" 50 | p.st( [ +x[i,j,k] == (1 if g[i-1][j-1] == k else 0) 51 | for (i,j,k) in T if g[i-1][j-1] > 0 ], 'given') 52 | 53 | #each cell must be assigned exactly one number 54 | p.st([sum(x[i,j,k] for k in K)==1 for i in I for j in J], 'cell') 55 | 56 | #cells in the same row must be assigned distinct numbers 57 | p.st([sum(x[i,j,k] for j in J)==1 for i in I for k in K], 'row') 58 | 59 | #cells in the same column must be assigned distinct numbers 60 | p.st([sum(x[i,j,k] for i in I)==1 for j in J for k in K], 'col') 61 | 62 | #cells in \-diagonal 63 | #p.st([sum(x[i,i,k] for i in I)==1 for k in K]) 64 | 65 | #cells in /-diagonal 66 | #p.st([sum(x[i,10-i,k] for i in I)==1 for k in K]) 67 | 68 | #cells in the same region must be assigned distinct numbers 69 | p.st([sum(x[i,j,k] for i in range(r,r+3) for j in range(c, c+3))==1 70 | for r in range(1,10,3) for c in range(1,10,3) for k in K],'reg') 71 | 72 | #there is no need for an objective function here 73 | 74 | p.solve() 75 | 76 | for i in I: 77 | if i in range(1,10,3): 78 | print(" +-------+-------+-------+") 79 | print('', end=' ') 80 | for j in J: 81 | if j in range(1,10,3): print("|", end=' ') 82 | print("%g"%sum(x[i,j,k].primal*k for k in K), end=' ') 83 | if j==9: print("|") 84 | if i == 9: 85 | print(" +-------+-------+-------+") 86 | p.end() 87 | -------------------------------------------------------------------------------- /models/test.py: -------------------------------------------------------------------------------- 1 | from pymprog import * 2 | 3 | def testBinder(): 4 | binder = iprod(('a','b'),range(3)) 5 | print(len(binder)) 6 | vd = {} 7 | for t in binder: 8 | vd[t] = t 9 | for t in binder: print(vd[t]) 10 | 11 | 12 | testBinder() 13 | 14 | def testProblem(): 15 | p = model('test') 16 | x = p.var('x', 3) 17 | for i in range(3): print(x[i] <= 5) 18 | inds = iprod(range(3), range(2)) 19 | y = p.var('y', inds) 20 | for t in inds: print(1 <= y[t] <= 3) 21 | print() 22 | #ex = (-x[0] + 2* x[1] - 5*y[2,1])*3 + 23 23 | #print(ex) 24 | #print(10 <= ex <= 30) 25 | #p.st(20 <= ex <= 60) 26 | for i in range(3): print(x[i].name, x[i].bounds) 27 | print("End") 28 | #del ex 29 | #del p.p 30 | #del p 31 | 32 | testProblem() 33 | 34 | import gc 35 | gc.collect() 36 | print(gc.garbage) 37 | -------------------------------------------------------------------------------- /models/tsp.py: -------------------------------------------------------------------------------- 1 | # TSP, Traveling Salesman Problem 2 | # Written in pymprog by Yingjie Lan 3 | 4 | # The Traveling Salesman Problem (TSP) is a famous problem. 5 | # Let a directed graph G = (V, E) be given, where V = {1, ..., n} is 6 | # a set of nodes, E V x V is a set of arcs. Let also each arc 7 | # e = (i,j) be assigned a number c[i,j], which is the length of the 8 | # arc e. The problem is to find a closed path of minimal length going 9 | # through each node of G exactly once. 10 | from __future__ import print_function 11 | 12 | n = 16 #number of nodes 13 | V = range(1,n+1) #set of notes 14 | #cost or each arc, format: (start, end):cost 15 | c = {(1,2):509, (1,3):501, (1,4):312, (1,5):1019, (1,6):736, (1,7):656, 16 | (1,8): 60, (1,9):1039, (1,10):726, (1,11):2314, (1,12):479, 17 | (1,13):448, (1,14):479, (1,15):619, (1,16):150, 18 | (2,1):509, (2,3):126, (2,4):474, (2,5):1526, (2,6):1226, (2,7):1133, 19 | (2,8):532, (2,9):1449, (2,10):1122, (2,11):2789, (2,12):958, 20 | (2,13):941, (2,14):978, (2,15):1127, (2,16):542, 21 | (3,1):501, (3,2):126, (3,4):541, (3,5):1516, (3,6):1184, (3,7):1084, 22 | (3,8):536, (3,9):1371, (3,10):1045, (3,11):2728, (3,12):913, 23 | (3,13):904, (3,14):946, (3,15):1115, (3,16):499, 24 | (4,1):312, (4,2):474, (4,3):541, (4,5):1157, (4,6):980, (4,7):919, 25 | (4,8):271, (4,9):1333, (4,10):1029, (4,11):2553, (4,12):751, 26 | (4,13):704, (4,14):720, (4,15):783, (4,16):455, 27 | (5,1):1019, (5,2):1526, (5,3):1516, (5,4):1157, (5,6):478, (5,7):583, 28 | (5,8):996, (5,9):858, (5,10):855, (5,11):1504, (5,12):677, 29 | (5,13):651, (5,14):600, (5,15):401, (5,16):1033, 30 | (6,1):736, (6,2):1226, (6,3):1184, (6,4):980, (6,5):478, (6,7):115, 31 | (6,8):740, (6,9):470, (6,10):379, (6,11):1581, (6,12):271, 32 | (6,13):289, (6,14):261, (6,15):308, (6,16):687, 33 | (7,1):656, (7,2):1133, (7,3):1084, (7,4):919, (7,5):583, (7,6):115, 34 | (7,8):667, (7,9):455, (7,10):288, (7,11):1661, (7,12):177, 35 | (7,13):216, (7,14):207, (7,15):343, (7,16):592, 36 | (8,1): 60, (8,2):532, (8,3):536, (8,4):271, (8,5):996, (8,6):740, 37 | (8,7):667, (8,9):1066, (8,10):759, (8,11):2320, (8,12):493, 38 | (8,13):454, (8,14):479, (8,15):598, (8,16):206, 39 | (9,1):1039, (9,2):1449, (9,3):1371, (9,4):1333, (9,5):858, (9,6):470, 40 | (9,7):455, (9,8):1066, (9,10):328, (9,11):1387, (9,12):591, 41 | (9,13):650, (9,14):656, (9,15):776, (9,16):933, 42 | (10,1):726, (10,2):1122, (10,3):1045, (10,4):1029, (10,5):855, 43 | (10,6):379, (10,7):288, (10,8):759, (10,9):328, (10,11):1697, 44 | (10,12):333, (10,13):400, (10,14):427, (10,15):622, (10,16):610, 45 | (11,1):2314, (11,2):2789, (11,3):2728, (11,4):2553, (11,5):1504, 46 | (11,6):1581, (11,7):1661, (11,8):2320, (11,9):1387, (11,10):1697, 47 | (11,12):1838, (11,13):1868, (11,14):1841, (11,15):1789, (11,16):2248, 48 | (12,1):479, (12,2):958, (12,3):913, (12,4):751, (12,5):677, (12,6):271, 49 | (12,7):177, (12,8):493, (12,9):591, (12,10):333, (12,11):1838, 50 | (12,13): 68, (12,14):105, (12,15):336, (12,16):417, 51 | (13,1):448, (13,2):941, (13,3):904, (13,4):704, (13,5):651, (13,6):289, 52 | (13,7):216, (13,8):454, (13,9):650, (13,10):400, (13,11):1868, 53 | (13,12): 68, (13,14): 52, (13,15):287, (13,16):406, 54 | (14,1):479, (14,2):978, (14,3):946, (14,4):720, (14,5):600, (14,6):261, 55 | (14,7):207, (14,8):479, (14,9):656, (14,10):427, (14,11):1841, 56 | (14,12):105, (14,13): 52, (14,15):237, (14,16):449, 57 | (15,1):619, (15,2):1127, (15,3):1115, (15,4):783, (15,5):401, (15,6):308, 58 | (15,7):343, (15,8):598, (15,9):776, (15,10):622, (15,11):1789, 59 | (15,12):336, (15,13):287, (15,14):237, (15,16):636, 60 | (16,1):150, (16,2):542, (16,3):499, (16,4):455, (16,5):1033, (16,6):687, 61 | (16,7):592, (16,8):206, (16,9):933, (16,10):610, (16,11):2248, 62 | (16,12):417, (16,13):406, (16,14):449, (16,15):636} 63 | #set of arcs: (i,j) repr an arc from i to j 64 | E = c.keys() 65 | 66 | import pymprog 67 | p = pymprog.model("tsp") 68 | x = p.var('x', E, bool) # x created over index set E. 69 | #minize the total travel distance 70 | p.min(sum(c[t]*x[t] for t in E), 'totaldist') 71 | #subject to: leave each city exactly once 72 | p.st([sum(x[k,j] for j in V if (k,j) in E)==1 for k in V], 'leave') 73 | #subject to: enter each city exactly once 74 | p.st([sum(x[i,k] for i in V if (i,k) in E)==1 for k in V], 'enter') 75 | 76 | #We then need some flow constraints to eliminate subtours. 77 | #y: the number of cars carried: endowed with n at city 1. 78 | #exactly one car will be sold at each city. 79 | y=p.var('y', E) 80 | p.st([(n-1)*x[t] >= y[t] for t in E], 'cap') 81 | p.st([sum(y[i,k] for i in V if (i,k) in E) + (n if k==1 else 0) 82 | ==sum(y[k,j] for j in V if (k,j) in E) + 1 for k in V], 'sale') 83 | 84 | 85 | p.solve(float) #solve as LP only. 86 | print("simplex done: %r"% p.status()) 87 | 88 | p.solve(int) #solve the IP problem 89 | 90 | # The optimal solution is 6859 91 | print(p.vobj()) 92 | tour = [t for t in E if x[t].primal>.5] 93 | cat, car = 1, n 94 | print("This is the optimal tour with [cars carried]:") 95 | for k in V: 96 | print(cat, end='') 97 | for i,j in tour: 98 | if i==cat: 99 | print("[%g]"%y[i,j].primal, end='') 100 | cat=j 101 | break 102 | print(cat) 103 | p.end() 104 | 105 | -------------------------------------------------------------------------------- /pushgit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | git push origin master 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | try: 2 | from setuptools import setup 3 | except ImportError: 4 | from distutils.core import setup 5 | 6 | #This setup file is provided by Yingjie Lan 7 | #to faciliate the installation of PyMathProg. 8 | 9 | with open('README.rst') as readme: 10 | ld = readme.read() 11 | 12 | setup(name = 'pymprog', 13 | version = '1.1.2', 14 | description = 'An easy and flexible mathematical programming environment for Python', 15 | long_description = ld, 16 | author = 'Yingjie Lan', 17 | author_email = 'ylan@pku.edu.cn', 18 | url = 'http://pymprog.sourceforge.net/', 19 | keywords = "glpk math optimization LP MIP", 20 | license = 'GPL', 21 | classifiers = [ 22 | 'Development Status :: 5 - Production/Stable', 23 | 'Intended Audience :: Science/Research', 24 | 'Intended Audience :: Education', 25 | 'License :: OSI Approved :: GNU General Public License (GPL)', 26 | 'Programming Language :: Python', 27 | "Programming Language :: Python :: 3", 28 | "Operating System :: OS Independent", 29 | #'Operating System :: Platform Independent', 30 | 'Topic :: Scientific/Engineering :: Mathematics'], 31 | py_modules=['pymprog'], #to include ./pymprog.py 32 | install_requires=['swiglpk >= 1.4.4'], # install it automatically? 33 | ) 34 | 35 | -------------------------------------------------------------------------------- /tests.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | def mymodels(): 4 | import glob, os.path 5 | ff =''' 6 | def test_{name}(me): 7 | print(' >>> Testing model: {name}') 8 | import models.{name} 9 | ''' 10 | for f in glob.glob('models/[a-z]*[.]py'): 11 | with open(f) as h: 12 | for l in h: 13 | if 'pymprog' in l: 14 | f = os.path.split(f)[1][:-3] 15 | print(f) 16 | yield ff.format(name=f) 17 | break 18 | 19 | class TestExamples(unittest.TestCase): 20 | for code in mymodels(): 21 | exec(code) 22 | 23 | if __name__ == '__main__': 24 | unittest.main() 25 | -------------------------------------------------------------------------------- /topypi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | python setup.py register sdist upload 3 | -------------------------------------------------------------------------------- /upldoc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd docs 3 | make clean 4 | make html 5 | cd _build/html 6 | scp -r . lanyjie@web.sourceforge.net:/home/project-web/pymprog/htdocs 7 | -------------------------------------------------------------------------------- /zipgit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ZIPF=`pwd`/$1-$(date +"%H%M%S").zip 3 | git archive --format zip --output $ZIPF master 4 | cd docs 5 | make clean 6 | make html 7 | cd _build 8 | zip -r $ZIPF html 9 | echo $ZIPF 10 | #scp $ZIPF lanyjie@frs.sourceforge.net:/home/frs/project/p/py/pymprog/pymprog-1.0/pymprog-1.1.1.zip 11 | --------------------------------------------------------------------------------