-
Notifications
You must be signed in to change notification settings - Fork 230
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
1,073 additions
and
469 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
# -------------------------------------------------------------------------- | ||
# Source file provided under Apache License, Version 2.0, January 2004, | ||
# http://www.apache.org/licenses/ | ||
# (c) Copyright IBM Corp. 2015, 2016, 2018, 2020 | ||
# -------------------------------------------------------------------------- | ||
|
||
""" | ||
A ship-building company has a certain number of customers. Each customer is supplied | ||
by exactly one plant. In turn, a plant can supply several customers. The problem is | ||
to decide where to set up the plants in order to supply every customer while minimizing | ||
the cost of building each plant and the transportation cost of supplying the customers. | ||
For each possible plant location there is a fixed cost and a production capacity. | ||
Both take into account the country and the geographical conditions. | ||
For every customer, there is a demand and a transportation cost with respect to | ||
each plant location. | ||
While a first solution of this problem can be found easily by CP Optimizer, it can take | ||
quite some time to improve it to a very good one. We illustrate the warm start capabilities | ||
of CP Optimizer by giving a good starting point solution that CP Optimizer will try to improve. | ||
This solution could be one from an expert or the result of another optimization engine | ||
applied to the problem. | ||
In the solution we only give a value to the variables that determine which plant delivers | ||
a customer. This is sufficient to define a complete solution on all model variables. | ||
CP Optimizer first extends the solution to all variables and then starts to improve it. | ||
The solve is enriched with a CPO callback, available from version of COS greater or equal to 12.10.0.0. | ||
This callback displays various information generated during the solve, in particular intermediate | ||
solutions that are found before the end of the solve. | ||
""" | ||
|
||
from docplex.cp.model import CpoModel | ||
import docplex.cp.solver.solver as solver | ||
from docplex.cp.utils import compare_natural | ||
from collections import deque | ||
import os | ||
from docplex.cp.solver.cpo_callback import CpoCallback | ||
|
||
|
||
#----------------------------------------------------------------------------- | ||
# Initialize the problem data | ||
#----------------------------------------------------------------------------- | ||
|
||
# Read problem data from a file and convert it as a list of integers | ||
filename = os.path.dirname(os.path.abspath(__file__)) + "/data/plant_location.data" | ||
data = deque() | ||
with open(filename, "r") as file: | ||
for val in file.read().split(): | ||
data.append(int(val)) | ||
|
||
# Read number of customers and locations | ||
nbCustomer = data.popleft() | ||
nbLocation = data.popleft() | ||
|
||
# Initialize cost. cost[c][p] = cost to deliver customer c from plant p | ||
cost = list([list([data.popleft() for l in range(nbLocation)]) for c in range(nbCustomer)]) | ||
|
||
# Initialize demand of each customer | ||
demand = list([data.popleft() for c in range(nbCustomer)]) | ||
|
||
# Initialize fixed cost of each location | ||
fixedCost = list([data.popleft() for p in range(nbLocation)]) | ||
|
||
# Initialize capacity of each location | ||
capacity = list([data.popleft() for p in range(nbLocation)]) | ||
|
||
|
||
#----------------------------------------------------------------------------- | ||
# Build the model | ||
#----------------------------------------------------------------------------- | ||
|
||
mdl = CpoModel() | ||
|
||
# Create variables identifying which location serves each customer | ||
cust = mdl.integer_var_list(nbCustomer, 0, nbLocation - 1, "CustomerLocation") | ||
|
||
# Create variables indicating which plant location is open | ||
open = mdl.integer_var_list(nbLocation, 0, 1, "OpenLocation") | ||
|
||
# Create variables indicating load of each plant | ||
load = [mdl.integer_var(0, capacity[p], "PlantLoad_" + str(p)) for p in range(nbLocation)] | ||
|
||
# Associate plant openness to its load | ||
for p in range(nbLocation): | ||
mdl.add(open[p] == (load[p] > 0)) | ||
|
||
# Add constraints | ||
mdl.add(mdl.pack(load, cust, demand)) | ||
|
||
# Add objective | ||
obj = mdl.scal_prod(fixedCost, open) | ||
for c in range(nbCustomer): | ||
obj += mdl.element(cust[c], cost[c]) | ||
mdl.add(mdl.minimize(obj)) | ||
|
||
|
||
#----------------------------------------------------------------------------- | ||
# Solve the model, tracking objective with a callback | ||
#----------------------------------------------------------------------------- | ||
|
||
class MyCallback(CpoCallback): | ||
def invoke(self, solver, event, jsol): | ||
# Get important elements | ||
obj_val = jsol.get_objective_values() | ||
obj_bnds = jsol.get_objective_bounds() | ||
obj_gaps = jsol.get_objective_gaps() | ||
solvests = jsol.get_solve_status() | ||
srchsts = jsol.get_search_status() | ||
#allvars = jsol.get_solution().get_all_var_solutions() if jsol.is_solution() else None | ||
solve_time = jsol.get_info('SolveTime') | ||
memory = jsol.get_info('MemoryUsage') | ||
print("CALLBACK: {}: {}, {}, objective: {} bounds: {}, gaps: {}, time: {}, memory: {}".format(event, solvests, srchsts, obj_val, obj_bnds, obj_gaps, solve_time, memory)) | ||
|
||
if compare_natural(solver.get_solver_version(), '12.10') >= 0: | ||
mdl.add_solver_callback(MyCallback()) | ||
|
||
# Solve the model | ||
print("Solve the model") | ||
msol = mdl.solve(TimeLimit=10) | ||
msol.write() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
# -------------------------------------------------------------------------- | ||
# Source file provided under Apache License, Version 2.0, January 2004, | ||
# http://www.apache.org/licenses/ | ||
# (c) Copyright IBM Corp. 2020 | ||
# -------------------------------------------------------------------------- | ||
|
||
""" | ||
The trim loss problems arises in the paper industry. | ||
The problem is to cut wide papers rolls into sub rolls (orders). | ||
The wide roll are cut into pieces with a cutting pattern. | ||
A cutting pattern defines the blades positions for cutting the roll. | ||
A maximum number of orders is allowed in a cutting pattern (here it is 6). | ||
When cutting a wide roll, we can have a loss of paper that is wasted. | ||
This loss is contrained to be not more than a given value (here it is 100) | ||
An order is characterised by a demand, a roll width, and a maximum number of | ||
time it can appear in a cutting pattern. | ||
The goal is to meet the demand while minimizing the roll used and the number | ||
of different cutting patterns used for production. | ||
In this example we also use: | ||
- extra constraints to avoid assigning orders to unused patterns, | ||
- lexicographic constraints to break symmetries between cutting patterns | ||
- strong constraints to have a better domain reduction by enumerating possible | ||
patterns configurations | ||
All this makes the proof of optimality rather fast. | ||
""" | ||
|
||
from docplex.cp.model import * | ||
from sys import stdout | ||
|
||
#----------------------------------------------------------------------------- | ||
# Initialize the problem data | ||
#----------------------------------------------------------------------------- | ||
|
||
# Data | ||
ROLL_WIDTH = 2200 # Width of roll to be cutted into pieces | ||
MAX_WASTE = 100 # Maximum waste per roll | ||
MAX_ORDER_PER_CUT = 5 # Maximum number of order per cutting pattern | ||
|
||
# Orders demand, width and max occurence in a cutting pattern | ||
ORDER_DEMAND = ( 8, 16, 12, 7, 14, 16) | ||
ORDER_WIDTH = (330, 360, 380, 430, 490, 530) | ||
ORDER_MAX_REPEAT = ( 2, 3, 3, 5, 3, 4) | ||
# Number of different order types | ||
NUM_ORDER_TYPE = len(ORDER_DEMAND) | ||
# Maximum number of cutting pattern | ||
NUM_PATTERN_TYPE = 6 | ||
# Maximum of time a cutting pattern is used | ||
MAX_PATTERN_USAGE = 16 | ||
# Cost of using a pattern | ||
PATTERN_COST = 0.1 | ||
# Cost of a roll | ||
ROLL_COST = 1 | ||
|
||
PATTERNS = range(NUM_PATTERN_TYPE) | ||
ORDERS = range(NUM_ORDER_TYPE) | ||
|
||
|
||
#----------------------------------------------------------------------------- | ||
# Build the model | ||
#----------------------------------------------------------------------------- | ||
|
||
model = CpoModel() | ||
|
||
# Decision variables : pattern usage | ||
patternUsage = [model.integer_var(0, MAX_PATTERN_USAGE, "PatternUsage_"+str(p)) for p in PATTERNS] | ||
|
||
# Decision variables : order quantity per pattern | ||
x = [[model.integer_var(0, max, "x["+str(o)+","+str(p)+"]") | ||
for (o, max) in enumerate(ORDER_MAX_REPEAT)] | ||
for p in PATTERNS] | ||
|
||
# Maximum number of orders per cutting pattern | ||
for p in PATTERNS : | ||
model.add(sum(x[p]) <= MAX_ORDER_PER_CUT) | ||
|
||
# Roll capacity | ||
usage = [0] + [v for v in range(ROLL_WIDTH - MAX_WASTE, ROLL_WIDTH+1)] # usage is [0, 2100..2200] | ||
rollUsage = [model.integer_var(domain = usage, name = "RollUsage_"+str(p)) for p in PATTERNS] | ||
|
||
for p in PATTERNS : | ||
model.add(sum(ORDER_WIDTH[o] * x[p][o] for o in ORDERS) == rollUsage[p]) | ||
|
||
# Production requirement | ||
for o in ORDERS : | ||
model.add(model.sum(x[p][o] * patternUsage[p] for p in PATTERNS) >= ORDER_DEMAND[o]) | ||
|
||
# Objective | ||
model.add(minimize(model.sum((patternUsage[p] > 0) * PATTERN_COST + patternUsage[p] * ROLL_COST | ||
for p in PATTERNS))) | ||
|
||
# Extra constraint to avoid assigning orders to an unused pattern | ||
for p in PATTERNS : | ||
model.add((patternUsage[p] == 0) == (rollUsage[p] == 0)) | ||
|
||
# Extra lexicographic constraint to break symmetries | ||
for p in range(NUM_PATTERN_TYPE - 1) : | ||
model.add(model.lexicographic([patternUsage[p]] + x[p], [patternUsage[p+1]] + x[p+1])) | ||
|
||
# Strong constraints to improve the time to prove optimality | ||
for p in PATTERNS : | ||
model.add(model.strong(x[p])) | ||
|
||
# KPIs : Number of rolls, of pattern used and total loss of paper | ||
model.add_kpi(model.sum([patternUsage[p] for p in PATTERNS]), "Rolls") | ||
model.add_kpi(model.sum([(patternUsage[p] > 0) for p in PATTERNS]), "Patterns") | ||
model.add_kpi(model.sum([patternUsage[p] * (ROLL_WIDTH - rollUsage[p]) for p in PATTERNS]), "Loss") | ||
|
||
|
||
#----------------------------------------------------------------------------- | ||
# Solve the model and display the result | ||
#----------------------------------------------------------------------------- | ||
|
||
print("Solve the model...") | ||
msol = model.solve(LogPeriod=1000000, TimeLimit=300) | ||
if msol: | ||
print("patternUsage = ") | ||
for p in PATTERNS: | ||
l = ROLL_WIDTH - msol[rollUsage[p]] | ||
stdout.write("Pattern {} , usage = {}, roll usage = {}, loss = {}, orders =".format(p, msol[patternUsage[p]], msol[rollUsage[p]], l)) | ||
for o in ORDERS: | ||
stdout.write(" {}".format(msol[x[p][o]])) | ||
stdout.write('\n') | ||
else: | ||
print("No solution found") |
Large diffs are not rendered by default.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.