diff --git a/examples/cp/basic/facility.py b/examples/cp/basic/facility.py
index aa40281..2c22689 100644
--- a/examples/cp/basic/facility.py
+++ b/examples/cp/basic/facility.py
@@ -57,8 +57,8 @@
open = integer_var_list(nbLocations, 0, 1, "open")
for s in supplier:
- mdl.add(element(s, open) == 1)
-
+ mdl.add(open[s] == 1)
+
for j in range(nbLocations):
mdl.add(count(supplier, j) <= capacity[j])
diff --git a/examples/cp/basic/golomb_ruler.py b/examples/cp/basic/golomb_ruler.py
index 4163d76..31ec883 100644
--- a/examples/cp/basic/golomb_ruler.py
+++ b/examples/cp/basic/golomb_ruler.py
@@ -21,28 +21,28 @@
from sys import stdout
# Set model parameters
-ORDER = 8 # Number of marks
-MAX_LENGTH = 50 # Max rule length
+ORDER = 8 # Number of marks
+MAX_LENGTH = (ORDER - 1) ** 2 # Max rule length
# Create model
mdl = CpoModel()
-# Create array of variables corresponding to rule marks
-marks = integer_var_list(ORDER, 1, MAX_LENGTH, "M")
-marks[0].set_domain((0,))
-
-# Add marks ordering constraints
-for i in range(1, ORDER):
- mdl.add(marks[i] > marks[i - 1])
+# Create array of variables corresponding to position rule marks
+marks = integer_var_list(ORDER, 0, MAX_LENGTH, "M")
# Create marks distances that should be all different
dist = [marks[i] - marks[j] for i in range(1, ORDER) for j in range(0, i)]
mdl.add(all_diff(dist))
-# Avoid symmetry
-mdl.add(dist[len(dist) - 1] > dist[0])
+# Avoid symmetric solutions by ordering marks
+mdl.add(marks[0] == 0)
+for i in range(1, ORDER):
+ mdl.add(marks[i] > marks[i - 1])
+
+# Avoid mirror solution
+mdl.add((marks[1] - marks[0]) < (marks[ORDER - 1] - marks[ORDER - 2]))
-# Add optimization
+# Minimize ruler size (position of the last mark)
mdl.add(minimize(marks[ORDER - 1]))
# Solve model
@@ -52,10 +52,10 @@
# Print solution
if msol:
stdout.write("Solution: " + msol.get_solve_status() + "\n")
- stdout.write("Position of rule marks: ")
+ stdout.write("Position of ruler marks: ")
for v in marks:
stdout.write(" " + str(msol[v]))
stdout.write("\n")
- stdout.write("Solve time: " + str(msol.get_solve_time()) + "\n")
+ stdout.write("Solve time: " + str(round(msol.get_solve_time(), 2)) + "s\n")
else:
stdout.write("Search status: " + msol.get_solve_status() + "\n")
diff --git a/examples/cp/basic/house_building.py b/examples/cp/basic/house_building.py
index 0c86d08..63c2ba1 100644
--- a/examples/cp/basic/house_building.py
+++ b/examples/cp/basic/house_building.py
@@ -123,7 +123,7 @@ def find_skills(worker, task):
v = (0, PLANNING[1])
tasks[(house, task)] = interval_var(v, v, size=task.duration, name="house {} task {}".format(house, task))
for task in SKILLS:
- wtasks[(house, task)] = interval_var(present=False, name="house {} skill {}".format(house, task))
+ wtasks[(house, task)] = interval_var(optional=True, name="house {} skill {}".format(house, task))
# Maximization objective of the model
obj2 = sum([s.level * presence_of(wtasks[(h, s)]) for s in SKILLS for h in HOUSES])
@@ -173,3 +173,4 @@ def find_skills(worker, task):
print("\nList of tasks in increasing start order:")
for tsk in ltasks:
print("From " + str(tsk[2]) + " to " + str(tsk[3]) + ", " + tsk[1].name + " in house " + str(tsk[0]))
+
diff --git a/examples/cp/basic/linear_peg_solitaire.py b/examples/cp/basic/linear_peg_solitaire.py
index f41aea2..43c4a84 100644
--- a/examples/cp/basic/linear_peg_solitaire.py
+++ b/examples/cp/basic/linear_peg_solitaire.py
@@ -81,18 +81,18 @@
fromState = states[m]
toState = states[m + 1]
# Constrain location of holes
- mdl.add(element(tvar, fromState) == HOLE)
+ mdl.add(fromState[tvar] == HOLE)
# Constrain move size and direction
delta = tvar - fvar
mdl.add(allowed_assignments(delta, [-2, -1, 1, 2]))
- peg = element(fvar, fromState)
+ peg = fromState[fvar]
mdl.add( ((peg == RED) & (delta > 0)) | ((peg == BLUE) & (delta < 0)) )
# Make moves
- mdl.add(element(tvar, toState) == element(fvar, fromState))
- mdl.add(element(fvar, toState) == HOLE)
+ mdl.add(toState[tvar] == fromState[fvar])
+ mdl.add(toState[fvar] == HOLE)
# Force equality of other positions
for p in range(SIZE):
- mdl.add(if_then((p != fvar) & (p != tvar), element(p, fromState) == element(p, toState)))
+ mdl.add(if_then((p != fvar) & (p != tvar), fromState[p] == toState[p]))
# Set initial position
for p in range(NB_PEGS):
diff --git a/examples/cp/basic/truck_fleet.py b/examples/cp/basic/truck_fleet.py
index 8d72c40..7b89a68 100644
--- a/examples/cp/basic/truck_fleet.py
+++ b/examples/cp/basic/truck_fleet.py
@@ -90,11 +90,11 @@
(2, 4, 5, 6))
for j in range(0, nbOrders):
configOfContainer = integer_var(allowedContainerConfigs[colors[j]])
- mdl.add(configOfContainer == element(where[j], truckConfigs))
+ mdl.add(configOfContainer == truckConfigs[where[j]])
# Only one customer per truck
for j in range(0, nbOrders):
- mdl.add(element(where[j], customerOfTruck) == customerOfOrder[j])
+ mdl.add(customerOfTruck[where[j]] == customerOfOrder[j])
# Non-used trucks are at the end
for j in range(1, nbTrucks):
diff --git a/examples/cp/visu/_utils_visu.py b/examples/cp/visu/_utils_visu.py
index ea63ac4..d444c5d 100644
--- a/examples/cp/visu/_utils_visu.py
+++ b/examples/cp/visu/_utils_visu.py
@@ -285,12 +285,18 @@ def display(self, axes):
axes.set_yticklabels(names)
for seq in self.sequences:
for itv in seq.intervals:
- if 0 <= itv.color:
+ if isinstance(itv.color, int):
+ if 0 <= itv.color:
+ axes.plot([itv.start, itv.end], [n - seq.get_position(), n - seq.get_position()], marker='|',
+ markeredgecolor=(0.52, 0.52, 0.52), markersize=iw, linestyle='')
+ axes.hlines(n - seq.get_position(), itv.start, itv.end, colors=tl.get_color(itv.color), lw=iw)
+ else:
+ axes.hlines(n - seq.get_position(), itv.start, itv.end, colors=tl.get_color(itv.color), lw=tw)
+ else:
axes.plot([itv.start, itv.end], [n - seq.get_position(), n - seq.get_position()], marker='|',
- markeredgecolor=(0.52, 0.52, 0.52), markersize=iw, linestyle='')
+ markeredgecolor=(0.52, 0.52, 0.52), markersize=iw, linestyle='')
axes.hlines(n - seq.get_position(), itv.start, itv.end, colors=tl.get_color(itv.color), lw=iw)
- else:
- axes.hlines(n - seq.get_position(), itv.start, itv.end, colors=tl.get_color(itv.color), lw=tw)
+
if itv.name is not None:
axes.text(float(itv.start + itv.end) / 2, n - seq.get_position(), itv.name,
horizontalalignment='center', verticalalignment='center')
diff --git a/examples/cp/visu/sched_RCPSP.py b/examples/cp/visu/sched_RCPSP.py
index 391b69b..04d6ce4 100644
--- a/examples/cp/visu/sched_RCPSP.py
+++ b/examples/cp/visu/sched_RCPSP.py
@@ -62,7 +62,7 @@
mdl = CpoModel()
tasks = [interval_var(name='T' + str(i + 1), size=durations[i]) for i in range(nbTasks)]
-resources = [pulse(0, 0, 0) for j in range(nbResources)]
+resources = [None for j in range(nbResources)]
# Add precedence constraints
for i in range(nbTasks):
@@ -73,7 +73,10 @@
for i in range(nbTasks):
for j in range(nbResources):
if 0 < demands[i][j]:
- resources[j] += pulse(tasks[i], demands[i][j])
+ if resources[j] is None:
+ resources[j] = pulse(tasks[i], demands[i][j])
+ else:
+ resources[j] += pulse(tasks[i], demands[i][j])
for j in range(nbResources):
# mdl.add(resources[j]<=capacities[j])
mdl.add(always_in(resources[j], INTERVAL_MIN, INTERVAL_MAX, 0, capacities[j]))
diff --git a/examples/cp/visu/sched_RCPSPMM.py b/examples/cp/visu/sched_RCPSPMM.py
index 6d18107..6fd4eae 100644
--- a/examples/cp/visu/sched_RCPSPMM.py
+++ b/examples/cp/visu/sched_RCPSPMM.py
@@ -148,7 +148,7 @@ def demand_non_renewable(self):
tasks = {tasks_data[i]: interval_var(name=tasks_data[i].name) for i in range(nb_tasks)}
modes = {modes_data[i]: interval_var(name=modes_data[i].name,
- present=False,
+ optional=True,
size=modes_data[i].duration) for i in range(len(modes_data))}
renewables = [pulse(0, 0, 0) for j in range(nb_renewable)]
diff --git a/examples/cp/visu/sched_job_shop_flex.py b/examples/cp/visu/sched_job_shop_flex.py
index 21a1977..0322610 100644
--- a/examples/cp/visu/sched_job_shop_flex.py
+++ b/examples/cp/visu/sched_job_shop_flex.py
@@ -65,7 +65,7 @@
OPS = {o[0]: interval_var(name="O" + str(o[0])) for o in ops}
MODES = {m[0]: interval_var(name="O" + str(m[1]) + "-M" + str(m[0]),
- present=False,
+ optional=True,
size=m[4]) for m in modes}
MACHS = [[] for j in range(nb_mchs)]
ALTS = {o[0]: [] for o in ops}
diff --git a/examples/cp/visu/sched_optional.py b/examples/cp/visu/sched_optional.py
index 53a6a08..f9f47b2 100644
--- a/examples/cp/visu/sched_optional.py
+++ b/examples/cp/visu/sched_optional.py
@@ -106,7 +106,7 @@ def make_house(loc, deadline):
allocs = []
for w in range(nbWorkers):
if 0 < t.skills[w]:
- wt = interval_var(present=False, name='H' + str(loc) + '-' + t.name + '(' + workerNames[w] + ')')
+ wt = interval_var(optional=True, name='H' + str(loc) + '-' + t.name + '(' + workerNames[w] + ')')
worker_tasks[w].append(wt)
allocs.append(wt)
total_skill += (t.skills[w] * presence_of(wt))
diff --git a/examples/cp/visu/sched_setup.py b/examples/cp/visu/sched_setup.py
index b89ae9e..7aa360f 100644
--- a/examples/cp/visu/sched_setup.py
+++ b/examples/cp/visu/sched_setup.py
@@ -121,10 +121,10 @@
tp.append(type)
ai = interval_var(name='A' + str(i) + '_TP' + str(type))
a.append(ai)
- a1i = interval_var(name='A' + str(i) + '_M1_TP' + str(type), present=False, size=d1)
+ a1i = interval_var(name='A' + str(i) + '_M1_TP' + str(type), optional=True, size=d1)
a1.append(a1i)
id[a1i.get_name()] = i
- a2i = interval_var(name='A' + str(i) + '_M2_TP' + str(type), present=False, size=d2)
+ a2i = interval_var(name='A' + str(i) + '_M2_TP' + str(type), optional=True, size=d2)
a2.append(a2i)
id[a2i.get_name()] = i
mdl.add(alternative(ai, [a1i, a2i]))
diff --git a/examples/cp/visu/sched_tcost.py b/examples/cp/visu/sched_tcost.py
index 2e6bffe..3c5cc70 100644
--- a/examples/cp/visu/sched_tcost.py
+++ b/examples/cp/visu/sched_tcost.py
@@ -101,10 +101,10 @@
tp.append(type)
ai = interval_var(name='A' + str(i) + '_TP' + str(type), size=d)
a.append(ai)
- a1i = interval_var(name='A' + str(i) + '_M1_TP' + str(type), present=False)
+ a1i = interval_var(name='A' + str(i) + '_M1_TP' + str(type), optional=True)
a1.append(a1i)
id[a1i.get_name()] = i
- a2i = interval_var(name='A' + str(i) + '_M2_TP' + str(type), present=False)
+ a2i = interval_var(name='A' + str(i) + '_M2_TP' + str(type), optional=True)
a2.append(a2i)
id[a2i.get_name()] = i
mdl.add(alternative(ai, [a1i, a2i]))
diff --git a/examples/mp/ipython/campsite.ipynb b/examples/mp/ipython/campsite.ipynb
deleted file mode 100644
index c3c69ec..0000000
--- a/examples/mp/ipython/campsite.ipynb
+++ /dev/null
@@ -1,298 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "# Campsite"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Selecting the ideal camping site requires careful consideration of many factors such as the proximity to water, availability of firewood, and protection from the elements. Your potential camping sites are shown in the corresponding map. The map consists of 64 sites each with varying characteristics including water, wood, swamp, and mosquitoes.\n",
- "\n",
- "The quality of a site is determined by a points system. A site that contains water receives +3 points and a site that is near water receives +1 point. A site that contains wood receives +2 points and a site that is near wood receives +1 point. A site that contains swamp is -2 points and a site that is near swamp is -1 point. A site that contains mosquitoes receives -3 points and a site that is near mosquitoes is -2 points. “Near” is defined as adjacent sites including diagonals.\n",
- "\n",
- "For example, site B5 is worth 1 point (based on +3 on water, +1 near wood, -2 near mosquitoes, -1 near swamp). Note that you only count points once for each type of characteristic.\n",
- "\n",
- "\n",
- "Question: Where is the best campsite?\n",
- "\n",
- "### References\n",
- "\n",
- " * The PuzzlOr - Campsite (http://puzzlor.com/2015-06_Campsite.html) .\n"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "
\n",
- " \n",
- ""
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": false
- },
- "outputs": [],
- "source": [
- "try:\n",
- " import docplex.mp\n",
- "except:\n",
- " !pip install docplex"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "First things first, we specialize a namedtuple class to model 2D points:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": true
- },
- "outputs": [],
- "source": [
- "from collections import namedtuple\n",
- "\n",
- "class Point(namedtuple(\"TPoint\", [\"x\", \"y\"])):\n",
- " def __str__(self):\n",
- " return \"P<%g,%g>\" % (self.x, self.y)\n",
- " def coords(self):\n",
- " return (self.x, self.y)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Now we define the site of water, wood, swamp and mosquitoes present on our map "
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": true
- },
- "outputs": [],
- "source": [
- "water = [Point(x=1,y=3),Point(x=2,y=5),Point(x=3,y=5),Point(x=4,y=2),Point(x=4,y=4),Point(x=4,y=8),Point(x=5,y=2),Point(x=5,y=3),Point(x=6,y=2),Point(x=7,y=3),Point(x=7,y=5)]\n",
- "wood = [Point(x=1,y=7),Point(x=3,y=4),Point(x=5,y=5),Point(x=7,y=3),Point(x=8,y=3),Point(x=8,y=4),Point(x=8,y=8)]\n",
- "swamp = [Point(x=1,y=5),Point(x=1,y=6),Point(x=2,y=1),Point(x=3,y=5),Point(x=4,y=2),Point(x=4,y=7),Point(x=6,y=2),Point(x=7,y=4),Point(x=7,y=8),Point(x=8,y=3)]\n",
- "mosquitoes = [Point(x=1,y=3),Point(x=1,y=6),Point(x=2,y=2),Point(x=3,y=5),Point(x=3,y=8),Point(x=7,y=1),Point(x=7,y=3),Point(x=8,y=6)]"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Now lets define a function that calculate the distance between two points"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": true
- },
- "outputs": [],
- "source": [
- "import math\n",
- "\n",
- "def euclidean_distance(p1, p2):\n",
- " dx = p1.x - p2.x\n",
- " dy = p1.y - p2.y\n",
- " return (dx * dx + dy * dy)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Let's define a function that give us the nearest site to our point"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": true
- },
- "outputs": [],
- "source": [
- "def nearest_site(p1):\n",
- " result = {}\n",
- " for i in [water, wood, swamp, mosquitoes]:\n",
- " if i == water: vardi = 'water'\n",
- " if i == wood: vardi = 'wood'\n",
- " if i == swamp: vardi = 'swamp'\n",
- " if i == mosquitoes: vardi = 'mosquitoes'\n",
- " for j in range(len(i)):\n",
- " if j == 0: result[vardi] = i[j] \n",
- " else:\n",
- " if euclidean_distance(i[j], p1)< euclidean_distance(result[vardi], p1):\n",
- " result[vardi] = i[j]\n",
- "\n",
- " return result"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Let's now define the number of point on a defined site"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": true
- },
- "outputs": [],
- "source": [
- "def how_many_point(p1):\n",
- " point=0\n",
- " neighborhood = nearest_site(p1)\n",
- " for i in ['water', 'wood', 'swamp', 'mosquitoes']:\n",
- " if euclidean_distance(p1,neighborhood[i])==0:\n",
- " if i == 'water': point= point+ 3\n",
- " if i == 'wood': point= point+2\n",
- " if i == 'swamp':point= point-2\n",
- " if i == 'mosquitoes': point= point-3\n",
- " elif euclidean_distance(p1,neighborhood[i])<=2:\n",
- " if i == 'water': point= point+1\n",
- " if i == 'wood': point= point+1\n",
- " if i == 'swamp': point= point-1\n",
- " if i == 'mosquitoes': point= point-2\n",
- " return point"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Lets map our point on every site on a matrix "
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": true
- },
- "outputs": [],
- "source": [
- "points = [Point(x,y) for (x,y) in [(i,j) for i in range(1,8) for j in range(1,8)]]\n",
- "def generate_matrix(point):\n",
- " matrix = {p: how_many_point(p) for p in point}\n",
- " return matrix\n",
- "matrix = generate_matrix(points)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": false
- },
- "outputs": [],
- "source": [
- "from docplex.mp.environment import Environment\n",
- "env = Environment()\n",
- "env.print_information()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Let's now build the model that will choose the best site "
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": false
- },
- "outputs": [],
- "source": [
- "from docplex.mp.context import Context\n",
- "context = Context(url=\"PUT_YOUR_DOCLOUD_URL_HERE\", \n",
- " api_key=\"PUT_YOUR_DOCLOUD_KEY_HERE\")\n",
- "#context.verbose = True"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": false,
- "scrolled": true
- },
- "outputs": [],
- "source": [
- "from docplex.mp.model import Model\n",
- "\n",
- "mdl = Model(\"best_campsite\", context=context)\n",
- "\n",
- "cord_vars = mdl.binary_var_dict(points, name=\"cord\")\n",
- "\n",
- "mdl.add_constraint(mdl.sum(cord_vars[h] for h in points) == 1)\n",
- "\n",
- "mdl.maximize(mdl.sum(cord_vars[h] * matrix[h] for h in points))\n",
- "\n",
- "ok = mdl.solve()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": false
- },
- "outputs": [],
- "source": [
- "if not ok:\n",
- " print(\"Campsite model fails\")\n",
- "else:\n",
- " total_points = mdl.objective_value\n",
- " best_site = [h for h in points if float(cord_vars[h]) ==1]\n",
- " print(\"the best site is {} and its score is {}\" .format(best_site,total_points))"
- ]
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "Python 2",
- "language": "python",
- "name": "python2"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 2
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython2",
- "version": "2.7.8"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 0
-}
diff --git a/examples/mp/ipython/kmedian.ipynb b/examples/mp/ipython/kmedian.ipynb
deleted file mode 100644
index f39074b..0000000
--- a/examples/mp/ipython/kmedian.ipynb
+++ /dev/null
@@ -1,492 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "# The K-Median Problem\n",
- "\n",
- "The K_median problem deals with finding pseudo-centers in a cloud of points.\n",
- "\n",
- "For starters, let's generate a random set of points in the plane, with coordinates ranging from 1 to 100."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "First things first, we specialize a namedtuple class to model 2D points:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": true
- },
- "outputs": [],
- "source": [
- "from collections import namedtuple\n",
- "\n",
- "class Point(namedtuple(\"TPoint\", [\"x\", \"y\"])):\n",
- " def __str__(self):\n",
- " return \"P<%g,%g>\" % (self.x, self.y)\n",
- " def coords(self):\n",
- " return (self.x, self.y)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Now define the euclidean distance function between two points"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": true
- },
- "outputs": [],
- "source": [
- "import math\n",
- "def euclidean_distance(p1, p2):\n",
- " dx = p1.x - p2.x\n",
- " dy = p1.y - p2.y\n",
- " return math.sqrt(dx * dx + dy * dy)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "We will also need an auxiliary function to generate the distance matrix between points.\n",
- "For now, we'll use the Euclidean distance, but any distance in the plane will do."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": true
- },
- "outputs": [],
- "source": [
- "def generate_matrix(points):\n",
- " matrix = {(p, q): euclidean_distance(p, q) for p in points for q in points}\n",
- " return matrix"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Now, inside this set of points, we want to choose some points, say C, which have a \"centric\" situation within\n",
- "the set. More precisely, we'd like to link any point to exactly one \"pseudo-center\" in such a way that the total distance from any point to its pseudo-center is minimal.\n",
- "If C=1 this is the classical barycenter of the set of points, but for greater values of C, this can become less intuitive.\n",
- "We need a small model to generate these pseudo-centers"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Let's install CPLEX for python library"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": true
- },
- "outputs": [],
- "source": [
- "try:\n",
- " import docplex.mp\n",
- "except:\n",
- " !pip install docplex"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Enter DOcloud credentials"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": true
- },
- "outputs": [],
- "source": [
- "from docplex.mp.environment import Environment\n",
- "env = Environment()\n",
- "env.print_information()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": true
- },
- "outputs": [],
- "source": [
- "from docplex.mp.context import Context\n",
- "context = Context(url=\"ENTER YOUR URL\", \n",
- " key=\"ENTER YOUR KEY\")"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": true
- },
- "outputs": [],
- "source": [
- "from docplex.mp.model import Model\n",
- "\n",
- "def build_kmedian1(points, distance_matrix, nb_hubs=1):\n",
- " # any point is a potential hub\n",
- " hubs = points\n",
- "\n",
- " assert isinstance(distance_matrix, dict)\n",
- " BIGDIST = 999999999\n",
- " for h in hubs:\n",
- " for p in points:\n",
- " if (h, p) in distance_matrix:\n",
- " assert distance_matrix[h, p] >= 0\n",
- " if h == p:\n",
- " assert distance_matrix[h, p] == 0\n",
- " elif (p, h) in distance_matrix:\n",
- " distance_matrix[h, p] = distance_matrix[h, p]\n",
- " else:\n",
- " print(\"* forbidden arc {0} -> {1}\".format(h, p))\n",
- "\n",
- " # now build the model\n",
- " kmedian_name = \"Kmedian_p_{0}_h_{1}\".format(len(points), nb_hubs)\n",
- " mdl = Model(context=context)\n",
- " # vars\n",
- " mdl.hub_vars = mdl.binary_var_dict(hubs, name=\"is_hub\")\n",
- " mdl.link_vars = mdl.binary_var_matrix(hubs, points, name=\"link\")\n",
- " # cts\n",
- " # ct #1. each point can be linked only to an open hub\n",
- " for p in points:\n",
- " for h in hubs:\n",
- " mdl.add_constraint(mdl.link_vars[h, p] <= mdl.hub_vars[h])\n",
- "\n",
- " # zap arcs with \"infinite\" distance\n",
- " for h in hubs:\n",
- " for p in points:\n",
- " if distance_matrix[h, p] >= BIGDIST:\n",
- " mdl.add_constraint(mdl.link_vars[h, p] == 0)\n",
- " elif p == h:\n",
- " mdl.add_constraint(mdl.link_vars[h, p] == 0)\n",
- "\n",
- "\n",
- " # ct #2 each non hub point is linked to exactly one hub.\n",
- " for p in points:\n",
- " mdl.add_constraint(mdl.sum(mdl.link_vars[h, p] for h in hubs) == 1 - mdl.hub_vars[p])\n",
- "\n",
- " # ct #3 total nb of open hubs\n",
- " mdl.add_constraint(mdl.sum(mdl.hub_vars[h] for h in hubs) == nb_hubs)\n",
- "\n",
- " # minimize total distance from points to hubs\n",
- " def get_dist(h, p):\n",
- " return distance_matrix.get((h, p), BIGDIST)\n",
- "\n",
- " total_distance = mdl.sum(mdl.link_vars[h, p] * get_dist(h, p) for h in hubs for p in points)\n",
- " mdl.minimize(total_distance)\n",
- " \n",
- " return mdl"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": true
- },
- "outputs": [],
- "source": [
- "def solve_kmedian1(points, distance_matrix, nb_hubs=1):\n",
- " mdl = build_kmedian1(points, distance_matrix, nb_hubs)\n",
- " # any point is a potential hub\n",
- " hubs = points\n",
- "\n",
- " ok = mdl.solve()\n",
- " if not ok:\n",
- " print(\"Kmedian model fails\")\n",
- " return None\n",
- " else:\n",
- " total_distance = mdl.objective_value\n",
- " open_hubs = [h for h in hubs if float(mdl.hub_vars[h]) >= 0.9]\n",
- " edges = [(p, h) for p in points for h in hubs if float(mdl.link_vars[p, h]) >= 0.9]\n",
- " # returning a tuple\n",
- " print(\"total distance=%g\" % total_distance)\n",
- " print(\"#hubs={0}\".format(len(open_hubs)))\n",
- " for h in open_hubs:\n",
- " # h is an index\n",
- " print(\"--hub: {0!s}\".format(h))\n",
- "\n",
- " res = total_distance, open_hubs, edges\n",
- " return res"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Now let's try the algorithm on a simple topology with three points as a triangle, and one point at the center of gravity of the triangle. Of course, we expect the hub to be at the center of gravity of the triangle."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": true
- },
- "outputs": [],
- "source": [
- "simple_points = [ Point(0,0), Point(10,0), Point(5, 10), Point(5,5)]\n",
- "simple_matrix = {(p, q): euclidean_distance(p, q) for p in simple_points for q in simple_points}\n",
- "\n",
- "simple_res = solve_kmedian1(points=simple_points, distance_matrix=simple_matrix, nb_hubs=1)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Define a function to plot the result, that is the initial points, the hubs found by the algorithms, and the edges."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": true
- },
- "outputs": [],
- "source": [
- "if env.has_matplotlib:\n",
- " import matplotlib.pyplot as plt\n",
- " %matplotlib inline\n",
- "\n",
- "def plot_kmedian(points, hubs, edges):\n",
- " if env.has_matplotlib:\n",
- " plt.cla()\n",
- " pxs = [p.x for p in points]\n",
- " pys = [p.y for p in points]\n",
- " plt.scatter(pxs, pys, c=\"yellow\", marker='o')\n",
- " hxs = [h.x for h in hubs]\n",
- " hys = [h.y for h in hubs]\n",
- " plt.scatter(hxs, hys, c=\"blue\", marker='v', s=100)\n",
- "\n",
- " edge_xs = []\n",
- " edge_ys = []\n",
- " for (pe, he) in edges:\n",
- " edge_xs.append(pe.x)\n",
- " edge_xs.append(he.x)\n",
- " edge_xs.append(None)\n",
- " edge_ys.append(pe.y)\n",
- " edge_ys.append(he.y)\n",
- " edge_ys.append(None)\n",
- " plt.plot(edge_xs, edge_ys)\n",
- " plt.show()\n",
- " else:\n",
- " print(\"warning: no display\")"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Let's try to plot the result of the simple topology:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": true
- },
- "outputs": [],
- "source": [
- "(simple_distance, simple_hubs, simple_edges) = simple_res\n",
- "print(\"hubs=%s\" % str(simple_hubs))\n",
- "print(\"simple=%s\" % simple_points)\n",
- "print(\"edges=%s\" % str(simple_edges))\n",
- "\n",
- "plot_kmedian(simple_points, simple_hubs, simple_edges)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "The hub is indeed at the center of gravity. \n",
- "Now let's try with a random set of points set in the upper right quadrant [0..100]"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": true
- },
- "outputs": [],
- "source": [
- "import random\n",
- "\n",
- "def generate_random_points(nb_points, grid_size=100):\n",
- " xs = [random.randint(0, grid_size) for _ in range(nb_points)]\n",
- " ys = [random.randint(0, grid_size) for _ in range(nb_points)]\n",
- " return set([Point(x, y) for (x, y) in zip(xs, ys)])\n",
- "\n",
- "rand_points = generate_random_points(nb_points=100, grid_size=100)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": true
- },
- "outputs": [],
- "source": [
- "# now generate the matrix\n",
- "rand_matrix = generate_matrix(rand_points)\n",
- "\n",
- "# and solve the kmedian problem looking for two centers this time\n",
- "(rand_distance, rand_hubs, rand_edges) = solve_kmedian1(rand_points, rand_matrix, nb_hubs=3)\n",
- "plot_kmedian(rand_points, rand_hubs, rand_edges)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Electrifying Example"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "A new city is being built which will include 20 distinct neighborhoods as shown by the house icons in the map. As part of the planning process, electricity needs to be connected to each of the neighborhoods.\n",
- "\n",
- "The city has been allocated funds to put in 3 electrical substations to service the electrical needs of the neighborhoods. The substations are represented by the 3 electrical box icons to the right of the map. Because laying electrical line to each neighborhood is expensive, the placement of the substations on the map requires careful consideration. \n",
- "\n",
- "A neighborhood will be serviced by the nearest electrical substation. A neighborhood may only be connected to one substation. The substations may be placed in any cell (including the same cell as an existing neighborhood). The cost of electrical wiring is 1M dolar per km. Distances are measured using a direct line between cells, which are each 1km apart. For example, the distance between cell A1 and B2 is 1.41km.\n",
- "\n",
- "\n",
- "Question: What is the minimum cost required to connect all neighborhoods to electricity?\n",
- "\n",
- "### References\n",
- "\n",
- " * The PuzzlOr - Electrifying (http://puzzlor.com/2014-12_Electrifying.html) .\n"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "\n",
- " \n",
- ""
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Let's begin with defining the points of our map that we want to connect"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": true
- },
- "outputs": [],
- "source": [
- "rand_points = set([Point(x=2,y=2),Point(x=2,y=5),Point(x=3,y=6),Point(x=3,y=8),Point(x=4,y=2),Point(x=4,y=10),Point(x=5,y=2),Point(x=5,y=3),Point(x=5,y=6),Point(x=5,y=8),Point(x=6,y=1),Point(x=6,y=5),Point(x=7,y=2),Point(x=7,y=8),Point(x=8,y=5),Point(x=8,y=7),Point(x=8,y=8),Point(x=9,y=4),Point(x=9,y=7),Point(x=10,y=3)])"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Let's now resolve the problem"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": true
- },
- "outputs": [],
- "source": [
- "# now generate the matrix\n",
- "rand_matrix = generate_matrix(rand_points)\n",
- "\n",
- "# and solve the kmedian problem looking for tree centers this time\n",
- "(rand_distance, rand_hubs, rand_edges) = solve_kmedian1(rand_points, rand_matrix, nb_hubs=3)\n",
- "plot_kmedian(rand_points, rand_hubs, rand_edges)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": []
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": true
- },
- "outputs": [],
- "source": []
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": true
- },
- "outputs": [],
- "source": []
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "Python 2",
- "language": "python",
- "name": "python2"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 2
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython2",
- "version": "2.7.8"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 0
-}
diff --git a/examples/mp/ipython/matchmaker.ipynb b/examples/mp/ipython/matchmaker.ipynb
deleted file mode 100644
index ef8d7d0..0000000
--- a/examples/mp/ipython/matchmaker.ipynb
+++ /dev/null
@@ -1,254 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": []
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "# Matchmaker"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "The Matchmaker was an honored profession in past cultures, serving the valuable purpose of pairing off men and women in hopes of a long and successful relationship. The Matchmaker would carefully consider the characteristics of each partner to determine which pairs would be compatible.\n",
- "\n",
- "Figure 1 shows six men and six women each with varying hair color and eye color. A man or woman will only accept a partner that has at least one of these traits in common. For example, Man A and Woman 5 would make a matching pair because they have at least one trait in common (same hair color). However Man A and Woman 1 would not make a matching pair because they do not have any traits in common.\n",
- "\n",
- "Question: What pairings of men and women allow for everyone to have a partner with at least one trait in common? (There are several correct answers.)\n",
- "\n",
- "### References\n",
- "\n",
- " * The PuzzlOr - Matchmaker (http://puzzlor.com/2011-06_Matchmaker.html) ."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "\n",
- " \n",
- ""
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "We begin with importing the library we going to use"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": false
- },
- "outputs": [],
- "source": [
- "try:\n",
- " import docplex.mp\n",
- "except:\n",
- " !pip install docplex"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": true
- },
- "outputs": [],
- "source": [
- "from docplex.mp.model import Model"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Let's define now our man's and women's characteristics"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": true
- },
- "outputs": [],
- "source": [
- "color_hair = ['green','red','black'] # all the color of hair\n",
- "color_eyes= ['green','bleu','red'] # all the color of eyes\n",
- "\n",
- "man = {'A':[2,1],'B':[1,2],'C':[1,3],'D':[2,1],'E':[3,1],'F':[1,2]}\n",
- "female = {'1':[1,3],'2':[2,1],'3':[3,2],'4':[3,3],'5':[2,3],'6':[3,3]}"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Let's define now some useful variable for our model"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": true
- },
- "outputs": [],
- "source": [
- "#matrix of all the combinaison between a man and women\n",
- "Comb = [['A1','A2','A3','A4','A5','A6'],\n",
- " ['B1','B2','B3','B4','B5','B6'],\n",
- " ['C1','C2','C3','C4','C5','C6'],\n",
- " ['D1','D2','D3','D4','D5','D6'],\n",
- " ['E1','E2','E3','E4','E5','E6'],\n",
- " ['F1','F2','F3','F4','F5','F6']]\n",
- "\n",
- "#transpose the matrix below\n",
- "tComb = [[Comb[j][i] for j in range(0, 6)] for i in range(0, 6)]\n",
- "\n",
- "#Matrix comb in other form\n",
- "Comb2 = ['A1','A2','A3','A4','A5','A6',\n",
- " 'B1','B2','B3','B4','B5','B6',\n",
- " 'C1','C2','C3','C4','C5','C6',\n",
- " 'D1','D2','D3','D4','D5','D6',\n",
- " 'E1','E2','E3','E4','E5','E6',\n",
- " 'F1','F2','F3','F4','F5','F6']\n",
- "\n",
- "#Function test_match that test if a man and women match toghether\n",
- "def test_match(a):\n",
- " m = a[0]\n",
- " f = a[1]\n",
- " return int(man[m][0]==female[f][0]) + int( (man[m][1]==female[f][1]))"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Now let's define our model and solve it"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": false
- },
- "outputs": [],
- "source": [
- "from docplex.mp.environment import Environment\n",
- "env = Environment()\n",
- "env.print_information()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": true
- },
- "outputs": [],
- "source": [
- "from docplex.mp.context import Context\n",
- "context = Context(url=\"PUT_YOUR_DOCLOUD_URL_HERE\", \n",
- " api_key=\"PUT_YOUR_DOCLOUD_KEY_HERE\")"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": false
- },
- "outputs": [],
- "source": [
- "mdl = Model(context=context)\n",
- "\n",
- "Comb_vars = mdl.binary_var_dict(Comb2, name=\"is_match\")\n",
- "\n",
- "\n",
- "#contraint : every man have one women\n",
- "for i in Comb:\n",
- " mdl.add_constraint(mdl.sum(Comb_vars[h] for h in i) <= 1)\n",
- "\n",
- "#contraint : every women to have one man\n",
- "for i in tComb:\n",
- " mdl.add_constraint(mdl.sum(Comb_vars[h] for h in i) <= 1)\n",
- " \n",
- "#contraint : a man have a women if they match\n",
- "for i in Comb:\n",
- " mdl.add_constraint(mdl.sum(Comb_vars[h] * test_match(h) for h in i) >= 1)\n",
- " \n",
- "#for h in Comb2:\n",
- "# ind1 = mdl.add_indicator(Comb_vars[h], Comb_vars[h] * test_match(h) >= 1 , active_value=1 )\n",
- "\n",
- " \n",
- "# our objective is to find the maximum couples\n",
- "mdl.maximize(mdl.sum(Comb_vars[h]* test_match(h) for h in Comb2))\n",
- "\n",
- "#mdl.print_information()\n",
- "#mdl.prettyprint()\n",
- "\n",
- "ok = mdl.solve()\n",
- "\n",
- "mdl.print_solution()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Let's add a nice print to our result"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": false
- },
- "outputs": [],
- "source": [
- "if not ok:\n",
- " print(\"Matchmaker model fails\")\n",
- "else:\n",
- " total_points = mdl.objective_value\n",
- " couples = [h for h in Comb2 if float(Comb_vars[h]) ==1]\n",
- " print(\"we need to match these couples:\")\n",
- " for i in couples:\n",
- " print(\"the man {} with the women {}\" .format(i[0],i[1]))"
- ]
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "Python 2",
- "language": "python",
- "name": "python2"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 2
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython2",
- "version": "2.7.8"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 0
-}
diff --git a/examples/mp/ipython/n_c.ipynb b/examples/mp/ipython/n_c.ipynb
deleted file mode 100644
index 054cb8e..0000000
--- a/examples/mp/ipython/n_c.ipynb
+++ /dev/null
@@ -1,362 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "# Noughts and crosses"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Given a Tic Tac Toe grid in 3 dimension that we want to fill with 14 white balls and 13 black balls.\n",
- "This N&C model consist on finding the best combinaison of the 14 white balls to have the minimum number of white straight lines.\n",
- " "
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": false
- },
- "outputs": [],
- "source": [
- "try:\n",
- " import docplex.mp\n",
- "except:\n",
- " !pip install docplex"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": true
- },
- "outputs": [],
- "source": [
- "from docplex.mp.model import Model"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Firstly we create the 49 lines and the 27 cells of our cube."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": true
- },
- "outputs": [],
- "source": [
- "r =range(1,4)\n",
- "\n",
- "cell= [(r1,r2,r3) for r1 in r for r2 in r for r3 in r]\n",
- "\n",
- "lines=[]\n",
- "\n",
- "\n",
- "for x in [(x1, y1, z1, x2, y2, z2, x3, y3, z3) for x1 in r for x2 in r for x3 in r for y1 in r for y2 in r for y3 in r for z1 in r for z2 in r for z3 in r if (x2-x1==x3-x2) if (y2-y1==y3-y2) if (z2-z1==z3-z2) if (9*x1+3*y1+z1<9*x3+3*y3+z3) ]:\n",
- " lines.append(x)\n",
- "\n",
- "assert len(lines)==49;"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": false
- },
- "outputs": [],
- "source": [
- "from docplex.mp.environment import Environment\n",
- "env = Environment()\n",
- "env.print_information()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "We now create our model and solve it"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": true
- },
- "outputs": [],
- "source": [
- "from docplex.mp.context import Context\n",
- "context = Context(url=\"PUT_YOUR_DOCLOUD_URL_HERE\", \n",
- " api_key=\"PUT_YOUR_DOCLOUD_KEY_HERE\")"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": false
- },
- "outputs": [],
- "source": [
- "mdl = Model(context=context)\n",
- "\n",
- "lines_monocolor = mdl.binary_var_dict(lines, name=\"is_monocolor\")\n",
- "\n",
- "cell_dict= mdl.binary_var_dict(cell, name=\"is_cell\")\n",
- "\n",
- "\n",
- "for l in lines:\n",
- " ind1 = mdl.add_indicator(lines_monocolor[l], cell_dict[l[0],l[1],l[2]]+ cell_dict[l[3],l[4],l[5]]+cell_dict[l[6],l[7],l[8]] >=1 , active_value=0 )\n",
- " \n",
- " ind2 = mdl.add_indicator(lines_monocolor[l], cell_dict[l[0],l[1],l[2]]+ cell_dict[l[3],l[4],l[5]]+cell_dict[l[6],l[7],l[8]] <=2 , active_value=0 )\n",
- "\n",
- "mdl.add_constraint(mdl.sum(cell_dict[h] for h in cell) == 14)\n",
- "\n",
- "\n",
- "mdl.print_information()\n",
- "\n",
- "mdl.minimize(mdl.sum(lines_monocolor[l] for l in lines))\n",
- "\n",
- "ok = mdl.solve()\n",
- "\n",
- "if not ok: \n",
- " print(\"N&C model fails\")\n",
- "else:\n",
- " mdl.print_solution()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": true
- },
- "outputs": [],
- "source": [
- "env = mdl.environment"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": false
- },
- "outputs": [],
- "source": [
- "# Print of the solution\n",
- "print (\"##############Diff print##############\")\n",
- "monocolor_lines_position = []\n",
- "for l in lines:\n",
- " monocolor_lines_position.append( int(float(lines_monocolor[l])))\n",
- "\n",
- "print(monocolor_lines_position)\n",
- "\n",
- "game_cells = []\n",
- "for l in cell:\n",
- " game_cells.append(float(cell_dict[l]))\n",
- "\n",
- "if env.has_numpy:\n",
- " import numpy as np\n",
- " game_cells = np.reshape(game_cells, (-1,3))\n",
- "else:\n",
- " ii = len(game_cells) // 3\n",
- " game_cells2 = [[0 for j in range(0, 3)] for i in range(0, ii)]\n",
- " for i in range(0, ii):\n",
- " for j in range(0, 3):\n",
- " game_cells2[i][j] = game_cells[i * 3 + j]\n",
- " game_cells = game_cells2\n",
- "\n",
- "print(game_cells)\n",
- "\n",
- "print (\"total lines :{0!s} \" .format(int(mdl.objective_value)) )"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "We represent the results on a 3d grid"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": true
- },
- "outputs": [],
- "source": [
- "monocolor_lines_points = []\n",
- "for l in lines:\n",
- " if float(lines_monocolor[l]) != 0: \n",
- " monocolor_lines_points.append( l)\n",
- "\n",
- "\n",
- "k2=5 # Space the graph\n",
- "k1=k2/2\n",
- "\n",
- "xs=[]\n",
- "ys=[]\n",
- "zs=[]\n",
- "VecStart_xs = []\n",
- "VecStart_ys = []\n",
- "VecStart_zs = []\n",
- "\n",
- "VecEnd_xs = []\n",
- "VecEnd_ys = []\n",
- "VecEnd_zs =[]\n",
- "\n",
- "for i in range(len(monocolor_lines_points)):\n",
- " xs.append(monocolor_lines_points[i][0])\n",
- " VecStart_xs.append(monocolor_lines_points[i][0])\n",
- " ys.append(monocolor_lines_points[i][1])\n",
- " VecStart_ys.append(monocolor_lines_points[i][1])\n",
- " \n",
- " if monocolor_lines_points[i][2] == 1:\n",
- " zs.append(monocolor_lines_points[i][2])\n",
- " VecStart_zs.append(monocolor_lines_points[i][2])\n",
- " elif monocolor_lines_points[i][2] == 2:\n",
- " zs.append(k1+monocolor_lines_points[i][2])\n",
- " VecStart_zs.append(k1+monocolor_lines_points[i][2])\n",
- " else :\n",
- " zs.append(k2+monocolor_lines_points[i][2])\n",
- " VecStart_zs.append(k2+monocolor_lines_points[i][2])\n",
- " \n",
- " xs.append(monocolor_lines_points[i][3])\n",
- " ys.append(monocolor_lines_points[i][4])\n",
- " \n",
- " if monocolor_lines_points[i][5] == 1:\n",
- " zs.append(monocolor_lines_points[i][5])\n",
- " elif monocolor_lines_points[i][5] == 2:\n",
- " zs.append(k1+monocolor_lines_points[i][5])\n",
- " else :\n",
- " zs.append(k2+monocolor_lines_points[i][5])\n",
- " \n",
- " \n",
- " xs.append(monocolor_lines_points[i][6])\n",
- " VecEnd_xs.append(monocolor_lines_points[i][6])\n",
- " ys.append(monocolor_lines_points[i][7])\n",
- " VecEnd_ys.append(monocolor_lines_points[i][7])\n",
- " \n",
- " if monocolor_lines_points[i][8] == 1:\n",
- " zs.append(monocolor_lines_points[i][8])\n",
- " VecEnd_zs.append(monocolor_lines_points[i][8])\n",
- " elif monocolor_lines_points[i][8] ==2:\n",
- " VecEnd_zs.append(k1+monocolor_lines_points[i][8])\n",
- " zs.append(k1+monocolor_lines_points[i][8])\n",
- " else :\n",
- " zs.append(k2+monocolor_lines_points[i][8])\n",
- " VecEnd_zs.append(k2+monocolor_lines_points[i][8])"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "You can comment the 3rd line if you want the graphic displayed in a pop-up window instead of inline"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": true
- },
- "outputs": [],
- "source": [
- "if env.has_matplotlib:\n",
- " from mpl_toolkits.mplot3d import Axes3D\n",
- " import matplotlib.pyplot as plt\n",
- " %matplotlib inline"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": false
- },
- "outputs": [],
- "source": [
- "if env.has_matplotlib:\n",
- " fig = plt.figure()\n",
- " ax = fig.add_subplot(111, projection='3d')\n",
- "\n",
- " ax.scatter(xs, ys, zs, c='r', marker='o', s=100) \n",
- "\n",
- " x1=[3,3,1.5,2.5,3,3,1.5,2.5,3,3,1.5,2.5]\n",
- " y1=[1.5,2.5,1,1,1.5,2.5,1,1,1.5,2.5,1,1]\n",
- " z1=[3+k2,3+k2,3+k2,3+k2,2+k1,2+k1,2+k1,2+k1,1,1,1,1]\n",
- "\n",
- " x2=[1,1,1.5,2.5,1,1,1.5,2.5,1,1,1.5,2.5]\n",
- " y2=[1.5,2.5,3,3,1.5,2.5,3,3,1.5,2.5,3,3]\n",
- " z2=[3+k2,3+k2,3+k2,3+k2,2+k1,2+k1,2+k1,2+k1,1,1,1,1]\n",
- "\n",
- " for i in range(len(x1)):\n",
- "\n",
- " VecStart_x = [x1[i]]\n",
- " VecStart_y = [y1[i]]\n",
- " VecStart_z = [z1[i]]\n",
- "\n",
- " VecEnd_x = [x2[i]]\n",
- " VecEnd_y = [y2[i]]\n",
- " VecEnd_z =[z2[i]]\n",
- "\n",
- " ax.plot(VecStart_x + VecEnd_x, VecStart_y + VecEnd_y, VecStart_z +VecEnd_z, c='b')\n",
- "\n",
- " for i in range(len(VecStart_xs)):\n",
- "\n",
- " VecStart_x = [VecStart_xs[i]]\n",
- " VecStart_y = [VecStart_ys[i]]\n",
- " VecStart_z = [VecStart_zs[i]]\n",
- "\n",
- " VecEnd_x = [VecEnd_xs[i]]\n",
- " VecEnd_y = [VecEnd_ys[i]]\n",
- " VecEnd_z =[VecEnd_zs[i]]\n",
- "\n",
- " ax.plot(VecStart_x + VecEnd_x, VecStart_y + VecEnd_y, VecStart_z +VecEnd_z, c='r')\n",
- "\n",
- "\n",
- " ax.set_xlabel('X Label')\n",
- " ax.set_ylabel('Y Label')\n",
- " ax.set_zlabel('Z Label')\n",
- "\n",
- " plt.show()"
- ]
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "Python 2",
- "language": "python",
- "name": "python2"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 2
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython2",
- "version": "2.7.8"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 0
-}
diff --git a/examples/mp/ipython/warehouse.ipynb b/examples/mp/ipython/warehouse.ipynb
deleted file mode 100644
index e4e76f4..0000000
--- a/examples/mp/ipython/warehouse.ipynb
+++ /dev/null
@@ -1,606 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "# The Warehouse Problem\n",
- "\n",
- "The Warehouse Problem is a well-know optimization case found in many textbooks.\n",
- "The problem consists, given a set of candidate warehouse *locations* and a set of *stores*\n",
- "to decide which warehouse to open and which warehouse will server which store.\n",
- "\n",
- "## Input Data\n",
- "\n",
- "Data is provided as follows:\n",
- "\n",
- "* For each warehouse, we require a tuple (name, capacity, fixed-cost), where _name_ is the unique name of the warehouse, _capacity_ is the maximum number of stores it can supply and _fixed_cost_ is the cost incurred by opening the warehouse.\n",
- "* For each couple (warehouse, store) a _supply_cost_ which estimates the cost of supplying this store by this warehouse.\n",
- "\n",
- "A compact way of representing the data is a Python _dictionary_: for each warehouse tuple, list all supply costs for all stores.\n",
- "\n",
- "## Business Decisions\n",
- "\n",
- "The problem consists in deciding which warehouse will be open and for each store, by which warehouse it will be supplied.\n",
- "\n",
- "## Business Constraints\n",
- "\n",
- "Decisions must satisfy the following (simplified) business constraints:\n",
- "\n",
- "1. Unicity: each store is supplied by one unique warehouse\n",
- "2. A store can only be supplied by an _open_ warehouse\n",
- "3. Capacity: the number of stores supplied by a warehouse must be less than its capacity\n",
- "\n",
- "## Business Objective\n",
- "\n",
- "The goal is to minimize the total cost incurred by the decisions, which is made of two costs:\n",
- "\n",
- "* The *Total Opening Cost* is the sum of opening costs ranging over all open warehouses.\n",
- "* The *Total Supply Cost* is the sum of supply costs for all the chosen (warehouse, store) pairs\n"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "To begin with, let's define a small warehouse dataset:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "WAREHOUSES = {\n",
- " ('Bonn', 1, 20): [20, 28, 74, 2, 46, 42, 1, 10, 93, 47],\n",
- " ('Bordeaux', 4, 35): [24, 27, 97, 55, 96, 22, 5, 73, 35, 65],\n",
- " ('London', 2, 32): [11, 82, 71, 73, 59, 29, 73, 13, 63, 55],\n",
- " ('Paris', 1, 37): [25, 83, 96, 69, 83, 67, 59, 43, 85, 71],\n",
- " ('Rome', 2, 25): [30, 74, 70, 61, 54, 59, 56, 96, 46, 95],\n",
- " ('Brussels', 2, 28): [30, 74, 70, 61, 34, 59, 56, 96, 46, 95]\n",
- "}\n",
- "\n",
- "print(WAREHOUSES)\n",
- "print(\"#candidate warehouses=%d\" % len(WAREHOUSES))\n",
- "print(\"#stores to supply=%d\" % len(list(WAREHOUSES.values())[0]))"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Each key in the dictionary is a tuple with three fields: the name (a string), the capacity (a positive integer) and the fixed cost (also positive).\n",
- "Each value is a list of length 10 (the number of stores to supply). the k^th element of the list being the supply cost from the warehouse to the k^th store. For example,\n",
- "\n",
- "* London warehouse can supply at most two stores\n",
- "* London warehouse has an opening cost of 30\n",
- "* The cost incurred by supplying the first store from London is 11"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## The math model"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Solving with DOcplex"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Now let's solve this problem with DOcplex.\n",
- "First, we define named tuples to store information relative to warehouses and stores in a clear and convenient manner."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "from collections import namedtuple\n",
- "\n",
- "class TWareHouse(namedtuple(\"TWarehouse1\", [\"id\", \"capacity\", \"fixed_cost\"])):\n",
- " def __str__(self):\n",
- " return self.id\n",
- "\n",
- "class TStore(namedtuple(\"TStore1\", [\"id\"])):\n",
- " def __str__(self):\n",
- " return 'store_%d' % self.id"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Second, we compute the set of warehouse and store tuples. The use of *itervalues* from the *six* module is a technicality to ensure portability across Python 2 and Python3.\n",
- "We chose to model stores by an integer range from 1 to nb_stores for convenience."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "from six import itervalues\n",
- "\n",
- "nb_stores = 0 if not WAREHOUSES else len(next(itervalues(WAREHOUSES)))\n",
- "warehouses = [TWareHouse(*wrow) for wrow in WAREHOUSES.keys()]\n",
- "# we'll use the warehouses dictionary as a two-entry dictionary\n",
- "supply_costs = WAREHOUSES\n",
- "# we count stores from 1 to NSTORES included for convenience\n",
- "stores = [TStore(idx) for idx in range(1, nb_stores + 1)]"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## The model\n",
- "First we need one instance of model to store our modeling artefacts"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "try:\n",
- " import docplex.mp\n",
- "except:\n",
- " !pip install docplex"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "from docplex.mp.environment import Environment\n",
- "env = Environment()\n",
- "env.print_information()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "from docplex.mp.context import Context\n",
- "context = Context(url=\"PUT_YOUR_DOCLOUD_URL_HERE\", \n",
- " api_key=\"PUT_YOUR_DOCLOUD_KEY_HERE\")"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "from docplex.mp.model import Model\n",
- "warehouse_model = Model(context=context)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Defining decision variables\n",
- "\n",
- "In DOcplex, decision variables are related to objects of the business model. In our case, we have two decisions to make: which warehouse is open and, for each store, from which warehouse is it supplied.\n",
- "\n",
- "First, we create one binary (yes/no) decision variable for each warehouse: the variable will be equal to 1 if and only if the warehouse is open.\n",
- "We use the **binary_var_dict** method of the model object to create a dictionary from warehouses to variables.\n",
- "The first argument states that keys of the dcitionary will be our warehouse objects;\n",
- "the second argument is a a simple string *'open', that is used to generate names for variables, by suffixing 'open' with the string representation of warehouse objects (in other terms the output of the **str()** Python function.\n",
- "This is the reason why we redefined the method **__str__** method of class TWarehouse in the beginning.\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "open_vars = warehouse_model.binary_var_dict(keys=warehouses, name='open')"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Second, we define one binary variable for each __pair__ or (warehouse, store). This is done using the method\n",
- "**binary_var_matrix**; in this case keys to variables are all (warehouse, store) pairs.\n",
- "\n",
- "The naming scheme applies to both components of pairs,for example the variable deciding whether the London warehouse supplies store 1 will be named supply_London_store_1."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "supply_vars = warehouse_model.binary_var_matrix(warehouses, stores, 'supply')"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "At this step, we can check how many variables we have defined. as we have 5 warehouses and 10 stores, we expect to have defined 5\\*10 + 5 = 55 variables."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "warehouse_model.print_information()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "As expected, we have not defined any constraints yet.\n",
- "\n",
- "## Defining constraints\n",
- "\n",
- "The first constraint states that each store is supplied by exactly one warehouse. In other terms, the sum of supply variables for a given store, ranging over all warehouses, must be equal to 1. \n",
- "Printing model information, we check that this code defines 10 constraints"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "for s in stores:\n",
- " warehouse_model.add_constraint(warehouse_model.sum(supply_vars[w, s] for w in warehouses) == 1)\n",
- "warehouse_model.print_information()"
- ]
- },
- {
- "cell_type": "raw",
- "metadata": {},
- "source": [
- "The second constraints states that a store can be suppplied only by an open warehouse. To model this, we use a little trick of logic, converting this logical implication (w supplies s) => w is open into an inequality between binary variables, as in:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "for s in stores:\n",
- " for w in warehouses: \n",
- " warehouse_model.add_constraint(supply_vars[w, s] <= open_vars[w])"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "whenever a supply var from warehouse __w__ equals one, its open variable will also be equal to one. Conversely, when the open variable is zero, all supply variable for this warehouse will be zero.\n",
- "This constraint does not prevent having an open warehouse supplying no stores.\n",
- "Though this could be taken care of by adding an extra constraint, this is not necessary\n",
- "as such cases will be automatically eliminated by searching for the minimal cost (see the objective section).\n"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "The third constraint states the capacity limitation on each warehouse. Note the overloading of the logical operator **<=** to express the constraint."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "for w in warehouses:\n",
- " warehouse_model.add_constraint(warehouse_model.sum(supply_vars[w, s] for s in stores) <= w.capacity)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "At this point we can summarize the variables and constraint we have defined in the model."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "warehouse_model.print_information()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Defining the Objective\n",
- "\n",
- "The objective is to minimize the total costs. There are two costs:\n",
- "\n",
- "* the opening cost which is incurred each time a warehouse is open\n",
- "* the supply cost which is incurred by the assignments of warehouses to stores\n",
- "\n",
- "We define two linear expressions to model these two costs and state that our objective is to minimize the sum."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "total_opening_cost = warehouse_model.sum(open_vars[w] * w.fixed_cost for w in warehouses)\n",
- "total_opening_cost.name = \"Total Opening Cost\"\n",
- "\n",
- "def _get_supply_cost(warehouse, store):\n",
- " ''' A nested function to return the supply costs from a warehouse and a store'''\n",
- " return supply_costs[warehouse][store.id - 1]\n",
- "\n",
- "total_supply_cost = warehouse_model.sum([supply_vars[w,s] * _get_supply_cost(w, s) for w in warehouses for s in stores])\n",
- "total_supply_cost.name = \"Total Supply Cost\"\n",
- "\n",
- "warehouse_model.minimize(total_opening_cost + total_supply_cost)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "The model is now complete and we can solve it and get the final optimal objective\n",
- "\n",
- "## Solving the model"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "ok = warehouse_model.solve()\n",
- "assert ok\n",
- "obj = warehouse_model.objective_value\n",
- "print(\"optimal objective is %g\" % obj)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "But there's more: we can precise the value of those two costs, by evaluating the value of the two expressions.\n",
- "Here we see that the opening cost is 140 and the supply cost is 293."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "opening_cost_obj = total_opening_cost.solution_value\n",
- "supply_cost_obj = total_supply_cost.solution_value\n",
- "print(\"total opening cost=%g\" % opening_cost_obj)\n",
- "print(\"total supply cost=%g\" % supply_cost_obj)\n",
- "\n",
- "# store results for later on...\n",
- "results=[]\n",
- "results.append((opening_cost_obj, supply_cost_obj))"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Displaying results\n",
- "\n",
- "We can leverage the graphic toolkit __maptplotlib__ to display the results. First, let's draw a pie chart of opening cost vs. supply costs to clearly see the respective impact of both costs."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "if env.has_matplotlib:\n",
- " import matplotlib.pyplot as plt\n",
- " %matplotlib inline\n",
- "\n",
- "def display_costs(costs, labels, colors):\n",
- " if env.has_matplotlib:\n",
- " plt.axis(\"equal\")\n",
- " plt.pie(costs, labels=labels, colors=colors, autopct=\"%1.1f%%\")\n",
- " plt.show()\n",
- " else:\n",
- " print(\"warning: no display\")\n",
- " \n",
- "display_costs(costs=[opening_cost_obj, supply_cost_obj], \n",
- " labels=[\"Opening Costs\", \"Supply Costs\"], \n",
- " colors=[\"gold\", \"lightBlue\"])"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "We can also display the breakdown of supply costs per warehouse.\n",
- "First, compute the sum of supply cost values for each warehouse: we need to sum the actual supply cost from w to s\n",
- "for those pairs (w, s) whose variable is true (here we test the value to be >=0.9, testing equality to 1 would not\n",
- "be robust because of numerical precision issues)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "supply_cost_by_warehouse = [ sum(_get_supply_cost(w,s) for s in stores if supply_vars[w,s].solution_value >= 0.9) for w in warehouses]\n",
- "wh_labels = [w.id for w in warehouses]\n",
- "display_costs(supply_cost_by_warehouse, wh_labels, None)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Exploring the Pareto frontier\n",
- "\n",
- "remember we solved the model by minimizing the __sum__ of the two costs, getting an optimal solution\n",
- "with total cost of 383, with opening costs = 120 and supply costs = 263.\n",
- "\n",
- "In some cases, it might be intersting to wonder what is the absolute minimum of any of these two costs: what\n",
- "if we'd like to minimize opening costs __first__, and __then__ the supply cost (of course keeping the best opeing cost we obtained in first phase.\n",
- "\n",
- "This is very easy to achieve with DOcplex, using the \"solve\\_lexicographic\" method on the model.\n",
- "This method takes two arguments: the first one is a list of expressions, the second one a list of\n",
- "__senses__ (Minimize or Maximize), the default being to Minimize each expression.\n",
- "\n",
- "In this section we will explore hat happens when we minimize opening costs __then__ supply costs and conversely\n",
- "when we minimize supply costs and __then__ opening costs.\n",
- "\n",
- "#### Minimizing total opening cost and then total supply cost"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "opening_first_then_supply = [ total_opening_cost, total_supply_cost]\n",
- "warehouse_model.solve_lexicographic(goals=opening_first_then_supply)\n",
- "\n",
- "opening1 = total_opening_cost.solution_value\n",
- "supply1 = total_supply_cost.solution_value\n",
- "results.append( (opening1, supply1))"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "From this we can see that the model has sucessfully solved twice and that the absolute minimum of opening cost is 120,\n",
- "and for this value, the minimum supply cost is 263.\n",
- "Remember that in our first attempt we minimized the combined sum and obtained (120,263, but now we know 120 is the best we can achieve. The supply cost is now 352 , for a total combined cost of 472 which is indeed greater than the value of 433 we found when optimizing the combined sum.\n",
- "\n",
- "#### Minimizing total supply cost and then total opening cost\n",
- "\n",
- "Now let's do the reverse: minimize supply cost and then opening cost. What if the goal of minimizing total supply cost supersedes the second goal of optimizing total opening cost?\n",
- "The code is straightforward:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "supply_first_then_opening = [ total_supply_cost, total_opening_cost]\n",
- "warehouse_model.solve_lexicographic(goals=supply_first_then_opening)\n",
- "opening2 = total_opening_cost.solution_value\n",
- "supply2 = total_supply_cost.solution_value\n",
- "\n",
- "results.append( (opening2, supply2))"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Here, we obtain (152, 288), supply costs is down from 352 to 288 but opening cost raises from 120 to 150.\n",
- "We can check that the combined sum is 288+152 = 440, which is grater than the optimal of 433 we found at the beginning."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "\n",
- "### Pareto Diagram\n",
- "\n",
- "We can plot these three points on a (opening, supply) plane:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "def display_pareto(res):\n",
- " if env.has_matplotlib:\n",
- " plt.cla()\n",
- " plt.xlabel('Opening Costs')\n",
- " plt.ylabel('Supply Costs')\n",
- " colors = ['g', 'r', 'b']\n",
- " markers = ['o', '<', '>']\n",
- " nb_res = len(res)\n",
- " pts = []\n",
- " for i, r in enumerate(res):\n",
- " opening, supply = r\n",
- " p = plt.scatter(opening, supply, color=colors[i%3], s=50+10*i, marker=markers[i%3])\n",
- " pts.append(p)\n",
- " plt.legend(pts,\n",
- " ('Sum', 'Opening_first', 'Supply_first'),\n",
- " scatterpoints=1,\n",
- " loc='lower left',\n",
- " ncol=3,\n",
- " fontsize=8)\n",
- " plt.show()\n",
- " else:\n",
- " print(\"Warning: no display\")\n",
- "\n",
- "display_pareto(results)"
- ]
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "Python 2",
- "language": "python",
- "name": "python2"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 2.0
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython2",
- "version": "2.7.8"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 0
-}
diff --git a/examples/mp/modeling/nurses.py b/examples/mp/modeling/nurses.py
index 9caf272..ab48c0f 100644
--- a/examples/mp/modeling/nurses.py
+++ b/examples/mp/modeling/nurses.py
@@ -6,36 +6,37 @@
from collections import namedtuple
-from enum import Enum
-
from docplex.mp.model import Model
from docplex.mp.context import Context
+# utility to conevrt a weekday string to an index in 0..6
+_all_days = ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"]
+
-class Weekday(Enum):
- (Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday) = range(1, 8) # [1..7]
+def day_to_day_week(day):
+ day_map = {day: d for d, day in enumerate(_all_days)}
+ return day_map[day.lower()]
TWorkRules = namedtuple("TWorkRules", ["work_time_max"])
-TShift1 = namedtuple("TShift", ["department", "day", "start_time", "end_time", "min_requirement", "max_requirement"])
TVacation = namedtuple("TVacation", ["nurse", "day"])
TNursePair = namedtuple("TNursePair", ["firstNurse", "secondNurse"])
TSkillRequirement = namedtuple("TSkillRequirement", ["department", "skill", "required"])
# subclass the namedtuple to refine the str() method as the nurse's name
-class TNurse(namedtuple("TNurse1", ["name", "seniority", "qualification", "payRate"])):
+class TNurse(namedtuple("TNurse1", ["name", "seniority", "qualification", "pay_rate"])):
def __str__(self):
return self.name
# specialized namedtuple to redefine its str() method
-class TShift(TShift1):
+class TShift(namedtuple("TShift", ["department", "day", "start_time", "end_time", "min_requirement", "max_requirement"])):
def __str__(self):
# keep first two characters in department, uppercase
dept2 = self.department[0:4].upper()
# keep 3 days of weekday
- dayname = self.day.name[0:3]
+ dayname = self.day[0:3]
return '{}_{}_{:02d}'.format(dept2, dayname, self.start_time)
@@ -49,13 +50,11 @@ def to_abstime(day_index, time_of_day):
:return:
"""
- ONE_DAY = 24
- time = ONE_DAY * (day_index - 1)
+ time = 24 * (day_index - 1)
time += time_of_day
return time
def __init__(self, weekday, start_time_of_day, end_time_of_day):
- assert (isinstance(weekday, Weekday))
assert (start_time_of_day >= 0)
assert (start_time_of_day <= 24)
assert (end_time_of_day >= 0)
@@ -65,11 +64,11 @@ def __init__(self, weekday, start_time_of_day, end_time_of_day):
self._start_time_of_day = start_time_of_day
self._end_time_of_day = end_time_of_day
# conversion to absolute time.
- start_day_index = weekday.value
+ start_day_index = day_to_day_week(self._weekday)
self.start_time = self.to_abstime(start_day_index, start_time_of_day)
end_day_index = start_day_index if end_time_of_day > start_time_of_day else start_day_index + 1
self.end_time = self.to_abstime(end_day_index, end_time_of_day)
- assert (self.end_time > self.start_time)
+ assert self.end_time > self.start_time
@property
def duration(self):
@@ -83,7 +82,8 @@ def overlaps(self, other_shift):
def solve(model):
- if model.solve():
+ sol = model.solve(log_output=True)
+ if sol is not None:
print("solution for a cost of {}".format(model.objective_value))
print_information(model)
print_solution(model)
@@ -106,139 +106,163 @@ def load_data(model, *args):
model.skill_requirements = SKILL_REQUIREMENTS
# transactional data
if number_of_args >= 6:
- model.vacations = [TVacation._make(vacation_row) for vacation_row in args[5]]
+ model.vacations = [TVacation(*vacation_row) for vacation_row in args[5]]
else:
model.vacations = []
if number_of_args >= 7:
- model.nurse_associations = [TNursePair._make(npr) for npr in args[6]]
+ model.nurse_associations = [TNursePair(*npr) for npr in args[6]]
else:
model.nurse_associations = []
if number_of_args >= 8:
- model.nurse_incompatibilities = [TNursePair._make(npr) for npr in args[7]]
+ model.nurse_incompatibilities = [TNursePair(*npr) for npr in args[7]]
else:
model.nurse_incompatibilities = []
def setup_data(model):
""" compute internal data """
- all_nurses = model.nurses
- model.vacations_by_nurse = {n: [vac_day for (vac_nurse_id, vac_day) in model.vacations if vac_nurse_id == n.name]
- for n in model.nurses}
- # compute shift activities (start, end duration)
+ # compute shift activities (start, end duration) and stor ethem in a dict indexed by shifts
model.shift_activities = {s: ShiftActivity(s.day, s.start_time, s.end_time) for s in model.shifts}
- model.nurses_by_id = {n.name: n for n in all_nurses}
+ # map from nurse names to nurse tuples.
+ model.nurses_by_id = {n.name: n for n in model.nurses}
def setup_variables(model):
all_nurses, all_shifts = model.nurses, model.shifts
+ # one binary variable for each pair (nurse, shift) equal to 1 iff nurse n is assigned to shift s
model.nurse_assignment_vars = model.binary_var_matrix(all_nurses, all_shifts, 'NurseAssigned')
+ # for each nurse, allocate one variable for worktime
model.nurse_work_time_vars = model.continuous_var_dict(all_nurses, lb=0, name='NurseWorkTime')
+ # and two variables for over_average and under-average work time
model.nurse_over_average_time_vars = model.continuous_var_dict(all_nurses, lb=0,
name='NurseOverAverageWorkTime')
model.nurse_under_average_time_vars = model.continuous_var_dict(all_nurses, lb=0,
name='NurseUnderAverageWorkTime')
- model.average_nurse_work_time = model.continuous_var(lb=0, name='AverageNurseWorkTime')
+ # finally the global average wotk time
+ model.average_nurse_work_time = model.continuous_var(lb=0, name='AverageWorkTime')
def setup_constraints(model):
all_nurses = model.nurses
all_shifts = model.shifts
- nurse_assigned_vars = model.nurse_assignment_vars
- nurse_work_time_vars = model.nurse_work_time_vars
+ nurse_assigned = model.nurse_assignment_vars
+ nurse_work_time = model.nurse_work_time_vars
shift_activities = model.shift_activities
nurses_by_id = model.nurses_by_id
max_work_time = model.work_rules.work_time_max
# define average
model.add_constraint(
- len(all_nurses) * model.average_nurse_work_time == model.sum(
- model.nurse_work_time_vars[n] for n in model.nurses),
- "average")
+ len(all_nurses) * model.average_nurse_work_time == model.sum(nurse_work_time[n] for n in all_nurses), "average")
# compute nurse work time , average and under, over
for n in all_nurses:
- work_time_var = nurse_work_time_vars[n]
+ work_time_var = nurse_work_time[n]
+ model.add_constraint(
+ work_time_var == model.sum(nurse_assigned[n, s] * shift_activities[s].duration for s in all_shifts),
+ "work_time_{0!s}".format(n))
+
+ # relate over/under average worktime variables to the worktime variables
+ # the trick here is that variables have zero lower bound
+ # however, thse variables are not completely defined by this constraint,
+ # only their difference is.
+ # if these variables are part of the objective, CPLEX wil naturally minimize their value,
+ # as expected
model.add_constraint(
- work_time_var == model.sum(nurse_assigned_vars[n, s] * shift_activities[s].duration for s in model.shifts),
- "work_time_%s" % str(n))
- model.add_constraint(work_time_var == model.average_nurse_work_time + model.nurse_over_average_time_vars[n] -
- model.nurse_under_average_time_vars[n], "averag_work_time_%s" % str(n))
+ work_time_var == model.average_nurse_work_time
+ + model.nurse_over_average_time_vars[n]
+ - model.nurse_under_average_time_vars[n],
+ "average_work_time_{0!s}".format(n))
- model.add_constraint(work_time_var <= max_work_time, "max_time_%s" % str(n))
+ # state the maximum work time as a constraint, so that is can be relaxed,
+ # should the problem become infeasible.
+ model.add_constraint(work_time_var <= max_work_time, "max_time_{0!s}".format(n))
# vacations
- for n in all_nurses:
- for vac_day in model.vacations_by_nurse[n]:
- for shift in (s for s in all_shifts if s.day == vac_day):
- model.add_constraint(nurse_assigned_vars[n, shift] == 0,
- "medium_vacations_%s_%s_%s" % (str(n), vac_day, str(shift)))
+ for vac_nurse_id, vac_day in model.vacations:
+ vac_n = nurses_by_id[vac_nurse_id]
+ for shift in (s for s in all_shifts if s.day == vac_day):
+ model.add_constraint(nurse_assigned[vac_n, shift] == 0,
+ "medium_vacations_{0!s}_{1!s}_{2!s}".format(vac_n, vac_day, shift))
# a nurse cannot be assigned overlapping shifts
+ # post only one constraint per couple(s1, s2)
model.number_of_overlaps = 0
- for s1 in all_shifts:
- for s2 in all_shifts:
- if s1 != s2 and shift_activities[s1].overlaps(shift_activities[s2]):
+ nb_shifts = len(all_shifts)
+ for i1 in range(nb_shifts):
+ for i2 in range(i1 + 1, nb_shifts):
+ s1 = all_shifts[i1]
+ s2 = all_shifts[i2]
+ if shift_activities[s1].overlaps(shift_activities[s2]):
model.number_of_overlaps += 1
for n in all_nurses:
- model.add_constraint(nurse_assigned_vars[n, s1] + nurse_assigned_vars[n, s2] <= 1,
- "medium_overlapping_%s_%s_%s" % (str(s1), str(s2), str(n)))
+ model.add_constraint(nurse_assigned[n, s1] + nurse_assigned[n, s2] <= 1,
+ "high_overlapping_{0!s}_{1!s}_{2!s}".format(s1, s2, n))
for s in all_shifts:
demand_min = s.min_requirement
demand_max = s.max_requirement
- model.add_range(demand_min, model.sum([nurse_assigned_vars[n, s] for n in model.nurses]), demand_max,
- "medium_shift_%s" % str(s))
+ total_assigned = model.sum(nurse_assigned[n, s] for n in model.nurses)
+ model.add_constraint(total_assigned >= demand_min,
+ "high_req_min_{0!s}_{1}".format(s, demand_min))
+ model.add_constraint(total_assigned <= demand_max,
+ "medium_req_max_{0!s}_{1}".format(s, demand_max))
for (dept, skill, required) in model.skill_requirements:
if required > 0:
for dsh in (s for s in all_shifts if dept == s.department):
- model.add_constraint(model.sum(nurse_assigned_vars[skilled_nurse, dsh] for skilled_nurse in
+ model.add_constraint(model.sum(nurse_assigned[skilled_nurse, dsh] for skilled_nurse in
(n for n in all_nurses if
n.name in model.nurse_skills.keys() and skill in model.nurse_skills[
n.name])) >= required,
- "high_required_%s_%s_%s_%s" % (str(dept), str(skill), str(required), str(dsh)))
+ "high_required_{0!s}_{1!s}_{2!s}_{3!s}".format(dept, skill, required, dsh))
# nurse-nurse associations
+ # for each pair of associted nurses, their assignement variables are equal
+ # over all shifts.
c = 0
- for (first_nurse_id, second_nurse_id) in model.nurse_associations:
- if first_nurse_id in nurses_by_id and second_nurse_id in nurses_by_id:
- first_nurse = nurses_by_id[first_nurse_id]
- second_nurse = nurses_by_id[second_nurse_id]
+ for (nurse_id1, nurse_id2) in model.nurse_associations:
+ if nurse_id1 in nurses_by_id and nurse_id2 in nurses_by_id:
+ nurse1 = nurses_by_id[nurse_id1]
+ nurse2 = nurses_by_id[nurse_id2]
for s in all_shifts:
c += 1
- ct_name = 'medium_ct_nurse_assoc%s_%s_%d' % (first_nurse_id, second_nurse_id, c)
- model.add_constraint(nurse_assigned_vars[first_nurse, s] == nurse_assigned_vars[second_nurse, s],
- ct_name)
+ ctname = 'medium_ct_nurse_assoc_{0!s}_{1!s}_{2:d}'.format(nurse_id1, nurse_id2, c)
+ model.add_constraint(nurse_assigned[nurse1, s] == nurse_assigned[nurse2, s], ctname)
# nurse-nurse incompatibilities
+ # for each pair of incompatible nurses, the sum of assigned variables is less than one
+ # in other terms, both nurses can never be assigned to the same shift
c = 0
- for (first_nurse_id, second_nurse_id) in model.nurse_incompatibilities:
- if first_nurse_id in nurses_by_id and second_nurse_id in nurses_by_id:
- first_nurse = nurses_by_id[first_nurse_id]
- second_nurse = nurses_by_id[second_nurse_id]
+ for (nurse_id1, nurse_id2) in model.nurse_incompatibilities:
+ if nurse_id1 in nurses_by_id and nurse_id2 in nurses_by_id:
+ nurse1 = nurses_by_id[nurse_id1]
+ nurse2 = nurses_by_id[nurse_id2]
for s in all_shifts:
c += 1
- ct_name = 'medium_ct_nurse_incompat_%s_%s_%d' % (first_nurse_id, second_nurse_id, c)
- model.add_constraint(nurse_assigned_vars[first_nurse, s] + nurse_assigned_vars[second_nurse, s] <= 1,
- ct_name)
+ ctname = 'medium_ct_nurse_incompat_{0!s}_{1!s}_{2:d}'.format(nurse_id1, nurse_id2, c)
+ model.add_constraint(nurse_assigned[nurse1, s] + nurse_assigned[nurse2, s] <= 1, ctname)
- model.nurse_costs = [model.nurse_assignment_vars[n, s] * n.payRate * model.shift_activities[s].duration for n in
+ model.total_number_of_assignments = model.sum(nurse_assigned[n,s] for n in all_nurses for s in all_shifts)
+ #model.total_salary_cost = model.sum(nurse_work_time[n] * n.pay_rate for n in all_nurses)
+ model.nurse_costs = [model.nurse_assignment_vars[n, s] * n.pay_rate * model.shift_activities[s].duration for n in
model.nurses
for s in model.shifts]
- model.total_number_of_assignments = model.sum(
- model.nurse_assignment_vars[n, s] for n in model.nurses for s in model.shifts)
model.total_salary_cost = model.sum(model.nurse_costs)
-
def setup_objective(model):
model.add_kpi(model.total_salary_cost, "Total salary cost")
model.add_kpi(model.total_number_of_assignments, "Total number of assignments")
model.add_kpi(model.average_nurse_work_time)
- total_fairness = model.sum(model.nurse_over_average_time_vars[n] for n in model.nurses) + model.sum(
- model.nurse_under_average_time_vars[n] for n in model.nurses)
+ total_over_average_worktime = model.sum(model.nurse_over_average_time_vars[n] for n in model.nurses)
+ total_under_average_worktime = model.sum(model.nurse_under_average_time_vars[n] for n in model.nurses)
+ model.add_kpi(total_over_average_worktime, "Total over-average worktime")
+ model.add_kpi(total_under_average_worktime, "Total under-average worktime")
+ total_fairness = total_over_average_worktime + total_under_average_worktime
model.add_kpi(total_fairness, "Total fairness")
- model.minimize(model.total_salary_cost + model.total_number_of_assignments + total_fairness)
+
+ model.minimize(model.total_salary_cost + total_fairness + model.total_number_of_assignments)
def print_information(model):
@@ -263,7 +287,7 @@ def print_solution(model):
print("Cost By Department:")
for d in model.departments:
cost = sum(
- model.nurse_assignment_vars[n, s].solution_value * n.payRate * model.shift_activities[s].duration for n in
+ model.nurse_assignment_vars[n, s].solution_value * n.pay_rate * model.shift_activities[s].duration for n in
model.nurses for s in model.shifts if s.department == d)
print("\t{}: {}".format(d, cost))
print("Nurses Assignments")
@@ -273,7 +297,7 @@ def print_solution(model):
print("\t{}: total hours:{}".format(n.name, total_hours))
for s in model.shifts:
if model.nurse_assignment_vars[n, s].solution_value == 1:
- print("\t\t{}: {} {}-{}".format(s.day.name, s.department, s.start_time, s.end_time))
+ print("\t\t{}: {} {}-{}".format(s.day, s.department, s.start_time, s.end_time))
SKILLS = ["Anaesthesiology",
@@ -321,47 +345,47 @@ def print_solution(model):
("Zoe", 8, 3, 29)
]
-SHIFTS = [("Emergency", Weekday.Monday, 2, 8, 3, 5),
- ("Emergency", Weekday.Monday, 8, 12, 4, 7),
- ("Emergency", Weekday.Monday, 12, 18, 2, 5),
- ("Emergency", Weekday.Monday, 18, 2, 3, 7),
- ("Consultation", Weekday.Monday, 8, 12, 10, 13),
- ("Consultation", Weekday.Monday, 12, 18, 8, 12),
- ("Cardiac Care", Weekday.Monday, 8, 12, 10, 13),
- ("Cardiac Care", Weekday.Monday, 12, 18, 8, 12),
- ("Emergency", Weekday.Tuesday, 8, 12, 4, 7),
- ("Emergency", Weekday.Tuesday, 12, 18, 2, 5),
- ("Emergency", Weekday.Tuesday, 18, 2, 3, 7),
- ("Consultation", Weekday.Tuesday, 8, 12, 10, 13),
- ("Consultation", Weekday.Tuesday, 12, 18, 8, 12),
- ("Cardiac Care", Weekday.Tuesday, 8, 12, 4, 7),
- ("Cardiac Care", Weekday.Tuesday, 12, 18, 2, 5),
- ("Cardiac Care", Weekday.Tuesday, 18, 2, 3, 7),
- ("Emergency", Weekday.Wednesday, 2, 8, 3, 5),
- ("Emergency", Weekday.Wednesday, 8, 12, 4, 7),
- ("Emergency", Weekday.Wednesday, 12, 18, 2, 5),
- ("Emergency", Weekday.Wednesday, 18, 2, 3, 7),
- ("Consultation", Weekday.Wednesday, 8, 12, 10, 13),
- ("Consultation", Weekday.Wednesday, 12, 18, 8, 12),
- ("Emergency", Weekday.Thursday, 2, 8, 3, 5),
- ("Emergency", Weekday.Thursday, 8, 12, 4, 7),
- ("Emergency", Weekday.Thursday, 12, 18, 2, 5),
- ("Emergency", Weekday.Thursday, 18, 2, 3, 7),
- ("Consultation", Weekday.Thursday, 8, 12, 10, 13),
- ("Consultation", Weekday.Thursday, 12, 18, 8, 12),
- ("Emergency", Weekday.Friday, 2, 8, 3, 5),
- ("Emergency", Weekday.Friday, 8, 12, 4, 7),
- ("Emergency", Weekday.Friday, 12, 18, 2, 5),
- ("Emergency", Weekday.Friday, 18, 2, 3, 7),
- ("Consultation", Weekday.Friday, 8, 12, 10, 13),
- ("Consultation", Weekday.Friday, 12, 18, 8, 12),
- ("Emergency", Weekday.Saturday, 2, 12, 5, 7),
- ("Emergency", Weekday.Saturday, 12, 20, 7, 9),
- ("Emergency", Weekday.Saturday, 20, 2, 12, 12),
- ("Emergency", Weekday.Sunday, 2, 12, 5, 7),
- ("Emergency", Weekday.Sunday, 12, 20, 7, 9),
- ("Emergency", Weekday.Sunday, 20, 2, 12, 12),
- ("Geriatrics", Weekday.Sunday, 8, 10, 2, 5)]
+SHIFTS = [("Emergency", "monday", 2, 8, 3, 5),
+ ("Emergency", "monday", 8, 12, 4, 7),
+ ("Emergency", "monday", 12, 18, 2, 5),
+ ("Emergency", "monday", 18, 2, 3, 7),
+ ("Consultation", "monday", 8, 12, 10, 13),
+ ("Consultation", "monday", 12, 18, 8, 12),
+ ("Cardiac Care", "monday", 8, 12, 10, 13),
+ ("Cardiac Care", "monday", 12, 18, 8, 12),
+ ("Emergency", "tuesday", 8, 12, 4, 7),
+ ("Emergency", "tuesday", 12, 18, 2, 5),
+ ("Emergency", "tuesday", 18, 2, 3, 7),
+ ("Consultation", "tuesday", 8, 12, 10, 13),
+ ("Consultation", "tuesday", 12, 18, 8, 12),
+ ("Cardiac Care", "tuesday", 8, 12, 4, 7),
+ ("Cardiac Care", "tuesday", 12, 18, 2, 5),
+ ("Cardiac Care", "tuesday", 18, 2, 3, 7),
+ ("Emergency", "wednesday", 2, 8, 3, 5),
+ ("Emergency", "wednesday", 8, 12, 4, 7),
+ ("Emergency", "wednesday", 12, 18, 2, 5),
+ ("Emergency", "wednesday", 18, 2, 3, 7),
+ ("Consultation", "wednesday", 8, 12, 10, 13),
+ ("Consultation", "wednesday", 12, 18, 8, 12),
+ ("Emergency", "thursday", 2, 8, 3, 5),
+ ("Emergency", "thursday", 8, 12, 4, 7),
+ ("Emergency", "thursday", 12, 18, 2, 5),
+ ("Emergency", "thursday", 18, 2, 3, 7),
+ ("Consultation", "thursday", 8, 12, 10, 13),
+ ("Consultation", "thursday", 12, 18, 8, 12),
+ ("Emergency", "friday", 2, 8, 3, 5),
+ ("Emergency", "friday", 8, 12, 4, 7),
+ ("Emergency", "friday", 12, 18, 2, 5),
+ ("Emergency", "friday", 18, 2, 3, 7),
+ ("Consultation", "friday", 8, 12, 10, 13),
+ ("Consultation", "friday", 12, 18, 8, 12),
+ ("Emergency", "saturday", 2, 12, 5, 7),
+ ("Emergency", "saturday", 12, 20, 7, 9),
+ ("Emergency", "saturday", 20, 2, 12, 12),
+ ("Emergency", "sunday", 2, 12, 5, 7),
+ ("Emergency", "sunday", 12, 20, 7, 9),
+ ("Emergency", "sunday", 20, 2, 12, 12),
+ ("Geriatrics", "sunday", 8, 10, 2, 5)]
NURSE_SKILLS = {"Anne": ["Anaesthesiology", "Oncology", "Pediatrics"],
"Betsy": ["Cardiac Care"],
@@ -378,65 +402,65 @@ def print_solution(model):
"Zoe": ["Cardiac Care"]
}
-VACATIONS = [("Anne", Weekday.Friday),
- ("Anne", Weekday.Sunday),
- ("Cathy", Weekday.Thursday),
- ("Cathy", Weekday.Tuesday),
- ("Joan", Weekday.Thursday),
- ("Joan", Weekday.Saturday),
- ("Juliet", Weekday.Monday),
- ("Juliet", Weekday.Tuesday),
- ("Juliet", Weekday.Thursday),
- ("Nathalie", Weekday.Sunday),
- ("Nathalie", Weekday.Thursday),
- ("Isabelle", Weekday.Monday),
- ("Isabelle", Weekday.Thursday),
- ("Patricia", Weekday.Saturday),
- ("Patricia", Weekday.Wednesday),
- ("Nicole", Weekday.Friday),
- ("Nicole", Weekday.Wednesday),
- ("Jude", Weekday.Tuesday),
- ("Jude", Weekday.Friday),
- ("Debbie", Weekday.Saturday),
- ("Debbie", Weekday.Wednesday),
- ("Joyce", Weekday.Sunday),
- ("Joyce", Weekday.Thursday),
- ("Chris", Weekday.Thursday),
- ("Chris", Weekday.Tuesday),
- ("Cecilia", Weekday.Friday),
- ("Cecilia", Weekday.Wednesday),
- ("Patrick", Weekday.Saturday),
- ("Patrick", Weekday.Sunday),
- ("Cindy", Weekday.Sunday),
- ("Dee", Weekday.Tuesday),
- ("Dee", Weekday.Friday),
- ("Jemma", Weekday.Friday),
- ("Jemma", Weekday.Wednesday),
- ("Bethanie", Weekday.Wednesday),
- ("Bethanie", Weekday.Tuesday),
- ("Betsy", Weekday.Monday),
- ("Betsy", Weekday.Thursday),
- ("David", Weekday.Monday),
- ("Gloria", Weekday.Monday),
- ("Jane", Weekday.Saturday),
- ("Jane", Weekday.Sunday),
- ("Janelle", Weekday.Wednesday),
- ("Janelle", Weekday.Friday),
- ("Julie", Weekday.Sunday),
- ("Kate", Weekday.Tuesday),
- ("Kate", Weekday.Monday),
- ("Nancy", Weekday.Sunday),
- ("Roberta", Weekday.Friday),
- ("Roberta", Weekday.Saturday),
- ("Janice", Weekday.Tuesday),
- ("Janice", Weekday.Friday),
- ("Suzanne", Weekday.Monday),
- ("Vickie", Weekday.Wednesday),
- ("Vickie", Weekday.Friday),
- ("Wendie", Weekday.Thursday),
- ("Wendie", Weekday.Saturday),
- ("Zoe", Weekday.Saturday),
- ("Zoe", Weekday.Sunday)]
+VACATIONS = [("Anne", "friday"),
+ ("Anne", "sunday"),
+ ("Cathy", "thursday"),
+ ("Cathy", "tuesday"),
+ ("Joan", "thursday"),
+ ("Joan", "saturday"),
+ ("Juliet", "monday"),
+ ("Juliet", "tuesday"),
+ ("Juliet", "thursday"),
+ ("Nathalie", "sunday"),
+ ("Nathalie", "thursday"),
+ ("Isabelle", "monday"),
+ ("Isabelle", "thursday"),
+ ("Patricia", "saturday"),
+ ("Patricia", "wednesday"),
+ ("Nicole", "friday"),
+ ("Nicole", "wednesday"),
+ ("Jude", "tuesday"),
+ ("Jude", "friday"),
+ ("Debbie", "saturday"),
+ ("Debbie", "wednesday"),
+ ("Joyce", "sunday"),
+ ("Joyce", "thursday"),
+ ("Chris", "thursday"),
+ ("Chris", "tuesday"),
+ ("Cecilia", "friday"),
+ ("Cecilia", "wednesday"),
+ ("Patrick", "saturday"),
+ ("Patrick", "sunday"),
+ ("Cindy", "sunday"),
+ ("Dee", "tuesday"),
+ ("Dee", "friday"),
+ ("Jemma", "friday"),
+ ("Jemma", "wednesday"),
+ ("Bethanie", "wednesday"),
+ ("Bethanie", "tuesday"),
+ ("Betsy", "monday"),
+ ("Betsy", "thursday"),
+ ("David", "monday"),
+ ("Gloria", "monday"),
+ ("Jane", "saturday"),
+ ("Jane", "sunday"),
+ ("Janelle", "wednesday"),
+ ("Janelle", "friday"),
+ ("Julie", "sunday"),
+ ("Kate", "tuesday"),
+ ("Kate", "monday"),
+ ("Nancy", "sunday"),
+ ("Roberta", "friday"),
+ ("Roberta", "saturday"),
+ ("Janice", "tuesday"),
+ ("Janice", "friday"),
+ ("Suzanne", "monday"),
+ ("Vickie", "wednesday"),
+ ("Vickie", "friday"),
+ ("Wendie", "thursday"),
+ ("Wendie", "saturday"),
+ ("Zoe", "saturday"),
+ ("Zoe", "sunday")]
NURSE_ASSOCIATIONS = [("Isabelle", "Dee"),
("Anne", "Patrick")]
@@ -459,8 +483,8 @@ def print_solution(model):
DEFAULT_WORK_RULES = TWorkRules(40)
-def build(context=None):
- mdl = Model("Nurses", context=context)
+def build(context=None, **kwargs):
+ mdl = Model("Nurses", context=context, **kwargs)
load_data(mdl, DEPTS, SKILLS, SHIFTS, NURSES, NURSE_SKILLS, VACATIONS, NURSE_ASSOCIATIONS,
NURSE_INCOMPATIBILITIES)
setup_data(mdl)
@@ -501,5 +525,4 @@ def run(context=None):
env = Environment()
env.print_information()
-
run(ctx)
diff --git a/examples/mp/workflow/cutstock.py b/examples/mp/workflow/cutstock.py
index d22ab2c..1647830 100644
--- a/examples/mp/workflow/cutstock.py
+++ b/examples/mp/workflow/cutstock.py
@@ -181,16 +181,17 @@ def print_information(self):
AbstractModel.print_information(self)
def print_solution(self, do_filter_zeros=True):
- print("| Nb of cuts \t| Pattern \t\t | Detail of pattern (nb of item1, nb of item2, ..., nb of item5) |")
- print("| ----------------------------------------------------------------------------------------------- |")
+ print("| Nb of cuts | Pattern | Detail of pattern (nb of item1, ..., nb of item5) |")
+ print("| {} |".format("-" * 75))
for p in self.patterns:
if self.cut_vars[p].solution_value >= 1e-3:
pattern_detail = {b.id: self.pattern_item_filled[(a, b)] for (a, b) in self.pattern_item_filled if
a == p}
print(
- "| {:g} \t \t \t| {} \t | {} \t\t\t\t\t\t\t\t |".format(self.cut_vars[p].solution_value, p,
- pattern_detail))
- print("| ----------------------------------------------------------------------------------------------- |")
+ "| {:<10g} | {!s:9} | {!s:50} |".format(self.cut_vars[p].solution_value,
+ p,
+ pattern_detail))
+ print("| {} |".format("-" * 75))
def run(self, context=None):
master_model = self
diff --git a/examples/mp/workflow/lagrangian_relaxation.py b/examples/mp/workflow/lagrangian_relaxation.py
index 3b1cfbc..5b3379d 100644
--- a/examples/mp/workflow/lagrangian_relaxation.py
+++ b/examples/mp/workflow/lagrangian_relaxation.py
@@ -25,7 +25,7 @@
def run_GAP_model(As, Bs, Cs, context=None):
- mdl = Model('GAP per Wolsey -without- Lagrangian Relaxation', 0, context=context)
+ mdl = Model('GAP per Wolsey -without- Lagrangian Relaxation', context=context)
print("#As={}, #Bs={}, #Cs={}".format(len(As), len(Bs), len(Cs)))
number_of_cs = len(C)
# variables
@@ -52,7 +52,7 @@ def run_GAP_model(As, Bs, Cs, context=None):
def run_GAP_model_with_Lagrangian_relaxation(As, Bs, Cs, max_iters=101, context=None):
- mdl = Model('GAP per Wolsey -with- Lagrangian Relaxation', 0, context=context)
+ mdl = Model('GAP per Wolsey -with- Lagrangian Relaxation', context=context)
print("#As={}, #Bs={}, #Cs={}".format(len(As), len(Bs), len(Cs)))
c_range = range(len(Cs))
# variables