sync python examples

This commit is contained in:
Laurent Perron
2020-11-30 11:59:56 +01:00
parent d8f1f6d54e
commit 425da9ed29
5 changed files with 285 additions and 211 deletions

View File

@@ -10,22 +10,26 @@
# 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.
"""Generates possible daily schedules for workers."""
"""Appointment selection.
This module maximizes the number of appointments that can
be fulfilled by a crew of installers while staying close to ideal
ratio of appointment types.
"""
import argparse
from ortools.sat.python import cp_model
# overloaded sum() clashes with pytype.
# pytype: disable=wrong-arg-types
from absl import app
from absl import flags
from ortools.linear_solver import pywraplp
from ortools.sat.python import cp_model
PARSER = argparse.ArgumentParser()
PARSER.add_argument(
'--load_min', default=480, type=int, help='Minimum load in minutes')
PARSER.add_argument(
'--load_max', default=540, type=int, help='Maximum load in minutes')
PARSER.add_argument(
'--commute_time', default=30, type=int, help='Commute time in minutes')
PARSER.add_argument(
'--num_workers', default=98, type=int, help='Maximum number of workers.')
FLAGS = flags.FLAGS
flags.DEFINE_integer('load_min', 480, 'Minimum load in minutes.')
flags.DEFINE_integer('load_max', 540, 'Maximum load in minutes.')
flags.DEFINE_integer('commute_time', 30, 'Commute time in minutes.')
flags.DEFINE_integer('num_workers', 98, 'Maximum number of workers.')
class AllSolutionCollector(cp_model.CpSolverSolutionCallback):
@@ -46,29 +50,26 @@ class AllSolutionCollector(cp_model.CpSolverSolutionCallback):
return self.__collect
def find_combinations(durations, load_min, load_max, commute_time):
"""This methods find all valid combinations of appointments.
def EnumerateAllKnapsacksWithRepetition(item_sizes, total_size_min,
total_size_max):
"""Enumerate all possible knapsacks with total size in the given range.
This methods find all combinations of appointments such that the sum of
durations + commute times is between load_min and load_max.
Args:
item_sizes: a list of integers. item_sizes[i] is the size of item #i.
total_size_min: an integer, the minimum total size.
total_size_max: an integer, the maximum total size.
Args:
durations: The durations of all appointments.
load_min: The min number of worked minutes for a valid selection.
load_max: The max number of worked minutes for a valid selection.
commute_time: The commute time between two appointments in minutes.
Returns:
A matrix where each line is a valid combinations of appointments.
"""
Returns:
The list of all the knapsacks whose total size is in the given inclusive
range. Each knapsack is a list [#item0, #item1, ... ], where #itemK is an
nonnegative integer: the number of times we put item #K in the knapsack.
"""
model = cp_model.CpModel()
variables = [
model.NewIntVar(0, load_max // (duration + commute_time), '')
for duration in durations
model.NewIntVar(0, total_size_max // size, '') for size in item_sizes
]
terms = sum(variables[i] * (duration + commute_time)
for i, duration in enumerate(durations))
model.AddLinearConstraint(terms, load_min, load_max)
load = sum(variables[i] * size for i, size in enumerate(item_sizes))
model.AddLinearConstraint(load, total_size_min, total_size_max)
solver = cp_model.CpSolver()
solution_collector = AllSolutionCollector(variables)
@@ -76,88 +77,164 @@ def find_combinations(durations, load_min, load_max, commute_time):
return solution_collector.combinations()
def select(combinations, loads, max_number_of_workers):
"""This method selects the optimal combination of appointments.
def AggregateItemCollectionsOptimally(item_collections, max_num_collections,
ideal_item_ratios):
"""Selects a set (with repetition) of combination of items optimally.
This method uses Mixed Integer Programming to select the optimal mix of
appointments.
Given a set of collections of N possible items (in each collection, an item
may appear multiple times), a given "ideal breakdown of items", and a
maximum number of collections, this method finds the optimal way to
aggregate the collections in order to:
- maximize the overall number of items
- while keeping the ratio of each item, among the overall selection, as close
as possible to a given input ratio (which depends on the item).
Each collection may be selected more than one time.
Args:
item_collections: a list of item collections. Each item collection is a
list of integers [#item0, ..., #itemN-1], where #itemK is the number
of times item #K appears in the collection, and N is the number of
distinct items.
max_num_collections: an integer, the maximum number of item collections
that may be selected (counting repetitions of the same collection).
ideal_item_ratios: A list of N float which sums to 1.0: the K-th element is
the ideal ratio of item #K in the whole aggregated selection.
Returns:
A pair (objective value, list of pairs (item collection, num_selections)),
where:
- "objective value" is the value of the internal objective function used
by the MIP Solver
- Each "item collection" is an element of the input item_collections
- and its associated "num_selections" is the number of times it was
selected.
"""
solver = pywraplp.Solver('Select',
pywraplp.Solver.CBC_MIXED_INTEGER_PROGRAMMING)
num_vars = len(loads)
num_combinations = len(combinations)
variables = [
solver.IntVar(0, max_number_of_workers, 's[%d]' % i)
for i in range(num_combinations)
]
achieved = [
solver.IntVar(0, 1000, 'achieved[%d]' % i) for i in range(num_vars)
]
transposed = [[
combinations[type][index] for type in range(num_combinations)
] for index in range(num_vars)]
pywraplp.Solver.SCIP_MIXED_INTEGER_PROGRAMMING)
n = len(ideal_item_ratios)
num_distinct_collections = len(item_collections)
max_num_items_per_collection = 0
for template in item_collections:
max_num_items_per_collection = max(max_num_items_per_collection,
sum(template))
upper_bound = max_num_items_per_collection * max_num_collections
# Maintain the achieved variables.
for i, coefs in enumerate(transposed):
# num_selections_of_collection[i] is an IntVar that represents the number
# of times that we will use collection #i in our global selection.
num_selections_of_collection = [
solver.IntVar(0, max_num_collections, 's[%d]' % i)
for i in range(num_distinct_collections)
]
# num_overall_item[i] is an IntVar that represents the total count of item #i,
# aggregated over all selected collections. This is enforced with dedicated
# constraints that bind them with the num_selections_of_collection vars.
num_overall_item = [
solver.IntVar(0, upper_bound, 'num_overall_item[%d]' % i)
for i in range(n)
]
for i in range(n):
ct = solver.Constraint(0.0, 0.0)
ct.SetCoefficient(achieved[i], -1)
for j, coef in enumerate(coefs):
ct.SetCoefficient(variables[j], coef)
ct.SetCoefficient(num_overall_item[i], -1)
for j in range(num_distinct_collections):
ct.SetCoefficient(num_selections_of_collection[j],
item_collections[j][i])
# Simple bound.
solver.Add(solver.Sum(variables) <= max_number_of_workers)
# Maintain the num_all_item variable as the sum of all num_overall_item
# variables.
num_all_items = solver.IntVar(0, upper_bound, 'num_all_items')
solver.Add(solver.Sum(num_overall_item) == num_all_items)
obj_vars = [
solver.IntVar(0, 1000, 'obj_vars[%d]' % i) for i in range(num_vars)
# Sets the total number of workers.
solver.Add(solver.Sum(num_selections_of_collection) == max_num_collections)
# Objective variables.
deviation_vars = [
solver.NumVar(0, upper_bound, 'deviation_vars[%d]' % i)
for i in range(n)
]
for i in range(num_vars):
solver.Add(obj_vars[i] >= achieved[i] - loads[i])
solver.Add(obj_vars[i] >= loads[i] - achieved[i])
for i in range(n):
deviation = deviation_vars[i]
solver.Add(deviation >= num_overall_item[i] -
ideal_item_ratios[i] * num_all_items)
solver.Add(deviation >= ideal_item_ratios[i] * num_all_items -
num_overall_item[i])
solver.Minimize(solver.Sum(obj_vars))
solver.Maximize(num_all_items - solver.Sum(deviation_vars))
result_status = solver.Solve()
# The problem has an optimal solution.
if result_status == pywraplp.Solver.OPTIMAL:
print('Problem solved in %f milliseconds' % solver.WallTime())
return solver.Objective().Value(), [
int(v.SolutionValue()) for v in variables
]
return -1, []
# The problem has an optimal solution.
return [int(v.solution_value()) for v in num_selections_of_collection]
return []
def get_optimal_schedule(demand, args):
"""Computes the optimal schedule for the appointment selection problem."""
combinations = find_combinations([a[2] for a in demand], args.load_min,
args.load_max, args.commute_time)
print('found %d possible combinations of appointements' % len(combinations))
def GetOptimalSchedule(demand):
"""Computes the optimal schedule for the installation input.
cost, selection = select(combinations, [a[0]
for a in demand], args.num_workers)
output = [(selection[i], [(combinations[i][t], demand[t][1])
for t in range(len(demand))
if combinations[i][t] != 0])
for i in range(len(selection)) if selection[i] != 0]
return cost, output
Args:
demand: a list of "appointment types". Each "appointment type" is
a triple (ideal_ratio_pct, name, duration_minutes), where
ideal_ratio_pct is the ideal percentage (in [0..100.0]) of that
type of appointment among all appointments scheduled.
Returns:
The same output type as EnumerateAllKnapsacksWithRepetition.
"""
combinations = EnumerateAllKnapsacksWithRepetition(
[a[2] + FLAGS.commute_time for a in demand], FLAGS.load_min,
FLAGS.load_max)
print(('Found %d possible day schedules ' % len(combinations) +
'(i.e. combination of appointments filling up one worker\'s day)'))
selection = AggregateItemCollectionsOptimally(
combinations, FLAGS.num_workers, [a[0] / 100.0 for a in demand])
output = []
for i in range(len(selection)):
if selection[i] != 0:
output.append((selection[i], [(combinations[i][t], demand[t][1])
for t in range(len(demand))
if combinations[i][t] != 0]))
return output
def main(args):
"""Solve the assignment problem."""
demand = [(40, 'A1', 90), (30, 'A2', 120), (25, 'A3', 180)]
print('appointments: ')
def main(_):
demand = [(45.0, 'Type1', 90), (30.0, 'Type2', 120), (25.0, 'Type3', 180)]
print('*** input problem ***')
print('Appointments: ')
for a in demand:
print(' %d * %s : %d min' % (a[0], a[1], a[2]))
print('commute time = %d' % args.commute_time)
print('accepted total duration = [%d..%d]' % (args.load_min, args.load_max))
print('%d workers' % args.num_workers)
cost, selection = get_optimal_schedule(demand, args)
print('Optimal solution as a cost of %d' % cost)
print(' %.2f%% of %s : %d min' % (a[0], a[1], a[2]))
print('Commute time = %d' % FLAGS.commute_time)
print('Acceptable duration of a work day = [%d..%d]' %
(FLAGS.load_min, FLAGS.load_max))
print('%d workers' % FLAGS.num_workers)
selection = GetOptimalSchedule(demand)
print()
installed = 0
installed_per_type = {}
for a in demand:
installed_per_type[a[1]] = 0
print('*** output solution ***')
for template in selection:
print('%d schedules with ' % template[0])
num_instances = template[0]
print('%d schedules with ' % num_instances)
for t in template[1]:
print(' %d installation of type %s' % (t[0], t[1]))
mult = t[0]
print(' %d installation of type %s' % (mult, t[1]))
installed += num_instances * mult
installed_per_type[t[1]] += num_instances * mult
print()
print('%d installations planned' % installed)
for a in demand:
name = a[1]
per_type = installed_per_type[name]
print((' %d (%.2f%%) installations of type %s planned' %
(per_type, per_type * 100.0 / installed, name)))
if __name__ == '__main__':
main(PARSER.parse_args())
app.run(main)

View File

@@ -16,23 +16,40 @@ We have a set of jobs to perform (duration, width).
We have two parallel machines that can perform this job.
One machine can only perform one job at a time.
At any point in time, the sum of the width of the two active jobs does not
exceed a max_length.
exceed a max_width.
The objective is to minimize the max end time of all jobs.
"""
from ortools.sat.python import cp_model
from absl import app
from ortools.sat.python import visualization
from ortools.sat.python import cp_model
def main():
def main(_):
"""Solves the gate scheduling problem."""
model = cp_model.CpModel()
jobs = [[3, 3], [2, 5], [1, 3], [3, 7], [7, 3], [2, 2], [2, 2], [5, 5],
[10, 2], [4, 3], [2, 6], [1, 2], [6, 8], [4, 5], [3, 7]]
jobs = [
[3, 3], # [duration, width]
[2, 5],
[1, 3],
[3, 7],
[7, 3],
[2, 2],
[2, 2],
[5, 5],
[10, 2],
[4, 3],
[2, 6],
[1, 2],
[6, 8],
[4, 5],
[3, 7]
]
max_length = 10
max_width = 10
horizon = sum(t[0] for t in jobs)
num_jobs = len(jobs)
@@ -57,14 +74,14 @@ def main():
ends.append(end)
demands.append(jobs[i][1])
# Create an optional copy of interval to be executed on machine 0.
performed_on_m0 = model.NewBoolVar('perform_%i_on_m0' % i)
performed.append(performed_on_m0)
# Create an optional copy of interval to be executed on machine 0.
start0 = model.NewIntVar(0, horizon, 'start_%i_on_m0' % i)
end0 = model.NewIntVar(0, horizon, 'end_%i_on_m0' % i)
interval0 = model.NewOptionalIntervalVar(
start0, duration, end0, performed_on_m0, 'interval_%i_on_m0' % i)
interval0 = model.NewOptionalIntervalVar(start0, duration, end0,
performed_on_m0,
'interval_%i_on_m0' % i)
intervals0.append(interval0)
# Create an optional copy of interval to be executed on machine 1.
@@ -79,8 +96,8 @@ def main():
model.Add(start0 == start).OnlyEnforceIf(performed_on_m0)
model.Add(start1 == start).OnlyEnforceIf(performed_on_m0.Not())
# Max Length constraint (modeled as a cumulative)
model.AddCumulative(intervals, demands, max_length)
# Width constraint (modeled as a cumulative)
model.AddCumulative(intervals, demands, max_width)
# Choose which machine to perform the jobs on.
model.AddNoOverlap(intervals0)
@@ -100,7 +117,7 @@ def main():
# Output solution.
if visualization.RunFromIPython():
output = visualization.SvgWrapper(solver.ObjectiveValue(), max_length,
output = visualization.SvgWrapper(solver.ObjectiveValue(), max_width,
40.0)
output.AddTitle('Makespan = %i' % solver.ObjectiveValue())
color_manager = visualization.ColorManager()
@@ -111,7 +128,7 @@ def main():
start = solver.Value(starts[i])
d_x = jobs[i][0]
d_y = jobs[i][1]
s_y = performed_machine * (max_length - d_y)
s_y = performed_machine * (max_width - d_y)
output.AddRectangle(start, s_y, d_x, d_y,
color_manager.RandomColor(), 'black', 'j%i' % i)
@@ -124,8 +141,8 @@ def main():
for i in all_jobs:
performed_machine = 1 - solver.Value(performed[i])
start = solver.Value(starts[i])
print(' - Job %i starts at %i on machine %i' % (i, start,
performed_machine))
print(' - Job %i starts at %i on machine %i' %
(i, start, performed_machine))
print('Statistics')
print(' - conflicts : %i' % solver.NumConflicts())
print(' - branches : %i' % solver.NumBranches())
@@ -133,4 +150,4 @@ def main():
if __name__ == '__main__':
main()
app.run(main)

View File

@@ -47,13 +47,13 @@ def main(_):
# We expand the creation of the diff array to avoid a pylint warning.
diffs = []
for i in range(0, size - 1):
for i in range(size - 1):
for j in range(i + 1, size):
diffs.append(marks[j] - marks[i])
solver.Add(solver.AllDifferent(diffs))
solver.Add(marks[size - 1] - marks[size - 2] > marks[1] - marks[0])
for i in range(0, size - 2):
for i in range(size - 2):
solver.Add(marks[i + 1] > marks[i])
solution = solver.Assignment()

View File

@@ -12,17 +12,18 @@
# limitations under the License.
"""Creates a shift scheduling problem and solves it."""
from ortools.sat.python import cp_model
from google.protobuf import text_format
from absl import app
from absl import flags
from ortools.sat.python import cp_model
from google.protobuf import text_format
FLAGS = flags.FLAGS
flags.DEFINE_string('output_proto', '',
'Output file to write the cp_model proto to.')
flags.DEFINE_string('params', '', 'Sat solver parameters.')
flags.DEFINE_string('params', 'max_time_in_seconds:10.0',
'Sat solver parameters.')
def negated_bounded_span(works, start, length):
@@ -376,8 +377,6 @@ def solve_shift_scheduling(params, output_proto):
solver = cp_model.CpSolver()
if params:
text_format.Parse(params, solver.parameters)
else:
text_format.Parse(r'max_time_in_seconds:10.0', solver.parameters)
solution_printer = cp_model.ObjectiveSolutionPrinter()
status = solver.SolveWithSolutionCallback(model, solution_printer)

View File

@@ -12,31 +12,25 @@
# limitations under the License.
"""Solves the Stell Mill Slab problem with 4 different techniques."""
# overloaded sum() clashes with pytype.
# pytype: disable=wrong-arg-types
import argparse
import collections
import time
from ortools.sat.python import cp_model
from absl import app
from absl import flags
from ortools.linear_solver import pywraplp
from ortools.sat.python import cp_model
PARSER = argparse.ArgumentParser()
FLAGS = flags.FLAGS
PARSER.add_argument(
'--problem', default=2, type=int, help='Problem id to solve.')
PARSER.add_argument(
'--break_symmetries',
default=True,
type=bool,
help='Break symmetries between equivalent orders.')
PARSER.add_argument(
'--solver',
default='sat_table',
help='Method used to solve: sat, sat_table, sat_column, mip_column.')
PARSER.add_argument(
'--output_proto',
default='',
help='Output file to write the cp_model proto to.')
flags.DEFINE_integer('problem', 2, 'Problem id to solve.')
flags.DEFINE_boolean('break_symmetries', True,
'Break symmetries between equivalent orders.')
flags.DEFINE_string(
'solver', 'mip_column', 'Method used to solve: sat, sat_table, sat_column, '
'mip_column.')
def build_problem(problem_id):
@@ -295,7 +289,7 @@ class SteelMillSlabSolutionPrinter(cp_model.CpSolverSolutionCallback):
print(line)
def steel_mill_slab(problem, break_symmetries, output_proto):
def steel_mill_slab(problem, break_symmetries):
"""Solves the Steel Mill Slab Problem."""
### Load problem.
(num_slabs, capacities, num_colors, orders) = build_problem(problem)
@@ -308,17 +302,18 @@ def steel_mill_slab(problem, break_symmetries, output_proto):
print('Solving steel mill with %i orders, %i slabs, and %i capacities' %
(num_orders, num_slabs, num_capacities - 1))
# Compute auxilliary data.
# Compute auxiliary data.
widths = [x[0] for x in orders]
colors = [x[1] for x in orders]
max_capacity = max(capacities)
loss_array = [
min(x for x in capacities if x >= c) - c
for c in range(max_capacity + 1)
min(x for x in capacities if x >= c) - c for c in range(max_capacity +
1)
]
max_loss = max(loss_array)
orders_per_color = [[o for o in all_orders if colors[o] == c + 1]
for c in all_colors]
orders_per_color = [
[o for o in all_orders if colors[o] == c + 1] for c in all_colors
]
unique_color_orders = [
o for o in all_orders if len(orders_per_color[colors[o] - 1]) == 1
]
@@ -335,14 +330,12 @@ def steel_mill_slab(problem, break_symmetries, output_proto):
for s in all_slabs
]
color_is_in_slab = [[
model.NewBoolVar('color_%i_in_slab_%i' % (c + 1, s))
for c in all_colors
model.NewBoolVar('color_%i_in_slab_%i' % (c + 1, s)) for c in all_colors
] for s in all_slabs]
# Compute load of all slabs.
for s in all_slabs:
model.Add(
sum(assign[o][s] * widths[o] for o in all_orders) == loads[s])
model.Add(sum(assign[o][s] * widths[o] for o in all_orders) == loads[s])
# Orders are assigned to one slab.
for o in all_orders:
@@ -434,8 +427,9 @@ def steel_mill_slab(problem, break_symmetries, output_proto):
### Output the solution.
if status in (cp_model.OPTIMAL, cp_model.FEASIBLE):
print('Loss = %i, time = %f s, %i conflicts' % (
solver.ObjectiveValue(), solver.WallTime(), solver.NumConflicts()))
print(
'Loss = %i, time = %f s, %i conflicts' %
(solver.ObjectiveValue(), solver.WallTime(), solver.NumConflicts()))
else:
print('No solution')
@@ -458,19 +452,19 @@ def collect_valid_slabs_dp(capacities, colors, widths, loss_array):
if assignment.load + new_width > max_capacity:
continue
new_colors = list(assignment.colors)
if not new_color in new_colors:
if new_color not in new_colors:
new_colors.append(new_color)
if len(new_colors) > 2:
continue
new_assignment = valid_assignment(
orders=assignment.orders + [order_id],
load=assignment.load + new_width,
colors=new_colors)
new_assignment = valid_assignment(orders=assignment.orders +
[order_id],
load=assignment.load + new_width,
colors=new_colors)
new_assignments.append(new_assignment)
all_valid_assignments.extend(new_assignments)
print('%i assignments created in %.2f s' % (len(all_valid_assignments),
time.time() - start_time))
print('%i assignments created in %.2f s' %
(len(all_valid_assignments), time.time() - start_time))
tuples = []
for assignment in all_valid_assignments:
solution = [0 for _ in range(len(colors))]
@@ -483,7 +477,7 @@ def collect_valid_slabs_dp(capacities, colors, widths, loss_array):
return tuples
def steel_mill_slab_with_valid_slabs(problem, break_symmetries, output_proto):
def steel_mill_slab_with_valid_slabs(problem, break_symmetries):
"""Solves the Steel Mill Slab Problem."""
### Load problem.
(num_slabs, capacities, num_colors, orders) = build_problem(problem)
@@ -496,13 +490,13 @@ def steel_mill_slab_with_valid_slabs(problem, break_symmetries, output_proto):
print('Solving steel mill with %i orders, %i slabs, and %i capacities' %
(num_orders, num_slabs, num_capacities - 1))
# Compute auxilliary data.
# Compute auxiliary data.
widths = [x[0] for x in orders]
colors = [x[1] for x in orders]
max_capacity = max(capacities)
loss_array = [
min(x for x in capacities if x >= c) - c
for c in range(max_capacity + 1)
min(x for x in capacities if x >= c) - c for c in range(max_capacity +
1)
]
max_loss = max(loss_array)
@@ -513,21 +507,18 @@ def steel_mill_slab_with_valid_slabs(problem, break_symmetries, output_proto):
assign = [[
model.NewBoolVar('assign_%i_to_slab_%i' % (o, s)) for s in all_slabs
] for o in all_orders]
loads = [
model.NewIntVar(0, max_capacity, 'load_%i' % s) for s in all_slabs
]
loads = [model.NewIntVar(0, max_capacity, 'load_%i' % s) for s in all_slabs]
losses = [model.NewIntVar(0, max_loss, 'loss_%i' % s) for s in all_slabs]
unsorted_valid_slabs = collect_valid_slabs_dp(capacities, colors, widths,
loss_array)
# Sort slab by descending load/loss. Remove duplicates.
valid_slabs = sorted(
unsorted_valid_slabs, key=lambda c: 1000 * c[-1] + c[-2])
valid_slabs = sorted(unsorted_valid_slabs,
key=lambda c: 1000 * c[-1] + c[-2])
for s in all_slabs:
model.AddAllowedAssignments(
[assign[o][s] for o in all_orders] + [losses[s], loads[s]],
valid_slabs)
model.AddAllowedAssignments([assign[o][s] for o in all_orders] +
[losses[s], loads[s]], valid_slabs)
# Orders are assigned to one slab.
for o in all_orders:
@@ -545,8 +536,9 @@ def steel_mill_slab_with_valid_slabs(problem, break_symmetries, output_proto):
print('Breaking symmetries')
width_to_unique_color_order = {}
ordered_equivalent_orders = []
orders_per_color = [[o for o in all_orders if colors[o] == c + 1]
for c in all_colors]
orders_per_color = [
[o for o in all_orders if colors[o] == c + 1] for c in all_colors
]
for c in all_colors:
colored_orders = orders_per_color[c]
if not colored_orders:
@@ -568,8 +560,7 @@ def steel_mill_slab_with_valid_slabs(problem, break_symmetries, output_proto):
for w, os in local_width_to_order.items():
if len(os) > 1:
for p in range(len(os) - 1):
ordered_equivalent_orders.append((os[p],
os[p + 1]))
ordered_equivalent_orders.append((os[p], os[p + 1]))
for w, os in width_to_unique_color_order.items():
if len(os) > 1:
for p in range(len(os) - 1):
@@ -597,12 +588,6 @@ def steel_mill_slab_with_valid_slabs(problem, break_symmetries, output_proto):
print('Model created')
# Output model proto to file.
if output_proto:
output_file = open(output_proto, 'w')
output_file.write(str(model.Proto()))
output_file.close()
### Solve model.
solver = cp_model.CpSolver()
solver.num_search_workers = 8
@@ -612,13 +597,14 @@ def steel_mill_slab_with_valid_slabs(problem, break_symmetries, output_proto):
### Output the solution.
if status == cp_model.OPTIMAL:
print('Loss = %i, time = %.2f s, %i conflicts' % (
solver.ObjectiveValue(), solver.WallTime(), solver.NumConflicts()))
print(
'Loss = %i, time = %.2f s, %i conflicts' %
(solver.ObjectiveValue(), solver.WallTime(), solver.NumConflicts()))
else:
print('No solution')
def steel_mill_slab_with_column_generation(problem, output_proto):
def steel_mill_slab_with_column_generation(problem):
"""Solves the Steel Mill Slab Problem."""
### Load problem.
(num_slabs, capacities, _, orders) = build_problem(problem)
@@ -629,13 +615,13 @@ def steel_mill_slab_with_column_generation(problem, output_proto):
print('Solving steel mill with %i orders, %i slabs, and %i capacities' %
(num_orders, num_slabs, num_capacities - 1))
# Compute auxilliary data.
# Compute auxiliary data.
widths = [x[0] for x in orders]
colors = [x[1] for x in orders]
max_capacity = max(capacities)
loss_array = [
min(x for x in capacities if x >= c) - c
for c in range(max_capacity + 1)
min(x for x in capacities if x >= c) - c for c in range(max_capacity +
1)
]
### Model problem.
@@ -645,8 +631,8 @@ def steel_mill_slab_with_column_generation(problem, output_proto):
loss_array)
# Sort slab by descending load/loss. Remove duplicates.
valid_slabs = sorted(
unsorted_valid_slabs, key=lambda c: 1000 * c[-1] + c[-2])
valid_slabs = sorted(unsorted_valid_slabs,
key=lambda c: 1000 * c[-1] + c[-2])
all_valid_slabs = range(len(valid_slabs))
# create model and decision variables.
@@ -655,7 +641,8 @@ def steel_mill_slab_with_column_generation(problem, output_proto):
for order_id in all_orders:
model.Add(
sum(selected[i] for i, slab in enumerate(valid_slabs)
sum(selected[i]
for i, slab in enumerate(valid_slabs)
if slab[order_id]) == 1)
# Redundant constraint (sum of loads == sum of widths).
@@ -669,22 +656,18 @@ def steel_mill_slab_with_column_generation(problem, output_proto):
print('Model created')
# Output model proto to file.
if output_proto:
output_file = open(output_proto, 'w')
output_file.write(str(model.Proto()))
output_file.close()
### Solve model.
solver = cp_model.CpSolver()
solver.parameters.num_search_workers = 8
solver.parameters.log_search_progress = True
solution_printer = cp_model.ObjectiveSolutionPrinter()
status = solver.SolveWithSolutionCallback(model, solution_printer)
### Output the solution.
if status in (cp_model.OPTIMAL, cp_model.FEASIBLE):
print('Loss = %i, time = %.2f s, %i conflicts' % (
solver.ObjectiveValue(), solver.WallTime(), solver.NumConflicts()))
print(
'Loss = %i, time = %.2f s, %i conflicts' %
(solver.ObjectiveValue(), solver.WallTime(), solver.NumConflicts()))
else:
print('No solution')
@@ -700,13 +683,13 @@ def steel_mill_slab_with_mip_column_generation(problem):
print('Solving steel mill with %i orders, %i slabs, and %i capacities' %
(num_orders, num_slabs, num_capacities - 1))
# Compute auxilliary data.
# Compute auxiliary data.
widths = [x[0] for x in orders]
colors = [x[1] for x in orders]
max_capacity = max(capacities)
loss_array = [
min(x for x in capacities if x >= c) - c
for c in range(max_capacity + 1)
min(x for x in capacities if x >= c) - c for c in range(max_capacity +
1)
]
### Model problem.
@@ -715,21 +698,22 @@ def steel_mill_slab_with_mip_column_generation(problem):
unsorted_valid_slabs = collect_valid_slabs_dp(capacities, colors, widths,
loss_array)
# Sort slab by descending load/loss. Remove duplicates.
valid_slabs = sorted(
unsorted_valid_slabs, key=lambda c: 1000 * c[-1] + c[-2])
valid_slabs = sorted(unsorted_valid_slabs,
key=lambda c: 1000 * c[-1] + c[-2])
all_valid_slabs = range(len(valid_slabs))
# create model and decision variables.
start_time = time.time()
solver = pywraplp.Solver('Steel',
pywraplp.Solver.CBC_MIXED_INTEGER_PROGRAMMING)
pywraplp.Solver.SCIP_MIXED_INTEGER_PROGRAMMING)
selected = [
solver.IntVar(0.0, 1.0, 'selected_%i' % i) for i in all_valid_slabs
]
for order in all_orders:
solver.Add(
sum(selected[i] for i in all_valid_slabs
sum(selected[i]
for i in all_valid_slabs
if valid_slabs[i][order]) == 1)
# Redundant constraint (sum of loads == sum of widths).
@@ -751,19 +735,16 @@ def steel_mill_slab_with_mip_column_generation(problem):
print('No solution')
def main(args):
'''Main function'''
if args.solver == 'sat':
steel_mill_slab(args.problem, args.break_symmetries, args.output_proto)
elif args.solver == 'sat_table':
steel_mill_slab_with_valid_slabs(args.problem, args.break_symmetries,
args.output_proto)
elif args.solver == 'sat_column':
steel_mill_slab_with_column_generation(args.problem, args.output_proto)
def main(_):
if FLAGS.solver == 'sat':
steel_mill_slab(FLAGS.problem, FLAGS.break_symmetries)
elif FLAGS.solver == 'sat_table':
steel_mill_slab_with_valid_slabs(FLAGS.problem, FLAGS.break_symmetries)
elif FLAGS.solver == 'sat_column':
steel_mill_slab_with_column_generation(FLAGS.problem)
else: # 'mip_column'
steel_mill_slab_with_mip_column_generation(args.problem)
steel_mill_slab_with_mip_column_generation(FLAGS.problem)
if __name__ == '__main__':
main(PARSER.parse_args())
# vim: set tw=2 ts=2 sw=2 expandtab:
app.run(main)