Skip to content

Commit 6c347a4

Browse files
authored
fix set partition problem case study docs (#827)
* remove build from gitignore * commit doc build output * fix references * remove compile output * Type examples/ (#819) * initial pass * black + isort * add ignores in pulp.py and pulp/__init__.py * start fixing mypy errors * fix some mypy * mypy passes * move Pattern class definition to be before use * restore gitignore * whiskas 1 * whiskas 2
1 parent 410720d commit 6c347a4

File tree

5 files changed

+94
-25
lines changed

5 files changed

+94
-25
lines changed

doc/source/CaseStudies/a_blending_problem.rst

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,14 @@ for this example is found in :download:`WhiskasModel1.py <../../../examples/Whis
9090
The start of the your file should then be headed with a short commenting section outlining the purpose of the program. For example:
9191

9292
.. literalinclude:: ../../../examples/WhiskasModel1.py
93-
:lines: 1-5
93+
:start-after: # BEGIN file_docstring
94+
:end-before: # END file_docstring
9495

9596
Then you will import PuLP's functions for use in your code:
9697

9798
.. literalinclude:: ../../../examples/WhiskasModel1.py
98-
:lines: 7-8
99+
:start-after: # BEGIN import_pulp
100+
:end-before: # END import_pulp
99101

100102
A variable called ``prob`` (although its name is not important) is
101103
created using the :class:`~pulp.LpProblem` function. It has two parameters, the first
@@ -104,7 +106,8 @@ parameter being either ``LpMinimize`` or ``LpMaximize`` depending on the
104106
type of LP you are trying to solve:
105107

106108
.. literalinclude:: ../../../examples/WhiskasModel1.py
107-
:lines: 10-11
109+
:start-after: # BEGIN define_prob
110+
:end-before: # END define_prob
108111

109112
The problem variables ``x1`` and ``x2`` are created using the
110113
:class:`~pulp.LpVariable` class. It has four parameters, the first is the
@@ -132,15 +135,17 @@ or::
132135
To explicitly create the two variables needed for this problem:
133136

134137
.. literalinclude:: ../../../examples/WhiskasModel1.py
135-
:lines: 13-15
138+
:start-after: # BEGIN chicken_beef_vars
139+
:end-before: # END chicken_beef_vars
136140

137141
The variable ``prob`` now begins collecting problem data with the
138142
``+=`` operator. The objective function is logically entered first, with
139143
an important comma ``,`` at the end of the statement and a short string
140144
explaining what this objective function is:
141145

142146
.. literalinclude:: ../../../examples/WhiskasModel1.py
143-
:lines: 17-18
147+
:start-after: # BEGIN obj_func
148+
:end-before: # END obj_func
144149

145150
The constraints are now entered (Note: any "non-negative"
146151
constraints were already included when defining the variables). This is
@@ -150,7 +155,8 @@ comma at the end of the constraint equation and a brief description of
150155
the cause of that constraint:
151156

152157
.. literalinclude:: ../../../examples/WhiskasModel1.py
153-
:lines: 20-25
158+
:start-after: # BEGIN constraints
159+
:end-before: # END constraints
154160

155161
Now that all the problem data is entered, the :meth:`~pulp.LpProblem.writeLP` function
156162
can be used to copy this information into a .lp file into the directory
@@ -166,14 +172,16 @@ seen frequently in Object Oriented software (such as this):
166172

167173

168174
.. literalinclude:: ../../../examples/WhiskasModel1.py
169-
:lines: 27-28
175+
:start-after: # BEGIN lp_file
176+
:end-before: # END lp_file
170177

171-
The LP is solved using the solver that PuLP chooses. The input
172-
brackets after :meth:`~pulp.LpProblem.solve` are left empty in this case, however they can be
178+
The LP is solved using the solver that PuLP chooses. The input brackets after
179+
:meth:`~pulp.LpProblem.solve` are left empty in this case, however they can be
173180
used to specify which solver to use (e.g ``prob.solve(CPLEX())`` ):
174181

175182
.. literalinclude:: ../../../examples/WhiskasModel1.py
176-
:lines: 30-31
183+
:start-after: # BEGIN prob_solve
184+
:end-before: # END prob_solve
177185

178186
Now the results of the solver call can be displayed as output to
179187
us. Firstly, we request the status of the solution, which can be one of
@@ -184,13 +192,15 @@ to its significant text meaning using the
184192
:attr:`~pulp.constants.LpStatus` is a dictionary(:obj:`dict`), its input must be in square brackets:
185193

186194
.. literalinclude:: ../../../examples/WhiskasModel1.py
187-
:lines: 33-34
195+
:start-after: # BEGIN print_status
196+
:end-before: # END print_status
188197

189198
The variables and their resolved optimum values can now be printed
190199
to the screen.
191200

192201
.. literalinclude:: ../../../examples/WhiskasModel1.py
193-
:lines: 36-38
202+
:start-after: # BEGIN print_var_value
203+
:end-before: # END print_var_value
194204

195205
The ``for`` loop makes ``variable`` cycle through all
196206
the problem variable names (in this case just ``ChickenPercent`` and
@@ -207,7 +217,8 @@ format to be displayed. :attr:`~pulp.LpProblem.objective` is an attribute of the
207217
``prob``:
208218

209219
.. literalinclude:: ../../../examples/WhiskasModel1.py
210-
:lines: 40-41
220+
:start-after: # BEGIN print_obj
221+
:end-before: # END print_obj
211222

212223
Running this file should then produce the output to show that
213224
Chicken will make up 33.33%, Beef will make up 66.67% and the
@@ -291,32 +302,36 @@ As with last time, it is advisable to head your file with commenting on its
291302
purpose, and the author name and date. Importing of the PuLP functions is also done in the same way:
292303

293304
.. literalinclude:: ../../../examples/WhiskasModel2.py
294-
:lines: 1-8
305+
:start-after: # BEGIN docstring_imports
306+
:end-before: # END docstring_imports
295307

296308
Next, before the ``prob`` variable or type of problem are defined,
297309
the key problem data is entered into dictionaries. This includes the
298-
list of Ingredients, followed by the cost of each Ingredient, and it's
310+
list of Ingredients, followed by the cost of each Ingredient, and its
299311
percentage of each of the four nutrients. These values are clearly laid
300312
out and could easily be changed by someone with little knowledge of
301313
programming. The ingredients are the reference keys, with the numbers as
302314
the data.
303315

304316
.. literalinclude:: ../../../examples/WhiskasModel2.py
305-
:lines: 10-61
317+
:start-after: # BEGIN problem_data
318+
:end-before: # END problem_data
306319

307320
The ``prob`` variable is created to contain the formulation, and the
308321
usual parameters are passed into :class:`~pulp.LpProblem`.
309322

310323
.. literalinclude:: ../../../examples/WhiskasModel2.py
311-
:lines: 63-64
324+
:start-after: # BEGIN define_prob
325+
:end-before: # END define_prob
312326

313327
A dictionary called ``ingredient_vars`` is created which contains
314328
the LP variables, with their defined lower bound of zero. The reference
315329
keys to the dictionary are the Ingredient names, and the data is
316330
``Ingr_IngredientName``. (e.g. MUTTON: Ingr_MUTTON)
317331

318332
.. literalinclude:: ../../../examples/WhiskasModel2.py
319-
:lines: 66-67
333+
:start-after: # BEGIN ingredient_vars
334+
:end-before: # END ingredient_vars
320335

321336
Since ``costs`` and ``ingredient_vars`` are now dictionaries with the
322337
reference keys as the Ingredient names, the data can be simply extracted
@@ -325,12 +340,14 @@ elements of the resulting list. Thus the objective function is simply
325340
entered and assigned a name:
326341

327342
.. literalinclude:: ../../../examples/WhiskasModel2.py
328-
:lines: 69-73
343+
:start-after: # BEGIN obj_function
344+
:end-before: # END obj_function
329345

330346
Further list comprehensions are used to define the other 5 constraints, which are also each given names describing them.
331347

332348
.. literalinclude:: ../../../examples/WhiskasModel2.py
333-
:lines: 75-92
349+
:start-after: # BEGIN constraints
350+
:end-before: # END constraints
334351

335352
Following this, the :meth:`~pulp.LpProblem.writeLP` line etc follow exactly the same as
336353
in the simplified example.

doc/source/CaseStudies/a_set_partitioning_problem.rst

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,29 +25,35 @@ First we use :func:`~pulp.allcombinations` to generate a list of all
2525
possible table seatings.
2626

2727
.. literalinclude:: ../../../examples/wedding.py
28-
:lines: 22-23
28+
:start-after: # BEGIN possible_tables
29+
:end-before: # END possible_tables
2930

3031
Then we create a binary variable that will be 1 if the table will be in the solution, or zero otherwise.
3132

3233
.. literalinclude:: ../../../examples/wedding.py
33-
:lines: 25-28
34+
:start-after: # BEGIN define_x
35+
:end-before: # END define_x
3436

3537
We create the :class:`~pulp.LpProblem` and then make the objective function. Note that
3638
happiness function used in this script would be difficult to model in any other way.
3739

3840
.. literalinclude:: ../../../examples/wedding.py
39-
:lines: 30-32
41+
:start-after: # BEGIN class_and_obj_fn
42+
:end-before: # END class_and_obj_fn
43+
4044

4145
We specify the total number of tables allowed in the solution.
4246

4347
.. literalinclude:: ../../../examples/wedding.py
44-
:lines: 34-38
48+
:start-after: # BEGIN total_table_constraint
49+
:end-before: # END total_table_constraint
4550

4651
This set of constraints defines the set partitioning problem by guaranteeing that a guest is allocated to
4752
exactly one table.
4853

4954
.. literalinclude:: ../../../examples/wedding.py
50-
:lines: 40-45
55+
:start-after: # BEGIN exactly_one_table_constraint
56+
:end-before: # END exactly_one_table_constraint
5157

5258
The full file can be found here :download:`wedding.py <../../../examples/wedding.py>`
5359

examples/WhiskasModel1.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,64 @@
1+
# BEGIN file_docstring
12
"""
23
The Simplified Whiskas Model Python Formulation for the PuLP Modeller
34
45
Authors: Antony Phillips, Dr Stuart Mitchell 2007
56
"""
7+
# END file_docstring
68

9+
# BEGIN import_pulp
710
# Import PuLP modeler functions
811
from pulp import *
912

13+
# END import_pulp
14+
15+
# BEGIN define_prob
1016
# Create the 'prob' variable to contain the problem data
1117
prob = LpProblem("The Whiskas Problem", LpMinimize)
18+
# END define_prob
1219

20+
# BEGIN chicken_beef_vars
1321
# The 2 variables Beef and Chicken are created with a lower limit of zero
1422
x1 = LpVariable("ChickenPercent", 0, None, LpInteger)
1523
x2 = LpVariable("BeefPercent", 0)
24+
# END chicken_beef_vars
1625

26+
# BEGIN obj_func
1727
# The objective function is added to 'prob' first
1828
prob += 0.013 * x1 + 0.008 * x2, "Total Cost of Ingredients per can"
29+
# END obj_func
1930

31+
# BEGIN constraints
2032
# The five constraints are entered
2133
prob += x1 + x2 == 100, "PercentagesSum"
2234
prob += 0.100 * x1 + 0.200 * x2 >= 8.0, "ProteinRequirement"
2335
prob += 0.080 * x1 + 0.100 * x2 >= 6.0, "FatRequirement"
2436
prob += 0.001 * x1 + 0.005 * x2 <= 2.0, "FibreRequirement"
2537
prob += 0.002 * x1 + 0.005 * x2 <= 0.4, "SaltRequirement"
38+
# END constraints
2639

40+
# BEGIN lp_file
2741
# The problem data is written to an .lp file
2842
prob.writeLP("WhiskasModel.lp")
43+
# END lp_file
2944

45+
# BEGIN prob_solve
3046
# The problem is solved using PuLP's choice of Solver
3147
prob.solve()
48+
# END prob_solve
3249

50+
# BEGIN print_status
3351
# The status of the solution is printed to the screen
3452
print("Status:", LpStatus[prob.status])
53+
# END print_status
3554

55+
# BEGIN print_var_value
3656
# Each of the variables is printed with it's resolved optimum value
3757
for v in prob.variables():
3858
print(v.name, "=", v.varValue)
59+
# END print_var_value
3960

61+
# BEGIN print_obj
4062
# The optimised objective function value is printed to the screen
4163
print("Total Cost of Ingredients per can = ", value(prob.objective))
64+
# END print_obj

examples/WhiskasModel2.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# BEGIN docstring_imports
12
"""
23
The Full Whiskas Model Python Formulation for the PuLP Modeller
34
@@ -7,6 +8,9 @@
78
# Import PuLP modeler functions
89
from pulp import *
910

11+
# END docstring_imports
12+
13+
# BEGIN problem_data
1014
# Creates a list of the Ingredients
1115
Ingredients = ["CHICKEN", "BEEF", "MUTTON", "RICE", "WHEAT", "GEL"]
1216

@@ -59,19 +63,27 @@
5963
"WHEAT": 0.008,
6064
"GEL": 0.000,
6165
}
66+
# END problem_data
6267

68+
# BEGIN define_prob
6369
# Create the 'prob' variable to contain the problem data
6470
prob = LpProblem("The Whiskas Problem", LpMinimize)
71+
# END define_prob
6572

73+
# BEGIN ingredient_vars
6674
# A dictionary called 'ingredient_vars' is created to contain the referenced Variables
6775
ingredient_vars = LpVariable.dicts("Ingr", Ingredients, 0)
76+
# END ingredient_vars
6877

78+
# BEGIN obj_function
6979
# The objective function is added to 'prob' first
7080
prob += (
7181
lpSum([costs[i] * ingredient_vars[i] for i in Ingredients]),
7282
"Total Cost of Ingredients per can",
7383
)
84+
# END obj_function
7485

86+
# BEGIN constraints
7587
# The five constraints are added to 'prob'
7688
prob += lpSum([ingredient_vars[i] for i in Ingredients]) == 100, "PercentagesSum"
7789
prob += (
@@ -90,6 +102,7 @@
90102
lpSum([saltPercent[i] * ingredient_vars[i] for i in Ingredients]) <= 0.4,
91103
"SaltRequirement",
92104
)
105+
# END constraints
93106

94107
# The problem data is written to an .lp file
95108
prob.writeLP("WhiskasModel2.lp")

examples/wedding.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,30 +25,40 @@ def happiness(
2525
return abs(ord(table[0]) - ord(table[-1]))
2626

2727

28+
# BEGIN possible_tables
2829
# create list of all possible tables
2930
possible_tables = [tuple(c) for c in pulp.allcombinations(guests, max_table_size)]
31+
# END possible_tables
3032

33+
# BEGIN define_x
3134
# create a binary variable to state that a table setting is used
3235
x = pulp.LpVariable.dicts(
3336
"table", possible_tables, lowBound=0, upBound=1, cat=pulp.LpInteger
3437
)
38+
# END define_x
3539

40+
# BEGIN class_and_obj_fn
3641
seating_model = pulp.LpProblem("Wedding Seating Model", pulp.LpMinimize)
3742

3843
seating_model += pulp.lpSum([happiness(table) * x[table] for table in possible_tables])
44+
# END class_and_obj_fn
3945

46+
# BEGIN total_table_constraint
4047
# specify the maximum number of tables
4148
seating_model += (
4249
pulp.lpSum([x[table] for table in possible_tables]) <= max_tables,
4350
"Maximum_number_of_tables",
4451
)
52+
# END total_table_constraint
4553

54+
# BEGIN exactly_one_table_constraint
4655
# A guest must seated at one and only one table
4756
for guest in guests:
4857
seating_model += (
4958
pulp.lpSum([x[table] for table in possible_tables if guest in table]) == 1,
5059
f"Must_seat_{guest}",
5160
)
61+
# END exactly_one_table_constraint
5262

5363
seating_model.solve()
5464

0 commit comments

Comments
 (0)