Files
ortools-clone/examples/python/steel_lns.py

262 lines
8.5 KiB
Python
Raw Normal View History

2010-10-14 13:47:55 +00:00
# Copyright 2010 Pierre Schaus pschaus@gmail.com, lperron@google.com
#
# Licensed under the Apache License, Version 2.0 (the 'License');
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an 'AS IS' BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
2016-01-15 01:42:27 +01:00
from __future__ import print_function
import argparse
from ortools.constraint_solver import pywrapcp
2010-10-14 13:47:55 +00:00
import random
parser = argparse.ArgumentParser()
2010-10-14 13:47:55 +00:00
2018-06-11 11:51:18 +02:00
parser.add_argument(
'--data',
default='examples/data/steel_mill/steel_mill_slab.txt',
help='path to data file')
parser.add_argument(
'--time_limit', default=20000, type=int, help='global time limit')
parser.add_argument(
'--lns_fragment_size',
default=10,
type=int,
help='size of the random lns fragment')
parser.add_argument(
'--lns_random_seed',
default=0,
type=int,
help='seed for the lns random generator')
parser.add_argument(
'--lns_fail_limit',
default=30,
type=int,
help='fail limit when exploring fragments')
2010-10-14 13:47:55 +00:00
# ---------- helper for binpacking posting ----------
def BinPacking(solver, binvars, weights, loadvars):
2018-06-11 11:51:18 +02:00
"""post the load constraint on bins.
2010-10-14 13:47:55 +00:00
constraints forall j: loadvars[j] == sum_i (binvars[i] == j) * weights[i])
2018-06-11 11:51:18 +02:00
"""
2010-10-14 13:47:55 +00:00
pack = solver.Pack(binvars, len(binvars))
2014-05-22 20:13:16 +00:00
pack.AddWeightedSumEqualVarDimension(weights, loadvars)
2010-10-14 13:47:55 +00:00
solver.Add(pack)
solver.Add(solver.SumEquality(loadvars, sum(weights)))
2018-06-11 11:51:18 +02:00
2010-10-14 13:47:55 +00:00
# ---------- data reading ----------
2014-05-22 20:13:16 +00:00
2010-10-14 13:47:55 +00:00
def ReadData(filename):
2014-05-22 20:13:16 +00:00
"""Read data from <filename>."""
2010-10-14 13:47:55 +00:00
f = open(filename)
capacity = [int(nb) for nb in f.readline().split()]
capacity.pop(0)
capacity = [0] + capacity
max_capacity = max(capacity)
nb_colors = int(f.readline())
nb_slabs = int(f.readline())
wc = [[int(j) for j in f.readline().split()] for i in range(nb_slabs)]
weights = [x[0] for x in wc]
colors = [x[1] for x in wc]
2018-06-11 11:51:18 +02:00
loss = [
min([x for x in capacity if x >= c]) - c for c in range(max_capacity + 1)
]
color_orders = [[o
for o in range(nb_slabs)
if colors[o] == c]
2014-05-22 20:13:16 +00:00
for c in range(1, nb_colors + 1)]
2016-01-15 01:42:27 +01:00
print('Solving steel mill with', nb_slabs, 'slabs')
2010-10-14 13:47:55 +00:00
return (nb_slabs, capacity, max_capacity, weights, colors, loss, color_orders)
2018-06-11 11:51:18 +02:00
2010-10-14 13:47:55 +00:00
# ---------- dedicated search for this problem ----------
2018-06-11 11:51:18 +02:00
2010-10-14 13:47:55 +00:00
class SteelDecisionBuilder(pywrapcp.PyDecisionBuilder):
2018-06-11 11:51:18 +02:00
"""Dedicated Decision Builder for steel mill slab.
2010-10-14 13:47:55 +00:00
Search for the steel mill slab problem with Dynamic Symmetry
Breaking during search is an adaptation (for binary tree) from the
paper of Pascal Van Hentenryck and Laurent Michel CPAIOR-2008.
The value heuristic comes from the paper
Solving Steel Mill Slab Problems with Constraint-Based Techniques:
CP, LNS, and CBLS,
Schaus et. al. to appear in Constraints 2010
2018-06-11 11:51:18 +02:00
"""
2010-10-14 13:47:55 +00:00
def __init__(self, x, nb_slabs, weights, loss_array, loads):
pywrapcp.PyDecisionBuilder.__init__(self)
2010-10-14 13:47:55 +00:00
self.__x = x
self.__nb_slabs = nb_slabs
self.__weights = weights
self.__loss_array = loss_array
self.__loads = loads
2014-05-22 20:13:16 +00:00
self.__max_capacity = len(loss_array) - 1
2010-10-14 13:47:55 +00:00
def Next(self, solver):
2014-05-22 20:13:16 +00:00
var, weight = self.NextVar()
2010-10-14 13:47:55 +00:00
if var:
v = self.MaxBound()
if v + 1 == var.Min():
# Symmetry breaking. If you need to assign to a new bin,
# select the first one.
solver.Add(var == v + 1)
return self.Next(solver)
else:
# value heuristic (important for difficult problem):
# try first to place the order in the slab that will induce
# the least increase of the loss
loads = self.getLoads()
2018-06-11 11:51:18 +02:00
l, v = min(
(self.__loss_array[loads[i] + weight], i)
for i in range(var.Min(),
var.Max() + 1)
if var.Contains(i) and loads[i] + weight <= self.__max_capacity)
2010-10-14 13:47:55 +00:00
decision = solver.AssignVariableValue(var, v)
return decision
else:
return None
def getLoads(self):
load = [0] * len(self.__loads)
for (w, x) in zip(self.__weights, self.__x):
if x.Bound():
load[x.Min()] += w
return load
def MaxBound(self):
2014-05-22 20:13:16 +00:00
""" returns the max value bound to a variable, -1 if no variables bound"""
2018-06-11 11:51:18 +02:00
return max([-1] + [
self.__x[o].Min()
for o in range(self.__nb_slabs)
if self.__x[o].Bound()
])
2010-10-14 13:47:55 +00:00
def NextVar(self):
2014-05-22 20:13:16 +00:00
""" mindom size heuristic with tie break on the weights of orders """
2010-10-14 13:47:55 +00:00
res = [(self.__x[o].Size(), -self.__weights[o], self.__x[o])
for o in range(self.__nb_slabs)
if self.__x[o].Size() > 1]
if res:
res.sort()
2014-05-22 20:13:16 +00:00
return (res[0][2], -res[0][1]) # returns the order var and its weight
2010-10-14 13:47:55 +00:00
else:
return (None, None)
def DebugString(self):
2014-05-22 20:13:16 +00:00
return 'SteelMillDecisionBuilder(' + str(self.__x) + ')'
2010-10-14 13:47:55 +00:00
2018-06-11 11:51:18 +02:00
2010-10-14 13:47:55 +00:00
# ----------- LNS Operator ----------
2014-05-22 20:13:16 +00:00
class SteelRandomLns(pywrapcp.BaseLns):
2010-10-14 13:47:55 +00:00
"""Random LNS for Steel."""
2014-06-17 08:26:19 +00:00
def __init__(self, x, rand, lns_size):
pywrapcp.BaseLns.__init__(self, x)
2010-10-14 13:47:55 +00:00
self.__random = rand
2014-06-17 08:26:19 +00:00
self.__lns_size = lns_size
2010-10-14 13:47:55 +00:00
2013-10-09 21:36:21 +00:00
def InitFragments(self):
pass
2014-06-17 08:14:40 +00:00
def NextFragment(self):
while self.FragmentSize() < self.__lns_size:
2014-06-17 08:26:58 +00:00
pos = self.__random.randint(0, self.Size() - 1)
self.AppendToFragment(pos)
return True
2010-10-14 13:47:55 +00:00
2018-06-11 11:51:18 +02:00
2010-10-14 13:47:55 +00:00
# ----------- Main Function -----------
def main(args):
2010-10-14 13:47:55 +00:00
# ----- solver and variable declaration -----
(nb_slabs, capacity, max_capacity, weights, colors, loss, color_orders) =\
ReadData(args.data)
2010-10-14 13:47:55 +00:00
nb_colors = len(color_orders)
solver = pywrapcp.Solver('Steel Mill Slab')
2018-06-11 11:51:18 +02:00
x = [solver.IntVar(0, nb_slabs - 1, 'x' + str(i)) for i in range(nb_slabs)]
load_vars = [
solver.IntVar(0, max_capacity - 1, 'load_vars' + str(i))
for i in range(nb_slabs)
]
2010-10-14 13:47:55 +00:00
# ----- post of the constraints -----
# Bin Packing.
BinPacking(solver, x, weights, load_vars)
# At most two colors per slab.
for s in range(nb_slabs):
2018-06-11 11:51:18 +02:00
solver.Add(
solver.SumLessOrEqual([
solver.Max([solver.IsEqualCstVar(x[c], s)
for c in o])
for o in color_orders
], 2))
2010-10-14 13:47:55 +00:00
# ----- Objective -----
objective_var = \
solver.Sum([load_vars[s].IndexOf(loss) for s in range(nb_slabs)]).Var()
objective = solver.Minimize(objective_var, 1)
# ----- start the search and optimization -----
assign_db = SteelDecisionBuilder(x, nb_slabs, weights, loss, load_vars)
first_solution = solver.Assignment()
first_solution.Add(x)
first_solution.AddObjective(objective_var)
store_db = solver.StoreAssignment(first_solution)
first_solution_db = solver.Compose([assign_db, store_db])
2016-01-15 01:42:27 +01:00
print('searching for initial solution,', end=' ')
2010-10-14 13:47:55 +00:00
solver.Solve(first_solution_db)
2016-01-15 01:42:27 +01:00
print('initial cost =', first_solution.ObjectiveValue())
2010-10-14 13:47:55 +00:00
2010-10-14 15:51:55 +00:00
# To search a fragment, we use a basic randomized decision builder.
# We can also use assign_db instead of inner_db.
2018-06-11 11:51:18 +02:00
inner_db = solver.Phase(x, solver.CHOOSE_RANDOM, solver.ASSIGN_MIN_VALUE)
2010-10-14 15:51:55 +00:00
# The most important aspect is to limit the time exploring each fragment.
inner_limit = solver.FailuresLimit(args.lns_fail_limit)
continuation_db = solver.SolveOnce(inner_db, [inner_limit])
2010-10-14 15:51:55 +00:00
# Now, we create the LNS objects.
2010-10-14 13:47:55 +00:00
rand = random.Random()
rand.seed(args.lns_random_seed)
local_search_operator = SteelRandomLns(x, rand, args.lns_fragment_size)
2010-10-15 13:57:34 +00:00
# This is in fact equivalent to the following predefined LNS operator:
# local_search_operator = solver.RandomLNSOperator(x,
# args.lns_fragment_size,
# args.lns_random_seed)
2010-10-14 13:47:55 +00:00
local_search_parameters = solver.LocalSearchPhaseParameters(
local_search_operator, continuation_db)
2010-10-14 13:47:55 +00:00
local_search_db = solver.LocalSearchPhase(first_solution,
local_search_parameters)
global_limit = solver.TimeLimit(args.time_limit)
2010-10-14 13:47:55 +00:00
2016-01-15 01:42:27 +01:00
print('using LNS to improve the initial solution')
2010-10-14 13:47:55 +00:00
search_log = solver.SearchLog(100000, objective_var)
2014-06-17 08:07:43 +00:00
solver.NewSearch(local_search_db, [objective, search_log, global_limit])
2010-10-14 15:51:55 +00:00
while solver.NextSolution():
2016-01-15 01:42:27 +01:00
print('Objective:', objective_var.Value(),\
'check:', sum(loss[load_vars[s].Min()] for s in range(nb_slabs)))
2010-10-14 15:51:55 +00:00
solver.EndSearch()
2010-10-14 13:47:55 +00:00
if __name__ == '__main__':
main(parser.parse_args())