1.0.528 release

dev 1.0.528
viulong-kong 2016-03-10 17:02:31 +01:00
parent 313e0b06e4
commit a3df7db956
21 changed files with 6696 additions and 0 deletions

View File

@ -0,0 +1,227 @@
# --------------------------------------------------------------------------
# Source file provided under Apache License, Version 2.0, January 2004,
# http://www.apache.org/licenses/
# (c) Copyright IBM Corp. 2015, 2016
# --------------------------------------------------------------------------
"""
Hitori is played with a grid of squares or cells, and each cell contains a
number. The objective is to eliminate numbers by filling in the squares such
that remaining cells do not contain numbers that appear more than once in
either a given row or column.
Filled-in cells cannot be horizontally or vertically adjacent, although they
can be diagonally adjacent. The remaining un-filled cells must form a single
component connected horizontally and vertically.
See https://en.wikipedia.org/wiki/Hitori
Please refer to documentation for appropriate setup of solving configuration.
"""
from docplex.cp.model import *
from sys import stdout
import copy
##############################################################################
## Problem data
##############################################################################
# Problem 0 (for test). A solution is:
# * 2 *
# 2 3 1
# * 1 *
HITORI_PROBLEM_0 = ( (2, 2, 1),
(2, 3, 1),
(1, 1, 1),
)
# Problem 1. A solution is:
# * 2 * 5 3
# 2 3 1 4 *
# * 1 * 3 5
# 1 * 5 * 2
# 5 4 3 2 1
HITORI_PROBLEM_1 = ( (2, 2, 1, 5, 3),
(2, 3, 1, 4, 5),
(1, 1, 1, 3, 5),
(1, 3, 5, 4, 2),
(5, 4, 3, 2, 1),
)
# Problem 2. A solution is:
# * 8 * 6 3 2 * 7
# 3 6 7 2 1 * 5 4
# * 3 4 * 2 8 6 1
# 4 1 * 5 7 * 3 *
# 7 * 3 * 8 5 1 2
# * 5 6 7 * 1 8 *
# 6 * 2 3 5 4 7 8
# 8 7 1 4 * 3 * 6
HITORI_PROBLEM_2 = ( (4, 8, 1, 6, 3, 2, 5, 7),
(3, 6, 7, 2, 1, 6, 5, 4),
(2, 3, 4, 8, 2, 8, 6, 1),
(4, 1, 6, 5, 7, 7, 3, 5),
(7, 2, 3, 1, 8, 5, 1, 2),
(3, 5, 6, 7, 3, 1, 8, 4),
(6, 4, 2, 3, 5, 4, 7, 8),
(8, 7, 1, 4, 2, 3, 5, 6),
)
# Problem 3, solution to discover !
HITORI_PROBLEM_3 = ( ( 2, 5, 6, 3, 8, 10, 7, 4, 13, 6, 14, 15, 9, 4, 1),
( 3, 1, 7, 12, 8, 4, 10, 4, 4, 11, 5, 13, 4, 9, 2),
( 4, 14, 10, 10, 14, 5, 11, 1, 6, 2, 7, 11, 13, 15, 12),
( 5, 10, 2, 5, 13, 3, 8, 5, 9, 7, 4, 10, 6, 10, 2),
( 1, 6, 8, 15, 10, 7, 4, 2, 15, 14, 9, 3, 3, 11, 4),
( 6, 14, 3, 11, 2, 4, 9, 5, 7, 13, 12, 8, 10, 14, 1),
(12, 8, 14, 11, 3, 7, 15, 13, 10, 7, 12, 13, 5, 2, 13),
(11, 4, 12, 15, 5, 6, 5, 3, 15, 10, 7, 9, 5, 13, 14),
( 8, 15, 4, 6, 15, 3, 13, 14, 6, 12, 10, 1, 11, 3, 5),
(15, 15, 9, 12, 1, 8, 11, 10, 2, 2, 11, 9, 4, 12, 2),
( 7, 1, 9, 9, 10, 5, 3, 11, 13, 6, 7, 4, 12, 5, 8),
(14, 10, 13, 4, 12, 15, 11, 10, 5, 7, 8, 12, 5, 3, 6),
( 5, 10, 11, 5, 11, 14, 14, 15, 8, 13, 13, 2, 7, 9, 9),
( 9, 7, 15, 10, 12, 11, 8, 6, 1, 5, 7, 14, 13, 1, 3),
( 6, 9, 1, 13, 6, 4, 12, 7, 14, 4, 2, 1, 3, 8, 12)
)
PUZZLE = HITORI_PROBLEM_3
SIZE = len(PUZZLE)
##############################################################################
## Utilities
##############################################################################
def print_grid(grid):
""" Print Hitori grid """
mxlen = max([len(str(grid[l][c])) for l in range(SIZE) for c in range(SIZE)])
frmt = " {:>" + str(mxlen) + "}"
for l in grid:
for v in l:
stdout.write(frmt.format(v))
stdout.write('\n')
def get_neighbors(l, c):
""" Build the list of neighbors of a given cell """
res = []
if c > 0: res.append((l, c-1))
if c < SIZE - 1: res.append((l, c+1))
if l > 0: res.append((l-1, c))
if l < SIZE - 1: res.append((l+1, c))
return res
def conditional(c, t, e):
""" Build a conditional expression
Args:
c: Condition expression
t: Expression to return if condition is true
e: Expression to return if expression is false
"""
return element(c, [e, t])
##############################################################################
## Create model
##############################################################################
# Create CPO model
mdl = CpoModel()
# Create one binary variable for each colored cell
color = [[integer_var(min=0, max=1, name="C" + str(l) + "_" + str(c)) for c in range(SIZE)] for l in range(SIZE)]
# Forbid adjacent colored cells
for l in range(SIZE):
for c in range(SIZE - 1):
mdl.add((color[l][c] + color[l][c + 1]) < 2)
for c in range(SIZE):
for l in range(SIZE - 1):
mdl.add((color[l][c] + color[l + 1][c]) < 2)
# Color cells for digits occurring more than once
for l in range(SIZE):
lvals = [] # List of values already processed
for c in range(SIZE):
v = PUZZLE[l][c]
if v not in lvals:
lvals.append(v)
lvars = [color[l][c]]
for c2 in range(c + 1, SIZE):
if PUZZLE[l][c2] == v:
lvars.append(color[l][c2])
# Add constraint if more than one occurrence of the value
nbocc = len(lvars)
if nbocc > 1:
mdl.add(sum(lvars) >= nbocc - 1)
for c in range(SIZE):
lvals = [] # List of values already processed
for l in range(SIZE):
v = PUZZLE[l][c]
if v not in lvals:
lvals.append(v)
lvars = [color[l][c]]
for l2 in range(l + 1, SIZE):
if PUZZLE[l2][c] == v:
lvars.append(color[l2][c])
# Add constraint if more than one occurrence of the value
nbocc = len(lvars)
if nbocc > 1:
mdl.add(sum(lvars) >= nbocc - 1)
# Each cell (blank or not) must be adjacent to at least another
for l in range(SIZE):
for c in range(SIZE):
lvars = [color[l2][c2] for l2, c2 in get_neighbors(l, c)]
mdl.add(sum(lvars) < len(lvars))
# At least cell 0,0 or cell 0,1 is blank.
# Build table of distance to one of these cells
# Black cells are associated to a max distance SIZE*SIZE
MAX_DIST = SIZE * SIZE
distance = [[integer_var(min=0, max=MAX_DIST, name="D" + str(l) + "_" + str(c)) for c in range(SIZE)] for l in range(SIZE)]
mdl.add(distance[0][0] == conditional(color[0][0], MAX_DIST, 0))
mdl.add(distance[0][1] == conditional(color[0][1], MAX_DIST, 0))
for c in range(2, SIZE):
mdl.add( distance[0][c] == conditional(color[0][c], MAX_DIST, 1 + min(distance[l2][c2] for l2, c2 in get_neighbors(0, c))) )
for l in range(1, SIZE):
for c in range(SIZE):
mdl.add( distance[l][c] == conditional(color[l][c], MAX_DIST, 1 + min(distance[l2][c2] for l2, c2 in get_neighbors(l, c))) )
# Force distance of blank cells to be less than max
for l in range(SIZE):
for c in range(SIZE):
mdl.add((color[l][c] > 0) | (distance[l][c] < MAX_DIST))
##############################################################################
## Solve model
##############################################################################
# Solve model
print("\nSolving model....")
msol = mdl.solve()
# Print solution
stdout.write("Initial problem:\n")
print_grid(PUZZLE)
stdout.write("Solution:\n")
if msol:
# Print solution grig
psol = []
for l in range(SIZE):
nl = []
for c in range(SIZE):
nl.append('.' if msol[color[l][c]] > 0 else PUZZLE[l][c])
psol.append(nl)
print_grid(psol)
# Print distance grid
print("Distances:")
psol = [['.' if msol[distance[l][c]] == MAX_DIST else msol[distance[l][c]] for c in range(SIZE)] for l in range(SIZE)]
print_grid(psol)
else:
stdout.write("No solution found\n")

View File

@ -0,0 +1,242 @@
# --------------------------------------------------------------------------
# Source file provided under Apache License, Version 2.0, January 2004,
# http://www.apache.org/licenses/
# (c) Copyright IBM Corp. 2015, 2016
# --------------------------------------------------------------------------
"""
Light Up, also called Akari, is a binary-determination logic puzzle published by Nikoli.
Light Up is played on a rectangular grid of white and black cells.
The player places light bulbs in white cells such that no two bulbs shine on each other,
until the entire grid is lit up. A bulb sends rays of light horizontally and vertically,
illuminating its entire row and column unless its light is blocked by a black cell.
A black cell may have a number on it from 0 to 4, indicating how many bulbs must be placed
adjacent to its four sides; for example, a cell with a 4 must have four bulbs around it,
one on each side, and a cell with a 0 cannot have a bulb next to any of its sides.
An unnumbered black cell may have any number of light bulbs adjacent to it, or none.
Bulbs placed diagonally adjacent to a numbered cell do not contribute to the bulb count.
See https://en.wikipedia.org/wiki/Light_Up_(puzzle).
Examples taken from https://www.brainbashers.com and https://en.wikipedia.org.
Please refer to documentation for appropriate setup of solving configuration.
"""
from docplex.cp.model import *
from sys import stdout
##############################################################################
## Problem data
##############################################################################
# Each problem is expressed as a list of strings, each one representing a line of the puzzle.
# Character may be:
# - Blank for an empty cell
# - A digit (in 0..4) for black cell that force a number of neighbor bulbs,
# - Any character to represent a black cell
# Problem 1. Solution:
LIGHT_UP_PROBLEM_1 = (" 2 ",
" ",
" X0 ",
" 1 ",
" 1 ")
# Problem 2
LIGHT_UP_PROBLEM_2 = ("X X X",
" X ",
" 3 0 ",
" 2 X 1",
" 10X ",
" 1XX ",
"X 2 2 ",
" X X ",
" 1 ",
"0 1 0")
# Problem 2
LIGHT_UP_PROBLEM_3 = (" X X 1 ",
"3 X 2 X 2 ",
" XX 1 XX ",
" X 1X1 X1 3 X X2XX ",
"X 1X X 0X",
" X XX XXXX ",
"X 1 3 ",
" X 3 X X",
" 1X 1X X X 2 X ",
" X X X XXX XX",
" X X 0X0XX 1 X ",
" 0 0 0 X X 1 ",
" 1 1 X ",
" X X X 1 X XX",
"X 2 2 X1X1 ",
"X X 0 1 X X X ",
" 2 X X 2 ",
"X X XX X ",
" 0 2X X ",
"XX 1 2 X 2X ")
PUZZLE = LIGHT_UP_PROBLEM_3
##############################################################################
## Utilities
##############################################################################
WIDTH = len(PUZZLE[0])
HEIGHT = len(PUZZLE)
def print_grid(grid):
""" Print grid """
for l in grid:
stdout.write('|')
for v in l:
stdout.write(" " + str(v))
stdout.write(' |\n')
def get_neighbors(l, c):
""" Build the list of neighbors of a given cell """
res = []
if c > 0: res.append((l, c-1))
if c < WIDTH - 1: res.append((l, c+1))
if l > 0: res.append((l-1, c))
if l < HEIGHT - 1: res.append((l+1, c))
return res
def get_all_visible(l, c):
""" Build the list of cells that are visible from a given one """
res = [(l, c)]
c2 = c - 1
while c2 >= 0 and PUZZLE[l][c2] == ' ':
res.append((l, c2))
c2 -= 1
c2 = c + 1
while c2 < WIDTH and PUZZLE[l][c2] == ' ':
res.append((l, c2))
c2 += 1
l2 = l - 1
while l2 >= 0 and PUZZLE[l2][c] == ' ':
res.append((l2, c))
l2 -= 1
l2 = l + 1
while l2 < HEIGHT and PUZZLE[l2][c] == ' ':
res.append((l2, c))
l2 += 1
return res
def get_right_empty_count(l, c):
""" Get the number of empty cells at the right of the given one, including it """
if PUZZLE[l][c] != ' ':
return 1
res = 1
c += 1
while c < WIDTH and PUZZLE[l][c] == ' ':
c += 1
res += 1
return res
def get_down_empty_count(l, c):
""" Get the number of empty cells at the down of the given one, including it """
if PUZZLE[l][c] != ' ':
return 1
res = 1
l += 1
while l < HEIGHT and PUZZLE[l][c] == ' ':
l += 1
res += 1
return res
def conditional(c, t, e):
""" Build a conditional expression
Args:
c: Condition expression
t: Expression to return if condition is true
e: Expression to return if expression is false
"""
return element(c, [e, t])
##############################################################################
## Create model
##############################################################################
# Create CPO model
mdl = CpoModel()
# Create one binary variable for presence of bulbs in cells
bulbs = [[integer_var(min=0, max=1, name="C" + str(l) + "_" + str(c)) for c in range(WIDTH)] for l in range(HEIGHT)]
# Force number of bulbs in black cells to zero
for l in range(HEIGHT):
for c in range(WIDTH):
if PUZZLE[l][c] != ' ':
mdl.add(bulbs[l][c] == 0)
# Force number of bulbs around numbered cells
for l in range(HEIGHT):
for c in range(WIDTH):
v = PUZZLE[l][c]
if v.isdigit():
mdl.add(sum(bulbs[l2][c2] for l2, c2 in get_neighbors(l, c)) == int(v))
# Avoid multiple bulbs on adjacent empty cells
for l in range(HEIGHT):
c = 0
while c < WIDTH:
nbc = get_right_empty_count(l, c)
if nbc > 1:
mdl.add(sum(bulbs[l][c2] for c2 in range(c, c + nbc)) <= 1)
c += nbc
for c in range(WIDTH):
l = 0
while l < HEIGHT:
nbc = get_down_empty_count(l, c)
if nbc > 1:
mdl.add(sum(bulbs[l2][c] for l2 in range(l, l + nbc)) <= 1)
l += nbc
# Each empty cell must be lighten by at least one bulb
for l in range(HEIGHT):
for c in range(WIDTH):
if PUZZLE[l][c] == ' ':
mdl.add(sum(bulbs[l2][c2] for l2, c2 in get_all_visible(l, c)) > 0)
# Minimize the total number of bulbs
nbbulbs = integer_var(0, HEIGHT * WIDTH, name="NbBulbs")
mdl.add(nbbulbs == sum(bulbs[l][c] for c in range(WIDTH) for l in range(HEIGHT)))
mdl.add(minimize(nbbulbs))
##############################################################################
## Solve model
##############################################################################
# Print CPO model
#mdl.export_as_cpo(srcloc=True)
# Solve model
print("\nSolving model....")
msol = mdl.solve()
# Print solution
stdout.write("Initial problem:\n")
print_grid(PUZZLE)
if msol:
# Print solution grig
psol = []
stdout.write("Solution: (bulbs represented by *):\n")
for l in range(HEIGHT):
nl = []
for c in range(WIDTH):
if PUZZLE[l][c] == ' ':
nl.append('*' if msol[bulbs[l][c]] > 0 else '.')
else:
nl.append(PUZZLE[l][c])
psol.append(nl)
print_grid(psol)
print("Total bulbs: " + str(msol[nbbulbs]))
else:
stdout.write("No solution found\n")

View File

@ -0,0 +1,153 @@
# --------------------------------------------------------------------------
# Source file provided under Apache License, Version 2.0, January 2004,
# http://www.apache.org/licenses/
# (c) Copyright IBM Corp. 2015, 2016
# --------------------------------------------------------------------------
"""
The problem is to build steel coils from slabs that are available in a
work-in-process inventory of semi-finished products. There is no limitation
in the number of slabs that can be requested, but only a finite number of slab
sizes is available (sizes 11, 13, 16, 17, 19, 20, 23, 24, 25, 26, 27, 28, 29,
30, 33, 34, 40, 43, 45). The problem is to select a number of slabs to
build the coil orders, and to satisfy the following constraints:
* A coil order can be built from only one slab.
* Each coil order requires a specific process to build it from a
slab. This process is encoded by a color.
* Several coil orders can be built from the same slab. But a slab can
be used to produce at most two different "colors" of coils.
* The sum of the sizes of each coil order built from a slab must not
exceed the slab size.
Finally, the production plan should minimize the unused capacity of the
selected slabs.
This problem is based on "prob038: Steel mill slab design problem" from
CSPLib (www.csplib.org). It is a simplification of an industrial problem
described in J. R. Kalagnanam, M. W. Dawande, M. Trumbo, H. S. Lee.
"Inventory Matching Problems in the Steel Industry," IBM Research
Report RC 21171, 1998.
Please refer to documentation for appropriate setup of solving configuration.
"""
from docplex.cp.model import *
from collections import namedtuple
from sys import stdout
##############################################################################
# Model configuration
##############################################################################
# The number of coils to produce
TUPLE_ORDER = namedtuple("TUPLE_ORDER", ["index", "weight", "color"])
orders = [ TUPLE_ORDER(1, 22, 5),
TUPLE_ORDER(2, 9, 3),
TUPLE_ORDER(3, 9, 4),
TUPLE_ORDER(4, 8, 5),
TUPLE_ORDER(5, 8, 7),
TUPLE_ORDER(6, 6, 3),
TUPLE_ORDER(7, 5, 6),
TUPLE_ORDER(8, 3, 0),
TUPLE_ORDER(9, 3, 2),
TUPLE_ORDER(10, 3, 3),
TUPLE_ORDER(11, 2, 1),
TUPLE_ORDER(12, 2, 5)
]
NB_SLABS = 12
MAX_COLOR_PER_SLAB = 2
# The total number of slabs available. In theory this can be unlimited,
# but we impose a reasonable upper bound in order to produce a practical
# optimization model.
# The different slab weights available.
slab_weights = [ 0, 11, 13, 16, 17, 19, 20, 23, 24, 25,
26, 27, 28, 29, 30, 33, 34, 40, 43, 45 ]
nb_orders = len(orders)
slabs = range(NB_SLABS)
allcolors = set([ o.color for o in orders ])
# CPO needs lists for pack constraint
order_weights = [ o.weight for o in orders ]
# The heaviest slab
max_slab_weight = max(slab_weights)
# The amount of loss incurred for different amounts of slab use
# The loss will depend on how much less steel is used than the slab
# just large enough to produce the coils.
loss = [ min([sw-use for sw in slab_weights if sw >= use]) for use in range(max_slab_weight+1)]
##############################################################################
# Modeling
##############################################################################
# Create model
mdl = CpoModel()
# Which slab is used to produce each coil
production_slab = integer_var_dict(orders, 0, NB_SLABS-1, "production_slab")
# How much of each slab is used
slab_use = integer_var_list(NB_SLABS, 0, max_slab_weight, "slab_use")
# The total loss is
total_loss = sum([element(slab_use[s], loss) for s in slabs])
# The orders are allocated to the slabs with capacity
mdl.add(pack(slab_use, [production_slab[o] for o in orders], order_weights))
# At most MAX_COLOR_PER_SLAB colors per slab
for s in slabs:
su = 0
for c in allcolors:
lo = False
for o in orders:
if o.color==c:
lo = (production_slab[o] == s) | lo
su += lo
mdl.add(su <= MAX_COLOR_PER_SLAB)
# Search strategy
mdl.set_search_phases([search_phase([production_slab[o] for o in orders])])
# Add minimization objective
mdl.add(minimize(total_loss))
##############################################################################
# Model solving
##############################################################################
# Solve model
print("Solving model....")
msol = mdl.solve(FailLimit=100000)
# Print solution
if msol:
print("Solution: ")
from_slabs = [set([o.index for o in orders if msol[production_slab[o]]== s])for s in slabs]
slab_colors = [set([o.color for o in orders if o.index in from_slabs[s]])for s in slabs]
for s in slabs:
if len(from_slabs[s]) > 0:
stdout.write("Slab = " + str(s))
stdout.write("\tLoss = " + str(loss[msol[slab_use[s]]]))
stdout.write("\tcolors = " + str(slab_colors[s]))
stdout.write("\tOrders = " + str(from_slabs[s]) + "\n")
else:
print("No solution found")

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,482 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<h1><font color=blue>Beginning with Constraint Programming</font></h1>\n",
"<H2><font color=green>The Golomb Ruler example</font></h2>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<h3>Problem description</h3>\n",
"<p>\n",
"A detailed description (from which this paragraph comes from) is available on <b>Wikipedia</b> at https://en.wikipedia.org/wiki/Golomb_ruler.\n",
"<p>\n",
"In mathematics, a Golomb ruler is a set of marks at integer positions along an imaginary ruler such that no two pairs of marks are the same distance apart. The number of marks on the ruler is its order, and the largest distance between two of its marks is its length. \n",
"<p>\n",
"Following is an example of Golomb ruler of order 4 and length 6.\n",
"<img src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/0/05/Golomb_Ruler-4.svg/220px-Golomb_Ruler-4.svg.png\"></center>\n",
"<p>\n",
"This problem is not only an intellectual problem. It has a lot of practical applications:\n",
"<ul>\n",
"<li> within Information Theory related to error correcting codes,\n",
"<li> the selection of radio frequencies to reduce the effects of intermodulation interference,\n",
"<li> the design of conference rooms, to maximize the number of possible configurations with a minimum of partitions:\n",
"</ul>\n",
"<center>\n",
"<img src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/5/52/Golomb_ruler_conference_room.svg/300px-Golomb_ruler_conference_room.svg.png\"></center>\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": true
},
"source": [
"<h3>Modeling the problem</h3>\n",
"<p>\n",
"Constraint Programming is a programming paradigm that allows to express a problem using:\n",
"<ul>\n",
"<li> the unknowns of the problem (the <i>variables</i>),\n",
"<li> the constraints/laws/rules of the problem, mathematical expressions linking variables together (the <i>constraints</i>),\n",
"<li> what is to be optimized (the <i>objective function</i>).\n",
"</ul>\n",
"<p>\n",
"All this information, plus some configuration parameters, is aggregated into a single object called <i>model</i>. \n",
"<p>\n",
"The remainder of this notebook describes in details how to build and solve this problem with IBM CP Optimizer, using its <i>DOcplex</i> Python modeling API."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<h3>Modeling with Python</h3>\n",
"<p>\n",
"Let's start to build our Golomb Ruler problem with IBM CP Optimizer for Python.\n",
"<p>\n",
"The first thing is to get access to the appropriate DOcplex library. The following code imports the appropriate package <i>docplex.cp</i>. If the import fails, an installation command is executed as it is probable that the package is not installed. "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"try:\n",
" import docplex.cp\n",
"except:\n",
" !pip install docplex"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note that the more global package <i>docplex</i> contains another subpackage <i>docplex.mp</i> that is dedicated to Mathematical Programming, another branch of optimization."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now, we need to import all required modeling functions that are provided by the <i>docplex.cp</i> package:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"# Import Constraint Programming modelization functions\n",
"from docplex.cp.model import *"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<h4>Define model input data</h4>\n",
"<p>\n",
"The first thing to define is the model input data.\n",
"In the case of the Golomb Ruler problem, there is only one input which is the order of the ruler, that is the number of marks:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"# Define required number of marks on the ruler\n",
"ORDER = 7"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<h4>Create the model container</h4>\n",
"<p>\n",
"The model is represented by a Python object that is filled with the different model elements (variables, constraints, objective function, etc). The first thing to do is then to create such an object:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"# Create model object\n",
"mdl = CpoModel()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<h4>Define model variables</h4>\n",
"<p>\n",
"Now, we need to define the variables of the problem. As the expected problem result is the list of mark positions, the simplest choice is to create one integer variable to represent the position of each mark on the ruler.\n",
"<p>\n",
"Each variable has a a set of possible values called his <i>domain</i>. To reduce the search space, it is important to reduce this domain as far as possible.\n",
"<p>\n",
"In our case, we can naively estimate that the maximum distance between two adjacent marks is the order of the ruler minus one. Then the maximal position of a mark is (ORDER - 1)². Each variable domain is then limited to an interval [0..(ORDER - 1)²].\n",
"<p>\n",
"A list of integer variables can be defined using method <i>integer_var_list()</i>. In our case, defining one variable for each mark can be created as follows:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"# Create array of variables corresponding to rule marks\n",
"marks = integer_var_list(ORDER, 0, (ORDER - 1) ** 2, \"M\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<h4>Define model constraints</h4>\n",
"<p>\n",
"We need to express that all possible distances between two marks must be different. To do this, we create an array that contains all these distances:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"# Create an array with all distances between all marks\n",
"dist = [marks[i] - marks[j] for i in range(1, ORDER) for j in range(0, i)]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We have used here the operator '-' to express the difference between variables. It may appear strange as the variables are not instanciated at that time, but the Python operator has been overloaded to construct a CP expression instead of attempting to compute the arithmetic difference. All other standard Python operators can be used to make operations between CP objects (<, >, <=, >=, ==, !=, +, -, /, *, &, |, //, **, ...). Have a look to documentation for details.\n",
"<p>\n",
"To force all these distances to be different, we use the special <i>all_diff()</i> constraint as follows:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"# Force all distances to be different\n",
"mdl.add(all_diff(dist))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The call <i>mdl.add(...)</i> is necessary to express that the constraint must be added to the model."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<h4>Remove symmetries</h4>\n",
"<p>\n",
"The constraint we have expressed above is theoritically enough, and the model can be solved as it is.\n",
"<p>\n",
"However, it does not differentiate between all possible permutations of the different mark positions that are solutions to the problem, for example, 0-1-4-6, 4-6-1-0, 6-0-1-4, etc. As they are ORDER! (factorial of ORDER) such permutations, the search space would be drastically reduced by removing them.\n",
"<p>\n",
"We can do that by forcing an order between marks, for example the order of their index:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"# Avoid symmetric solutions by ordering marks\n",
"for i in range(1, ORDER):\n",
" mdl.add(marks[i] > marks[i - 1])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We also know that first mark is at the beginning of the rule:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"# Force first mark position to zero\n",
"mdl.add(marks[0] == 0)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<h4>Avoid mirror solutions</h4>\n",
"<p>\n",
"Each optimal solution has a mirror, with all mark distances in the reverse order, for example, 01--4-6 and 0-2--56. \n",
"The following constraint can be added to avoid this: "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"# Avoid mirror solution\n",
"mdl.add((marks[1] - marks[0]) < (marks[ORDER - 1] - marks[ORDER - 2]))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<h4>Define objective</h4>\n",
"<p>\n",
"Finally, we want to get the shortest Golomb Ruler. This can be expressed by minimizing the position of the last mark.\n",
"As we have ordered the marks, we can do this using:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"# Minimize ruler size\n",
"mdl.add(minimize(marks[ORDER - 1]))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"If the marks were not ordered, we should have use instead:<br>\n",
"<code> mdl.add(minimize(max(marks)))</code><br>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<h3>Solve the model</h3>\n",
"<p>\n",
"The model is now completely defined. It is time to solve it !\n",
"<p>\n",
"To use the CP Optimizer solver available on the IBM Decision Optimization on Cloud service:\n",
"<ul>\n",
"<li> Register for the DOcloud free trial and use it free for 30 days by using https://developer.ibm.com/docloud/try-docloud-free\n",
"<li> Get your access credentials (base URL and access key) by going this page: http://developer.ibm.com/docloud/docs/api-key/\n",
"</ul>\n",
"<p>\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"# Initialize IBM Decision Optimization credentials\n",
"SVC_URL = \"https://api-oaas.docloud.ibmcloud.com/job_manager/rest/v1/\"\n",
"SVC_KEY = \"ENTER YOUR KEY HERE\""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The model can be solved by calling:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"# Solve the model\n",
"print(\"Solving model....\")\n",
"msol = mdl.solve(url=SVC_URL, key=SVC_KEY)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<h4>Print the solution</h4>\n",
"<p>\n",
"The shortest way to output the solution that has been found by the solver is to call the method <i>print_solution()</i> as follows:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"# Print solution\n",
"print(\"Solution: \")\n",
"msol.print_solution()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This output is totally generic and simply prints the value of all model variables, the objective value, and some other solving information.\n",
"<p>\n",
"A more specific output can be generated by writing more code. The following example illustrates how to access specific elements of the solution. "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"# Print solution\n",
"from sys import stdout\n",
"if msol:\n",
" # Print found solution\n",
" stdout.write(\"Solution: \" + msol.get_solve_status() + \"\\n\")\n",
" stdout.write(\"Position of ruler marks: \")\n",
" for v in marks:\n",
" stdout.write(\" \" + str(msol[v]))\n",
" stdout.write(\"\\n\")\n",
" stdout.write(\"Solve time: \" + str(round(msol.get_solve_time(), 2)) + \"s\\n\")\n",
"else:\n",
" # No solution found\n",
" stdout.write(\"No solution found. Search status: \" + msol.get_solve_status() + \"\\n\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Another possibility is for example to simulate real ruler using characters, as follows:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"# Print solution as a ruler\n",
"if msol:\n",
" stdout.write(\"Ruler: +\")\n",
" for i in range(1, ORDER):\n",
" stdout.write('-' * (msol[marks[i]] - msol[marks[i - 1]] - 1) + '+')\n",
" stdout.write(\"\\n\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<h3>Going further with Constraint Programming</h3>\n",
"<p>\n",
"The last available installable package is available on Pypi here: https://pypi.python.org/pypi/docplex\n",
"<p>\n",
"Downloadable documentation is here: https://github.com/IBMDecisionOptimization/docplex-doc, and an inline version is available here: http://rawgit.com/IBMDecisionOptimization/docplex-doc/master/docs/index.html\n",
"<p>\n",
"A complete set of modeling examples can be downloaded here: https://github.com/IBMDecisionOptimization/docplex-examples "
]
}
],
"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.10"
}
},
"nbformat": 4,
"nbformat_minor": 0
}

View File

@ -0,0 +1,926 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"collapsed": true
},
"source": [
"<h1><font color=blue>Beginning with Constraint Programming - Detailed Scheduling features</font></h1>\n",
"<H2><font color=green>House Building with worker skills</font></h2>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<h2>Problem description</h2>\n",
"<p>\n",
"This is a problem of building five houses in different locations; the masonry, roofing, painting, etc. must be scheduled. Some tasks must necessarily take place before others and these requirements are expressed through precedence constraints.\n",
"<p>\n",
"There are three workers, and each worker has a given skill level for each task. Each task requires one worker; the worker assigned must have a non-null skill level for the task. A worker can be assigned to only one task at a time.\n",
"<p>\n",
"Each house has a deadline. The objective is to maximize the skill levels of the workers assigned to the tasks."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<h2>About Detailed Scheduling concepts</h2>\n",
"<p>\n",
"<ul>\n",
"<li> Scheduling consists of assigning starting and completion times to a set of activities while satisfying different types of constraints (resource availability, precedence relationships, … ) and optimizing some criteria (minimizing tardiness, …)\n",
"<img src = \"./house_building_utils/activity.png\" >\n",
"<li> Time is considered as a continuous dimension: domain of possible start/completion times for an activity is potentially very large\n",
"<li>Beside start and completion times of activities, other types of decision variables are often involved in real industrial scheduling problems (resource allocation, optional activities …)\n",
"</ul>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<h2>Modeling with Python</h2>\n",
"<p>\n",
"Let's start to build our House Building problem with IBM CP Optimizer for Python.\n",
"<p>\n",
"The first thing is to get access to the appropriate DOcplex library. The following code imports the appropriate package <i>docplex.cp</i>. If the import fails, an installation command is executed as it is probable that the package is not installed. "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"try:\n",
" import docplex.cp\n",
"except:\n",
" !pip install docplex"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now, we need to import all required modeling functions that are provided by the <i>docplex.cp</i> package:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"from docplex.cp.model import *\n",
"from sys import stdout\n",
"from collections import namedtuple"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Input Data"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Planning contains the number of houses and the max amount of periods (<i>days</i>) for our schedule"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"NB_HOUSES = 5\n",
"MAX_AMOUNT_OF_PERIODS = 318\n",
"HOUSES = range(1, NB_HOUSES + 1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"All tasks must start and end between 0 and the max amount of periods"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"period_domain = (0, MAX_AMOUNT_OF_PERIODS)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"For each task type in the house building project, the following table shows the duration of the task in days along with the tasks that must be finished before the task can start. A worker can only work on one task at a time; each task, once started, may not be interrupted.\n",
"<p>\n",
"\n",
"| *Task* | *Duration* | *Preceding tasks* |\n",
"|---|---|---|\n",
"| masonry \t| 35 |\t|\n",
"| carpentry | 15 | masonry |\n",
"| plumbing \t| 40 | masonry |\n",
"| ceiling \t| 15 | masonry |\n",
"| roofing \t| 5 | carpentry |\n",
"| painting \t| 10 | ceiling |\n",
"| windows \t| 5 | roofing |\n",
"| facade \t| 10 | roofing, plumbing |\n",
"| garden \t| 5 | roofing, plumbing |\n",
"| moving \t| 5 | windows, facade, garden, painting | "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<h3>Tasks' durations</h3>"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"Task = (namedtuple(\"Task\", [\"name\", \"duration\"]))\n",
"TASKS = {Task(\"masonry\", 35),\n",
" Task(\"carpentry\", 15),\n",
" Task(\"plumbing\", 40),\n",
" Task(\"ceiling\", 15),\n",
" Task(\"roofing\", 5),\n",
" Task(\"painting\", 10),\n",
" Task(\"windows\", 5),\n",
" Task(\"facade\", 10),\n",
" Task(\"garden\", 5),\n",
" Task(\"moving\", 5),\n",
" }"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<h3>The tasks precedences</h3>"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"TaskPrecedence = (namedtuple(\"TaskPrecedence\", [\"beforeTask\", \"afterTask\"]))\n",
"TASK_PRECEDENCES = {TaskPrecedence(\"masonry\", \"carpentry\"),\n",
" TaskPrecedence(\"masonry\", \"plumbing\"),\n",
" TaskPrecedence(\"masonry\", \"ceiling\"),\n",
" TaskPrecedence(\"carpentry\", \"roofing\"),\n",
" TaskPrecedence(\"ceiling\", \"painting\"),\n",
" TaskPrecedence(\"roofing\", \"windows\"),\n",
" TaskPrecedence(\"roofing\", \"facade\"),\n",
" TaskPrecedence(\"plumbing\", \"facade\"),\n",
" TaskPrecedence(\"roofing\", \"garden\"),\n",
" TaskPrecedence(\"plumbing\", \"garden\"),\n",
" TaskPrecedence(\"windows\", \"moving\"),\n",
" TaskPrecedence(\"facade\", \"moving\"),\n",
" TaskPrecedence(\"garden\", \"moving\"),\n",
" TaskPrecedence(\"painting\", \"moving\"),\n",
" }"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"There are three workers with varying skill levels in regard to the ten tasks. If a worker has a skill level of zero for a task, he may not be assigned to the task.\n",
"<p>\n",
"\n",
"| *Task* | *Joe* | *Jack* | *Jim* |\n",
"|---|---|---|---|\n",
"|masonry |9 |\t5 |\t0|\n",
"|carpentry |7 |\t0 |\t5|\n",
"|plumbing |0 |\t7 |\t0|\n",
"|ceiling |5 |\t8 |\t0|\n",
"|roofing |6 |\t7 |\t0|\n",
"|painting |0 |\t9 |\t6|\n",
"|windows |8 |\t0 |\t5|\n",
"|façade |5 |\t5 |\t0|\n",
"|garden |5 |\t5 |\t9|\n",
"|moving |6 |\t0 |\t8|"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<h3>Workers Names</h3>"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"WORKERS = {\"Joe\", \"Jack\", \"Jim\"}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<h3>Workers Name and level for each of there skill</h3>"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"Skill = (namedtuple(\"Skill\", [\"worker\", \"task\", \"level\"]))\n",
"SKILLS = {Skill(\"Joe\", \"masonry\", 9),\n",
" Skill(\"Joe\", \"carpentry\", 7),\n",
" Skill(\"Joe\", \"ceiling\", 5),\n",
" Skill(\"Joe\", \"roofing\", 6),\n",
" Skill(\"Joe\", \"windows\", 8),\n",
" Skill(\"Joe\", \"facade\", 5),\n",
" Skill(\"Joe\", \"garden\", 5),\n",
" Skill(\"Joe\", \"moving\", 6),\n",
" Skill(\"Jack\", \"masonry\", 5),\n",
" Skill(\"Jack\", \"plumbing\", 7),\n",
" Skill(\"Jack\", \"ceiling\", 8),\n",
" Skill(\"Jack\", \"roofing\", 7),\n",
" Skill(\"Jack\", \"painting\", 9),\n",
" Skill(\"Jack\", \"facade\", 5),\n",
" Skill(\"Jack\", \"garden\", 5),\n",
" Skill(\"Jim\", \"carpentry\", 5),\n",
" Skill(\"Jim\", \"painting\", 6),\n",
" Skill(\"Jim\", \"windows\", 5),\n",
" Skill(\"Jim\", \"garden\", 9),\n",
" Skill(\"Jim\", \"moving\", 8)\n",
" }"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Utility functions"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"find_tasks: returns the task it refers to in the TASKS vector"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"def find_tasks(name):\n",
" return next(t for t in TASKS if t.name == name)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"find_skills: returns the skill it refers to in the SKILLS vector"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"def find_skills(worker, task):\n",
" return next(s for s in SKILLS if (s.worker == worker) and (s.task == task))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"find_max_level_skill: returns the tuple \"skill\" where the level is themaximum for a given task"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def find_max_level_skill(task):\n",
" st = [s for s in SKILLS if s.task == task]\n",
" return next(sk for sk in st if sk.level == max([s.level for s in st]))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Modeling"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<h3>Create the model container</h3>\n",
"<p>\n",
"The model is represented by a Python object that is filled with the different model elements (variables, constraints, objective function, etc). The first thing to do is then to create such an object:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"mdl = CpoModel()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Create variables"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<h3><i><font color=blue>Concept: interval variable</font></i></h3>\n",
"<p>\n",
"<ul>\n",
"<li> What for?<br>\n",
"<blockquote> Modeling an interval of time during which a particular property holds <br>\n",
"(an activity executes, a resource is idle, a tank must be non-empty, …)</blockquote> \n",
"<li> Example:<br>\n",
"<blockquote><code><font color=green>interval_var(start=(0,1000), end=(0,1000), size=(10,20))</font></code>\n",
"</blockquote> \n",
"<img src = \"./house_building_utils/intervalVar.png\" >\n",
"<li>Properties:\n",
"<ul>\n",
"<li>The **value** of an interval variable is an integer interval [start,end) \n",
"<li>**Domain** of possible values: [0,10), [1,11), [2,12),...[990,1000), [0,11),[1,12),...\n",
"<li>Domain of interval variables is represented **compactly** in CP Optimizer (a few bounds: smin, smax, emin, emax, szmin, szmax)\n",
"</ul>\n",
"</ul>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"For each house, an interval variable is created for each task.<br>\n",
"This interval must start and end inside the period_domain and its duration is set as the value stated in TASKS definition."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"tasks = {} # dict of interval variable for each house and task\n",
"for house in HOUSES:\n",
" for task in TASKS:\n",
" tasks[(house, task)] = interval_var(start=period_domain,\n",
" end=period_domain,\n",
" size=task.duration,\n",
" name=\"house {} task {}\".format(house, task))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<h3><i><font color=blue>Concept: optional interval variable</font></i></h3>\n",
"<p>\n",
"<ul>\n",
"<li>Interval variables can be defined as being **optional** that is, it is part of the decisions of the problem to decide whether the interval will be **present** or **absent** in the solution<br>\n",
"<li> What for?<br>\n",
"<blockquote> Modeling optional activities, alternative execution modes for activities, and … most of the discrete decisions in a schedule</blockquote> \n",
"<li> Example:<br>\n",
"<blockquote><code><font color=green>interval_var(</font><font color=red>optional=True</font><font color=green>, start=(0,1000), end=(0,1000), size=(10,20))</font></code>\n",
"</blockquote> \n",
"<li>Properties:\n",
"<ul>\n",
"<li>An optional interval variable has an additional possible value in its domain (absence value)\n",
"<li>**Optionality** is a powerful property that you must learn to leverage in your models\n",
"</ul>\n",
"</ul>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"For each house, an __optional__ interval variable is created for each skill.<br>\n",
"Skill being a tuple (worker, task, level), this means that for each house, an __optional__ interval variable is created for each couple worker-task such that the skill level of this worker for this task is > 0.<p>\n",
"The \"**set_optional()**\" specifier allows a choice between different variables, thus between different couples house-skill.\n",
"This means that the engine decides if the interval will be present or absent in the solution."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"wtasks = {} # dict of interval variable for each house and skill\n",
"for house in HOUSES:\n",
" for skill in SKILLS:\n",
" iv = interval_var(name='H' + str(house) + '-' + skill.task + '(' + skill.worker + ')')\n",
" iv.set_optional()\n",
" wtasks[(house, skill)] = iv"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Define constraints"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<h3>Temporal constraints</h3>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<h3><i><font color=blue>Concept: precedence constraint</font></i></h3>\n",
"<p>\n",
"<ul>\n",
"<li> What for?<br>\n",
"<ul>\n",
"<li>Modeling temporal constraints between interval variables\n",
"<li>Modeling constant or variable minimal delays\n",
"</ul>\n",
"<li>Properties\n",
"<blockquote>Semantic of the constraints handles optionality (as for all constraints in CP Optimizer).<br>\n",
"Example of endBeforeStart:<br>\n",
"<code><font color=green>end_before_start(a,b,z)</font></code><br>\n",
"present(a) <font color=red>AND</font> present(b) &Implies; end(a)+z &LessSlantEqual; start(b) \n",
"</blockquote>\n",
"<ul>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The tasks in the model have precedence constraints that are added to the model."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"for h in HOUSES:\n",
" for p in TASK_PRECEDENCES:\n",
" mdl.add(end_before_start(tasks[(h, find_tasks(p.beforeTask))], tasks[(h, find_tasks(p.afterTask))]))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<h3>Alternative workers</h3>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<h3><i><font color=blue>Concept: alternative constraint</font></i></h3>\n",
"<p>\n",
"<ul>\n",
"<li> What for?<br>\n",
"<ul>\n",
"<li>Modeling alternative resource/modes/recipes \n",
"<li>In general modeling a discrete selection in the schedule \n",
"</ul>\n",
"<li> Example:<br>\n",
"<blockquote><code><font color=green>alternative(a,[b1,...,bn])</font></code>\n",
"</blockquote> \n",
"<img src = \"./house_building_utils/alternative.png\" >\n",
"<li>Remark: Master interval variable **a** can of course be optional\n",
"</ul>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To constrain the solution so that exactly one of the interval variables wtasks associated with a given task of a given house is to be present in the solution, an \"**alternative**\" constraint is used."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"for h in HOUSES:\n",
" for t in TASKS:\n",
" mdl.add(alternative(tasks[(h, t)], [wtasks[(h, s)] for s in SKILLS if (s.task == t.name)], 1))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<h3>No overlap constraint</h3>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<h3><i><font color=blue>Concept: No-overlap constraint</font></i></h3>\n",
"<p>\n",
"<ul>\n",
"<li> Constraint noOverlap schedules a group of interval variables in such a way that they do not overlap in time.\n",
"<li> Absent interval variables are ignored.\n",
"<li>It is possible to constrain minimum delays between intervals using transition matrix.\n",
"<li>It is possible to constraint the first, last in the sequence or next or preceding interval\n",
"</ul>\n",
"<img src = \"./house_building_utils/noOverlap.png\" >"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To add the constraints that a given worker can be assigned only one task at a given moment in time, a **noOverlap** constraint is used."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"for w in WORKERS:\n",
" mdl.add(no_overlap([wtasks[(h, s)] for h in HOUSES for s in SKILLS if s.worker == w]))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Define objective"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The presence of an interval variable in wtasks in the solution must be accounted for in the objective. Thus for each of these possible tasks, the cost is incremented by the product of the skill level and the expression representing the presence of the interval variable in the solution.<p>\n",
"The objective of this problem is to maximize the skill level used for all the tasks."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"obj = sum([s.level * presence_of(wtasks[(h, s)]) for s in SKILLS for h in HOUSES])\n",
"mdl.add(maximize(obj))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Solve model"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The model is now completely defined. It is time to solve it !\n",
"<p>\n",
"To use the CP Optimizer solver available on the IBM Decision Optimization on Cloud service:\n",
"<ul>\n",
"<li> Register for the DOcloud free trial and use it free for 30 days by using https://developer.ibm.com/docloud/try-docloud-free\n",
"<li> Get your access credentials (base URL and access key) by going this page: http://developer.ibm.com/docloud/docs/api-key/\n",
"</ul>\n",
"<p>"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"# Initialize IBM Decision Optimization credentials\n",
"SVC_URL = \"https://api-oaas.docloud.ibmcloud.com/job_manager/rest/v1/\"\n",
"SVC_KEY = \"ENTER YOUR KEY HERE\""
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"# Solve the model\n",
"print(\"\\nSolving model....\")\n",
"msol = mdl.solve(url=SVC_URL, key=SVC_KEY, trace_log=True)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Display solution"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"print(\"Solve status: \" + msol.get_solve_status())\n",
"if msol.is_solution():\n",
" stdout.write(\"Solve time: \" + str(msol.get_solve_time()) + \"\\n\")\n",
" # Sort tasks in increasing begin order\n",
" ltasks = []\n",
" for hs in HOUSES:\n",
" for tsk in TASKS:\n",
" (beg, end, dur) = msol[tasks[(hs, tsk)]]\n",
" ltasks.append((hs, tsk, beg, end, dur))\n",
" ltasks = sorted(ltasks, key = lambda x : x[2])\n",
" # Print solution\n",
" print(\"\\nList of tasks in increasing start order:\")\n",
" for tsk in ltasks:\n",
" print(\"From \" + str(tsk[2]) + \" to \" + str(tsk[3]) + \", \" + tsk[1].name + \" in house \" + str(tsk[0]))\n",
"else:\n",
" stdout.write(\"No solution found\\n\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Import graphical tools"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"*You can set __POP\\_UP\\_GRAPHIC=True__ if you prefer a pop up graphic window instead of an inline one.*"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"POP_UP_GRAPHIC=False"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"import _utils_visu as visu\n",
"import matplotlib.pyplot as plt\n",
"if not POP_UP_GRAPHIC:\n",
" %matplotlib inline\n",
"#Change the plot size\n",
"from pylab import rcParams\n",
"rcParams['figure.figsize'] = 15, 3"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Draw solution"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Useful functions "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"With the aim to facilitate the display of tasks names, we keep only the n first characters."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def compact_name(name,n): return name[:n]"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"if msol and visu.is_visu_enabled():\n",
" workers_colors = {}\n",
" workers_colors[\"Joe\"] = 'lightblue'\n",
" workers_colors[\"Jack\"] = 'violet'\n",
" workers_colors[\"Jim\"] = 'lightgreen'\n",
" visu.timeline('Solution per houses', 0, MAX_AMOUNT_OF_PERIODS)\n",
" for h in HOUSES:\n",
" visu.sequence(name=\"house \" + str(h))\n",
" for s in SKILLS:\n",
" wt = msol.get_var_solution(wtasks[(h,s)])\n",
" if wt.is_present():\n",
" color = workers_colors[s.worker]\n",
" wtname = compact_name(s.task,2)\n",
" visu.interval(wt, color, wtname)\n",
" visu.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The purpose of this function is to compact the names of the different tasks with the aim of making the graphical display readable. </p>\n",
"For example \"H3-garden\" becomes \"G3\""
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"def compact_house_task(name):\n",
" loc, task = name[1:].split('-', 1)\n",
" return task[0].upper() + loc"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Green-like color when task is using the most skilled worker\n",
"Red-like color when task does not use the most skilled worker"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false,
"scrolled": false
},
"outputs": [],
"source": [
"if msol and visu.is_visu_enabled():\n",
" visu.timeline('Solution per workers', 0, MAX_AMOUNT_OF_PERIODS)\n",
" for w in WORKERS:\n",
" visu.sequence(name=w)\n",
" for h in HOUSES:\n",
" for s in SKILLS:\n",
" if s.worker == w:\n",
" wt = msol.get_var_solution(wtasks[(h,s)])\n",
" if wt.is_present():\n",
" ml = find_max_level_skill(s.task).level\n",
" if s.level == ml:\n",
" color = 'lightgreen'\n",
" else:\n",
" color = 'salmon'\n",
" wtname = compact_house_task(wt.get_name())\n",
" visu.interval(wt, color, wtname)\n",
" visu.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<h3>Going further with Constraint Programming</h3>\n",
"<p>\n",
"The last available installable package is available on Pypi here: https://pypi.python.org/pypi/docplex\n",
"<p>\n",
"Downloadable documentation is here: https://github.com/IBMDecisionOptimization/docplex-doc, and an inline version is available here: http://rawgit.com/IBMDecisionOptimization/docplex-doc/master/docs/index.html\n",
"<p>\n",
"A complete set of modeling examples can be downloaded here: https://github.com/IBMDecisionOptimization/docplex-examples "
]
}
],
"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.10"
}
},
"nbformat": 4,
"nbformat_minor": 0
}

View File

@ -0,0 +1,267 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"collapsed": true
},
"source": [
"# <center><font color = 'blue'> The eight queens puzzle"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": true
},
"source": [
"The eight queens puzzle is the problem of placing eight chess queens on an 8x8\n",
"chessboard so that no two queens threaten each other. Thus, a solution requires\n",
"that no two queens share the same row, column, or diagonal.\n",
"\n",
"The eight queens puzzle is an example of the more general n-queens problem of\n",
"placing n queens on an nxn chessboard, where solutions exist for all natural\n",
"numbers n with the exception of n=2 and n=3."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## References\n",
"* https://en.wikipedia.org/wiki/Eight_queens_puzzle"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"try:\n",
" import docplex.cp\n",
"except:\n",
" !pip install docplex"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"from docplex.cp.model import *\n",
"from sys import stdout"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Create model"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Set model parameter"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"NB_QUEEN = 8"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Create CPO model"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"mdl = CpoModel()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"# Create column index of each queen\n",
"x = integer_var_list(NB_QUEEN, 0, NB_QUEEN - 1, \"X\")\n",
"\n",
"# One queen per raw\n",
"mdl.add(all_diff(x))\n",
"\n",
"# One queen per diagonal xi - xj != i - j\n",
"mdl.add(all_diff(x[i] + i for i in range(NB_QUEEN)))\n",
"\n",
"# One queen per diagonal xi - xj != j - i\n",
"mdl.add(all_diff(x[i] - i for i in range(NB_QUEEN)))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Solve model"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"__Set your DOcloud credentials:__\n",
"0. A first option is to set the DOcloud url and key directly in the model source file *(see below)*\n",
"1. For a persistent setting, create a Python file __docloud_config.py__ somewhere that is visible from the __PYTHONPATH__"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"print(\"\\nSolving model....\")\n",
"msol = mdl.solve(url=\"https://api-oaas.docloud.ibmcloud.com/job_manager/rest/v1/\", \n",
" key=\"ENTER YOUR KEY HERE\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Display solution"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Import required external libraries (numpy and matplotlib)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"try:\n",
" import numpy as np\n",
" import matplotlib.pyplot as plt\n",
" VISU_ENABLED = True\n",
"except ImportError:\n",
" VISU_ENABLED = False"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"def display(sol):\n",
" %matplotlib inline\n",
" \n",
" chess_board = np.zeros((NB_QUEEN, NB_QUEEN, 3))\n",
" black = 0.5\n",
" white = 1\n",
" for l in range(8):\n",
" for c in range(8):\n",
" if (l%2 == c%2):\n",
" col = white\n",
" else:\n",
" col = black\n",
" chess_board[l,c,::]=col\n",
"\n",
" fig, ax = plt.subplots()\n",
" ax.imshow(chess_board, interpolation='none')\n",
" wq_im_file = \"./n_queen_utils/WQueen.png\"\n",
" bq_im_file = \"./n_queen_utils/BQueen.png\"\n",
" wq = plt.imread(wq_im_file)\n",
" bq = plt.imread(bq_im_file)\n",
" for y, x in enumerate(sol):\n",
" if (x%2 == y%2):\n",
" queen = bq\n",
" else:\n",
" queen = wq \n",
" ax.imshow(queen, extent=[x-0.4, x+0.4, y-0.4, y+0.4])\n",
" ax.set(xticks=[], yticks=[])\n",
" ax.axis('image')\n",
" plt.show()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false,
"scrolled": true
},
"outputs": [],
"source": [
"if msol: \n",
" stdout.write(\"Solution:\")\n",
" sol = [msol[v] for v in x]\n",
" for v in range(NB_QUEEN):\n",
" stdout.write(\" \" + str(sol[v]))\n",
" stdout.write(\"\\n\")\n",
" stdout.write(\"Solve time: \" + str(msol.get_solve_time()) + \"\\n\")\n",
" if VISU_ENABLED:\n",
" display(sol)\n",
"else:\n",
" stdout.write(\"No solution found\\n\")"
]
}
],
"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.10"
}
},
"nbformat": 4,
"nbformat_minor": 0
}

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,358 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# <center><font color = 'blue'> Sched Square"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The aim of the square example is to place a set of small squares of\n",
"different sizes into a large square."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Import lib"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"try:\n",
" import docplex.cp\n",
"except:\n",
" !pip install docplex"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"from docplex.cp.model import *"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Data"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Size of the englobing square"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"SIZE_SQUARE = 112"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Sizes of the sub-squares"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"SIZE_SUBSQUARE = [50, 42, 37, 35, 33, 29, 27, 25, 24, 19, 18, 17, 16, 15, 11, 9, 8, 7, 6, 4, 2]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Modeling"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Create CPO model"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"mdl = CpoModel()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Create array of variables for sub-squares"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"x = []\n",
"y = []\n",
"rx = pulse(0, 0, 0)\n",
"ry = pulse(0, 0, 0)\n",
"\n",
"for i in range(len(SIZE_SUBSQUARE)):\n",
" sq = SIZE_SUBSQUARE[i]\n",
" vx = interval_var(size=sq, name=\"X\" + str(i))\n",
" vx.set_end((0, SIZE_SQUARE))\n",
" x.append(vx)\n",
" rx += pulse(vx, sq)\n",
"\n",
" vy = interval_var(size=sq, name=\"Y\" + str(i))\n",
" vy.set_end((0, SIZE_SQUARE))\n",
" y.append(vy)\n",
" ry += pulse(vy, sq)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Create dependencies between variables"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"for i in range(len(SIZE_SUBSQUARE)):\n",
" for j in range(i):\n",
" mdl.add((end_of(x[i]) <= start_of(x[j]))\n",
" | (end_of(x[j]) <= start_of(x[i]))\n",
" | (end_of(y[i]) <= start_of(y[j]))\n",
" | (end_of(y[j]) <= start_of(y[i])))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Set other constraints"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"mdl.add(always_in(rx, 0, SIZE_SQUARE, SIZE_SQUARE, SIZE_SQUARE))\n",
"mdl.add(always_in(ry, 0, SIZE_SQUARE, SIZE_SQUARE, SIZE_SQUARE))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Solve the model"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Define search phases"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"mdl.set_search_phases([search_phase(x), search_phase(y)])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Set your DOcloud credentials:\n",
"0. A first option is to set the DOcloud url and key directly in the model source file *(see below)*\n",
"1. For a persistent setting, create a Python file __docloud_config.py__ somewhere that is visible from the __PYTHONPATH__"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"msol = mdl.solve(url=\"https://api-oaas.docloud.ibmcloud.com/job_manager/rest/v1/\", \n",
" key=\"ENTER YOUR KEY HERE\",\n",
" TimeLimit=20,\n",
" LogPeriod=50000)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Display Solution"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Print Solution"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"print(\"Solution: \")\n",
"msol.print_solution()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Import graphical tools"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"import _utils_visu as visu\n",
"import matplotlib.pyplot as plt"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"*You can set __POP\\_UP\\_GRAPHIC=True__ if you prefer a pop up graphic window instead of an inline one.*"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"POP_UP_GRAPHIC=False"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"if msol and visu.is_visu_enabled():\n",
" import matplotlib.cm as cm\n",
" from matplotlib.patches import Polygon\n",
" \n",
" if not POP_UP_GRAPHIC:\n",
" %matplotlib inline\n",
" \n",
" # Plot external square\n",
" print(\"Plotting squares....\")\n",
" fig, ax = plt.subplots()\n",
" plt.plot((0, 0), (0, SIZE_SQUARE), (SIZE_SQUARE, SIZE_SQUARE), (SIZE_SQUARE, 0))\n",
" for i in range(len(SIZE_SUBSQUARE)):\n",
" # Display square i\n",
" (sx, sy) = (msol.get_var_solution(x[i]), msol.get_var_solution(y[i]))\n",
" (sx1, sx2, sy1, sy2) = (sx.get_start(), sx.get_end(), sy.get_start(), sy.get_end())\n",
" poly = Polygon([(sx1, sy1), (sx1, sy2), (sx2, sy2), (sx2, sy1)], fc=cm.Set2(float(i) / len(SIZE_SUBSQUARE)))\n",
" ax.add_patch(poly)\n",
" # Display identifier of square i at its center\n",
" ax.text(float(sx1 + sx2) / 2, float(sy1 + sy2) / 2, str(SIZE_SUBSQUARE[i]), ha='center', va='center')\n",
" plt.margins(0)\n",
" plt.show()\n"
]
}
],
"metadata": {
"celltoolbar": "Raw Cell Format",
"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.10"
}
},
"nbformat": 4,
"nbformat_minor": 0
}

View File

@ -0,0 +1,460 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# <center><font color = 'blue'> Sudoku"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Sudoku is a logic-based, combinatorial number-placement puzzle.\n",
"The objective is to fill a 9x9 grid with digits so that each column, each row,\n",
"and each of the nine 3x3 sub-grids that compose the grid contains all of the digits from 1 to 9.\n",
"The puzzle setter provides a partially completed grid, which for a well-posed puzzle has a unique solution."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## References\n",
"* See https://en.wikipedia.org/wiki/Sudoku for details"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Import lib"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"try:\n",
" import docplex.cp\n",
"except:\n",
" !pip install docplex"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"from docplex.cp.model import *\n",
"from sys import stdout"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Problem data"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Grid range"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"GRNG = range(9)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Different problems"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"_zero means cell to be filled with appropriate value_"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"SUDOKU_PROBLEM_1 = ( (0, 0, 0, 0, 9, 0, 1, 0, 0),\n",
" (2, 8, 0, 0, 0, 5, 0, 0, 0),\n",
" (7, 0, 0, 0, 0, 6, 4, 0, 0),\n",
" (8, 0, 5, 0, 0, 3, 0, 0, 6),\n",
" (0, 0, 1, 0, 0, 4, 0, 0, 0),\n",
" (0, 7, 0, 2, 0, 0, 0, 0, 0),\n",
" (3, 0, 0, 0, 0, 1, 0, 8, 0),\n",
" (0, 0, 0, 0, 0, 0, 0, 5, 0),\n",
" (0, 9, 0, 0, 0, 0, 0, 7, 0),\n",
" )\n",
"\n",
"SUDOKU_PROBLEM_2 = ( (0, 7, 0, 0, 0, 0, 0, 4, 9),\n",
" (0, 0, 0, 4, 0, 0, 0, 0, 0),\n",
" (4, 0, 3, 5, 0, 7, 0, 0, 8),\n",
" (0, 0, 7, 2, 5, 0, 4, 0, 0),\n",
" (0, 0, 0, 0, 0, 0, 8, 0, 0),\n",
" (0, 0, 4, 0, 3, 0, 5, 9, 2),\n",
" (6, 1, 8, 0, 0, 0, 0, 0, 5),\n",
" (0, 9, 0, 1, 0, 0, 0, 3, 0),\n",
" (0, 0, 5, 0, 0, 0, 0, 0, 7),\n",
" )\n",
"\n",
"SUDOKU_PROBLEM_3 = ( (0, 0, 0, 0, 0, 6, 0, 0, 0),\n",
" (0, 5, 9, 0, 0, 0, 0, 0, 8),\n",
" (2, 0, 0, 0, 0, 8, 0, 0, 0),\n",
" (0, 4, 5, 0, 0, 0, 0, 0, 0),\n",
" (0, 0, 3, 0, 0, 0, 0, 0, 0),\n",
" (0, 0, 6, 0, 0, 3, 0, 5, 4),\n",
" (0, 0, 0, 3, 2, 5, 0, 0, 6),\n",
" (0, 0, 0, 0, 0, 0, 0, 0, 0),\n",
" (0, 0, 0, 0, 0, 0, 0, 0, 0)\n",
" )"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"try:\n",
" import numpy as np\n",
" import matplotlib.pyplot as plt\n",
" VISU_ENABLED = True\n",
"except ImportError:\n",
" VISU_ENABLED = False"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"def print_grid(grid):\n",
" \"\"\" Print Sudoku grid \"\"\"\n",
" for l in GRNG:\n",
" if (l > 0) and (l % 3 == 0):\n",
" stdout.write('\\n')\n",
" for c in GRNG:\n",
" v = grid[l][c]\n",
" stdout.write(' ' if (c % 3 == 0) else ' ')\n",
" stdout.write(str(v) if v > 0 else '.')\n",
" stdout.write('\\n')"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def draw_grid(values):\n",
" %matplotlib inline\n",
" fig, ax = plt.subplots(figsize =(4,4))\n",
" min_val, max_val = 0, 9\n",
" R = range(0,9)\n",
" for l in R:\n",
" for c in R:\n",
" v = values[c][l]\n",
" s = \" \"\n",
" if v > 0:\n",
" s = str(v)\n",
" ax.text(l+0.5,8.5-c, s, va='center', ha='center')\n",
" ax.set_xlim(min_val, max_val)\n",
" ax.set_ylim(min_val, max_val)\n",
" ax.set_xticks(np.arange(max_val))\n",
" ax.set_yticks(np.arange(max_val))\n",
" ax.grid()\n",
" plt.show()\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def display_grid(grid, name):\n",
" stdout.write(name)\n",
" stdout.write(\":\\n\")\n",
" if VISU_ENABLED:\n",
" draw_grid(grid)\n",
" else:\n",
" print_grid(grid)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Create model"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Create CPO model"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"mdl = CpoModel()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Create grid of variables"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"grid = [[integer_var(min=1, max=9, name=\"C\" + str(l) + str(c)) for l in GRNG] for c in GRNG]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Set constraints"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Add alldiff constraints for lines"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"for l in GRNG:\n",
" mdl.add(all_diff([grid[l][c] for c in GRNG]))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Add alldiff constraints for columns"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"for c in GRNG:\n",
" mdl.add(all_diff([grid[l][c] for l in GRNG]))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Add alldiff constraints for sub-squares"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"ssrng = range(0, 9, 3)\n",
"for sl in ssrng:\n",
" for sc in ssrng:\n",
" mdl.add(all_diff([grid[l][c] for l in range(sl, sl + 3) for c in range(sc, sc + 3)]))"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"display_grid(SUDOKU_PROBLEM_1, \"PROBLEM 1\")\n",
"display_grid(SUDOKU_PROBLEM_2, \"PROBLEM 2\")\n",
"display_grid(SUDOKU_PROBLEM_3, \"PROBLEM 3\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Chose your prefered problem (SUDOKU_PROBLEM_1 or SUDOKU_PROBLEM_2 or SUDOKU_PROBLEM_3)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"problem = SUDOKU_PROBLEM_1"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Initialize known cells"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"for l in GRNG:\n",
" for c in GRNG:\n",
" v = problem[l][c]\n",
" if v > 0:\n",
" grid[l][c].set_domain((v, v))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Solve model"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"__Set your DOcloud credentials:__\n",
"0. A first option is to set the DOcloud url and key directly in the model source file *(see below)*\n",
"1. For a persistent setting, create a Python file __docloud_config.py__ somewhere that is visible from the __PYTHONPATH__"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"print(\"\\nSolving model....\")\n",
"msol = mdl.solve(url=\"https://api-oaas.docloud.ibmcloud.com/job_manager/rest/v1/\", \n",
" key=\"ENTER YOUR KEY HERE\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Display solution"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"display_grid(problem, \"Initial problem\")\n",
"if msol:\n",
" sol = [[msol[grid[l][c]] for c in GRNG] for l in GRNG]\n",
" stdout.write(\"Solve time: \" + str(msol.get_solve_time()) + \"\\n\")\n",
" display_grid(sol, \"Solution\")\n",
"else:\n",
" stdout.write(\"No solution found\\n\")"
]
}
],
"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.10"
}
},
"nbformat": 4,
"nbformat_minor": 0
}

View File

@ -0,0 +1,298 @@
{
"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": [
"<center>\n",
"<img src=\"http://puzzlor.com/images/CampSitePicture.png\"> \n",
"</center>"
]
},
{
"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
}

View File

@ -0,0 +1,444 @@
{
"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": {},
"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": {},
"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": {},
"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": {},
"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": {},
"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=\"ENTER YOUR URL\", \n",
" key=\"ENTER YOUR KEY\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"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": {},
"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": {},
"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": {},
"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": {},
"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": {},
"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": {},
"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": [
"<center>\n",
"<img src=\"http://puzzlor.com/images/NeighborhoodMap.gif\"> \n",
"</center>"
]
},
{
"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": {},
"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": {},
"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": []
}
],
"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
}

View File

@ -0,0 +1,254 @@
{
"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": [
"<center>\n",
"<img src=\"http://puzzlor.com/images/2011-06%20Matchmaker_v3.gif\"> \n",
"</center>"
]
},
{
"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
}

View File

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

View File

@ -0,0 +1,606 @@
{
"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='best',\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
}

9
info.json 100644
View File

@ -0,0 +1,9 @@
{
"provider": ["IBM"],
"name": "Decision Optimization Modeling for Python (DOcplex) samples" ,
"model": [ "CPLEX", "CPO" ],
"development": [ "Python" ],
"category": [ {"MODELING":["ADVANCED MODELING"]} ],
"industry": [ "Operational research" ],
"abstract": "These samples demonstrate how to use the DOcplex library to model and solve optimization problems."
}