Skip to content

Commit

Permalink
release 2.16.196
Browse files Browse the repository at this point in the history
  • Loading branch information
vlkong committed Nov 9, 2020
1 parent a26c8b0 commit 817cd41
Show file tree
Hide file tree
Showing 11 changed files with 1,073 additions and 469 deletions.
122 changes: 122 additions & 0 deletions examples/cp/basic/plant_location_with_cpo_callback.py
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()
129 changes: 129 additions & 0 deletions examples/cp/basic/trimloss.py
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")
580 changes: 377 additions & 203 deletions examples/cp/jupyter/scheduling_tuto.ipynb

Large diffs are not rendered by default.

10 changes: 0 additions & 10 deletions examples/mp/docplexcloud/diet_food.csv

This file was deleted.

10 changes: 0 additions & 10 deletions examples/mp/docplexcloud/diet_food_nutrients.csv

This file was deleted.

8 changes: 0 additions & 8 deletions examples/mp/docplexcloud/diet_nutrients.csv

This file was deleted.

50 changes: 0 additions & 50 deletions examples/mp/docplexcloud/diet_on_docplexcloud.py

This file was deleted.

Loading

0 comments on commit 817cd41

Please sign in to comment.