sync python examples
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user