Skip to content

Commit 817cd41

Browse files
committed
release 2.16.196
1 parent a26c8b0 commit 817cd41

File tree

11 files changed

+1073
-469
lines changed

11 files changed

+1073
-469
lines changed
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# --------------------------------------------------------------------------
2+
# Source file provided under Apache License, Version 2.0, January 2004,
3+
# http://www.apache.org/licenses/
4+
# (c) Copyright IBM Corp. 2015, 2016, 2018, 2020
5+
# --------------------------------------------------------------------------
6+
7+
"""
8+
A ship-building company has a certain number of customers. Each customer is supplied
9+
by exactly one plant. In turn, a plant can supply several customers. The problem is
10+
to decide where to set up the plants in order to supply every customer while minimizing
11+
the cost of building each plant and the transportation cost of supplying the customers.
12+
13+
For each possible plant location there is a fixed cost and a production capacity.
14+
Both take into account the country and the geographical conditions.
15+
16+
For every customer, there is a demand and a transportation cost with respect to
17+
each plant location.
18+
19+
While a first solution of this problem can be found easily by CP Optimizer, it can take
20+
quite some time to improve it to a very good one. We illustrate the warm start capabilities
21+
of CP Optimizer by giving a good starting point solution that CP Optimizer will try to improve.
22+
This solution could be one from an expert or the result of another optimization engine
23+
applied to the problem.
24+
25+
In the solution we only give a value to the variables that determine which plant delivers
26+
a customer. This is sufficient to define a complete solution on all model variables.
27+
CP Optimizer first extends the solution to all variables and then starts to improve it.
28+
29+
The solve is enriched with a CPO callback, available from version of COS greater or equal to 12.10.0.0.
30+
This callback displays various information generated during the solve, in particular intermediate
31+
solutions that are found before the end of the solve.
32+
"""
33+
34+
from docplex.cp.model import CpoModel
35+
import docplex.cp.solver.solver as solver
36+
from docplex.cp.utils import compare_natural
37+
from collections import deque
38+
import os
39+
from docplex.cp.solver.cpo_callback import CpoCallback
40+
41+
42+
#-----------------------------------------------------------------------------
43+
# Initialize the problem data
44+
#-----------------------------------------------------------------------------
45+
46+
# Read problem data from a file and convert it as a list of integers
47+
filename = os.path.dirname(os.path.abspath(__file__)) + "/data/plant_location.data"
48+
data = deque()
49+
with open(filename, "r") as file:
50+
for val in file.read().split():
51+
data.append(int(val))
52+
53+
# Read number of customers and locations
54+
nbCustomer = data.popleft()
55+
nbLocation = data.popleft()
56+
57+
# Initialize cost. cost[c][p] = cost to deliver customer c from plant p
58+
cost = list([list([data.popleft() for l in range(nbLocation)]) for c in range(nbCustomer)])
59+
60+
# Initialize demand of each customer
61+
demand = list([data.popleft() for c in range(nbCustomer)])
62+
63+
# Initialize fixed cost of each location
64+
fixedCost = list([data.popleft() for p in range(nbLocation)])
65+
66+
# Initialize capacity of each location
67+
capacity = list([data.popleft() for p in range(nbLocation)])
68+
69+
70+
#-----------------------------------------------------------------------------
71+
# Build the model
72+
#-----------------------------------------------------------------------------
73+
74+
mdl = CpoModel()
75+
76+
# Create variables identifying which location serves each customer
77+
cust = mdl.integer_var_list(nbCustomer, 0, nbLocation - 1, "CustomerLocation")
78+
79+
# Create variables indicating which plant location is open
80+
open = mdl.integer_var_list(nbLocation, 0, 1, "OpenLocation")
81+
82+
# Create variables indicating load of each plant
83+
load = [mdl.integer_var(0, capacity[p], "PlantLoad_" + str(p)) for p in range(nbLocation)]
84+
85+
# Associate plant openness to its load
86+
for p in range(nbLocation):
87+
mdl.add(open[p] == (load[p] > 0))
88+
89+
# Add constraints
90+
mdl.add(mdl.pack(load, cust, demand))
91+
92+
# Add objective
93+
obj = mdl.scal_prod(fixedCost, open)
94+
for c in range(nbCustomer):
95+
obj += mdl.element(cust[c], cost[c])
96+
mdl.add(mdl.minimize(obj))
97+
98+
99+
#-----------------------------------------------------------------------------
100+
# Solve the model, tracking objective with a callback
101+
#-----------------------------------------------------------------------------
102+
103+
class MyCallback(CpoCallback):
104+
def invoke(self, solver, event, jsol):
105+
# Get important elements
106+
obj_val = jsol.get_objective_values()
107+
obj_bnds = jsol.get_objective_bounds()
108+
obj_gaps = jsol.get_objective_gaps()
109+
solvests = jsol.get_solve_status()
110+
srchsts = jsol.get_search_status()
111+
#allvars = jsol.get_solution().get_all_var_solutions() if jsol.is_solution() else None
112+
solve_time = jsol.get_info('SolveTime')
113+
memory = jsol.get_info('MemoryUsage')
114+
print("CALLBACK: {}: {}, {}, objective: {} bounds: {}, gaps: {}, time: {}, memory: {}".format(event, solvests, srchsts, obj_val, obj_bnds, obj_gaps, solve_time, memory))
115+
116+
if compare_natural(solver.get_solver_version(), '12.10') >= 0:
117+
mdl.add_solver_callback(MyCallback())
118+
119+
# Solve the model
120+
print("Solve the model")
121+
msol = mdl.solve(TimeLimit=10)
122+
msol.write()

examples/cp/basic/trimloss.py

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# --------------------------------------------------------------------------
2+
# Source file provided under Apache License, Version 2.0, January 2004,
3+
# http://www.apache.org/licenses/
4+
# (c) Copyright IBM Corp. 2020
5+
# --------------------------------------------------------------------------
6+
7+
"""
8+
The trim loss problems arises in the paper industry.
9+
10+
The problem is to cut wide papers rolls into sub rolls (orders).
11+
The wide roll are cut into pieces with a cutting pattern.
12+
13+
A cutting pattern defines the blades positions for cutting the roll.
14+
A maximum number of orders is allowed in a cutting pattern (here it is 6).
15+
When cutting a wide roll, we can have a loss of paper that is wasted.
16+
This loss is contrained to be not more than a given value (here it is 100)
17+
18+
An order is characterised by a demand, a roll width, and a maximum number of
19+
time it can appear in a cutting pattern.
20+
21+
The goal is to meet the demand while minimizing the roll used and the number
22+
of different cutting patterns used for production.
23+
24+
In this example we also use:
25+
- extra constraints to avoid assigning orders to unused patterns,
26+
- lexicographic constraints to break symmetries between cutting patterns
27+
- strong constraints to have a better domain reduction by enumerating possible
28+
patterns configurations
29+
All this makes the proof of optimality rather fast.
30+
"""
31+
32+
from docplex.cp.model import *
33+
from sys import stdout
34+
35+
#-----------------------------------------------------------------------------
36+
# Initialize the problem data
37+
#-----------------------------------------------------------------------------
38+
39+
# Data
40+
ROLL_WIDTH = 2200 # Width of roll to be cutted into pieces
41+
MAX_WASTE = 100 # Maximum waste per roll
42+
MAX_ORDER_PER_CUT = 5 # Maximum number of order per cutting pattern
43+
44+
# Orders demand, width and max occurence in a cutting pattern
45+
ORDER_DEMAND = ( 8, 16, 12, 7, 14, 16)
46+
ORDER_WIDTH = (330, 360, 380, 430, 490, 530)
47+
ORDER_MAX_REPEAT = ( 2, 3, 3, 5, 3, 4)
48+
# Number of different order types
49+
NUM_ORDER_TYPE = len(ORDER_DEMAND)
50+
# Maximum number of cutting pattern
51+
NUM_PATTERN_TYPE = 6
52+
# Maximum of time a cutting pattern is used
53+
MAX_PATTERN_USAGE = 16
54+
# Cost of using a pattern
55+
PATTERN_COST = 0.1
56+
# Cost of a roll
57+
ROLL_COST = 1
58+
59+
PATTERNS = range(NUM_PATTERN_TYPE)
60+
ORDERS = range(NUM_ORDER_TYPE)
61+
62+
63+
#-----------------------------------------------------------------------------
64+
# Build the model
65+
#-----------------------------------------------------------------------------
66+
67+
model = CpoModel()
68+
69+
# Decision variables : pattern usage
70+
patternUsage = [model.integer_var(0, MAX_PATTERN_USAGE, "PatternUsage_"+str(p)) for p in PATTERNS]
71+
72+
# Decision variables : order quantity per pattern
73+
x = [[model.integer_var(0, max, "x["+str(o)+","+str(p)+"]")
74+
for (o, max) in enumerate(ORDER_MAX_REPEAT)]
75+
for p in PATTERNS]
76+
77+
# Maximum number of orders per cutting pattern
78+
for p in PATTERNS :
79+
model.add(sum(x[p]) <= MAX_ORDER_PER_CUT)
80+
81+
# Roll capacity
82+
usage = [0] + [v for v in range(ROLL_WIDTH - MAX_WASTE, ROLL_WIDTH+1)] # usage is [0, 2100..2200]
83+
rollUsage = [model.integer_var(domain = usage, name = "RollUsage_"+str(p)) for p in PATTERNS]
84+
85+
for p in PATTERNS :
86+
model.add(sum(ORDER_WIDTH[o] * x[p][o] for o in ORDERS) == rollUsage[p])
87+
88+
# Production requirement
89+
for o in ORDERS :
90+
model.add(model.sum(x[p][o] * patternUsage[p] for p in PATTERNS) >= ORDER_DEMAND[o])
91+
92+
# Objective
93+
model.add(minimize(model.sum((patternUsage[p] > 0) * PATTERN_COST + patternUsage[p] * ROLL_COST
94+
for p in PATTERNS)))
95+
96+
# Extra constraint to avoid assigning orders to an unused pattern
97+
for p in PATTERNS :
98+
model.add((patternUsage[p] == 0) == (rollUsage[p] == 0))
99+
100+
# Extra lexicographic constraint to break symmetries
101+
for p in range(NUM_PATTERN_TYPE - 1) :
102+
model.add(model.lexicographic([patternUsage[p]] + x[p], [patternUsage[p+1]] + x[p+1]))
103+
104+
# Strong constraints to improve the time to prove optimality
105+
for p in PATTERNS :
106+
model.add(model.strong(x[p]))
107+
108+
# KPIs : Number of rolls, of pattern used and total loss of paper
109+
model.add_kpi(model.sum([patternUsage[p] for p in PATTERNS]), "Rolls")
110+
model.add_kpi(model.sum([(patternUsage[p] > 0) for p in PATTERNS]), "Patterns")
111+
model.add_kpi(model.sum([patternUsage[p] * (ROLL_WIDTH - rollUsage[p]) for p in PATTERNS]), "Loss")
112+
113+
114+
#-----------------------------------------------------------------------------
115+
# Solve the model and display the result
116+
#-----------------------------------------------------------------------------
117+
118+
print("Solve the model...")
119+
msol = model.solve(LogPeriod=1000000, TimeLimit=300)
120+
if msol:
121+
print("patternUsage = ")
122+
for p in PATTERNS:
123+
l = ROLL_WIDTH - msol[rollUsage[p]]
124+
stdout.write("Pattern {} , usage = {}, roll usage = {}, loss = {}, orders =".format(p, msol[patternUsage[p]], msol[rollUsage[p]], l))
125+
for o in ORDERS:
126+
stdout.write(" {}".format(msol[x[p][o]]))
127+
stdout.write('\n')
128+
else:
129+
print("No solution found")

examples/cp/jupyter/scheduling_tuto.ipynb

Lines changed: 377 additions & 203 deletions
Large diffs are not rendered by default.

examples/mp/docplexcloud/diet_food.csv

Lines changed: 0 additions & 10 deletions
This file was deleted.

examples/mp/docplexcloud/diet_food_nutrients.csv

Lines changed: 0 additions & 10 deletions
This file was deleted.

examples/mp/docplexcloud/diet_nutrients.csv

Lines changed: 0 additions & 8 deletions
This file was deleted.

examples/mp/docplexcloud/diet_on_docplexcloud.py

Lines changed: 0 additions & 50 deletions
This file was deleted.

0 commit comments

Comments
 (0)