use black on examples/python

This commit is contained in:
Laurent Perron
2023-07-01 06:06:53 +02:00
parent d65333dab0
commit 84ec414e61
41 changed files with 18801 additions and 4140 deletions

View File

@@ -27,14 +27,13 @@ from absl import app
from absl import flags
from ortools.linear_solver import pywraplp
from ortools.sat.python import cp_model
# [END import]
_LOAD_MIN = flags.DEFINE_integer('load_min', 480, 'Minimum load in minutes.')
_LOAD_MAX = flags.DEFINE_integer('load_max', 540, 'Maximum load in minutes.')
_COMMUTE_TIME = flags.DEFINE_integer('commute_time', 30,
'Commute time in minutes.')
_NUM_WORKERS = flags.DEFINE_integer('num_workers', 98,
'Maximum number of workers.')
_LOAD_MIN = flags.DEFINE_integer("load_min", 480, "Minimum load in minutes.")
_LOAD_MAX = flags.DEFINE_integer("load_max", 540, "Maximum load in minutes.")
_COMMUTE_TIME = flags.DEFINE_integer("commute_time", 30, "Commute time in minutes.")
_NUM_WORKERS = flags.DEFINE_integer("num_workers", 98, "Maximum number of workers.")
class AllSolutionCollector(cp_model.CpSolverSolutionCallback):
@@ -55,24 +54,21 @@ class AllSolutionCollector(cp_model.CpSolverSolutionCallback):
return self.__collect
def EnumerateAllKnapsacksWithRepetition(item_sizes, total_size_min,
total_size_max):
def EnumerateAllKnapsacksWithRepetition(item_sizes, total_size_min, total_size_max):
"""Enumerate all possible knapsacks with total size in the given range.
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:
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.
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.
"""
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, total_size_max // size, '') for size in item_sizes
]
variables = [model.NewIntVar(0, total_size_max // size, "") for size in item_sizes]
load = sum(variables[i] * size for i, size in enumerate(item_sizes))
model.AddLinearConstraint(load, total_size_min, total_size_max)
@@ -85,52 +81,52 @@ def EnumerateAllKnapsacksWithRepetition(item_sizes, total_size_min,
return solution_collector.combinations()
def AggregateItemCollectionsOptimally(item_collections, max_num_collections,
ideal_item_ratios):
def AggregateItemCollectionsOptimally(
item_collections, max_num_collections, ideal_item_ratios
):
"""Selects a set (with repetition) of combination of items optimally.
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.
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.
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.CreateSolver('SCIP')
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.CreateSolver("SCIP")
if not solver:
return []
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))
max_num_items_per_collection = max(max_num_items_per_collection, sum(template))
upper_bound = max_num_items_per_collection * max_num_collections
# 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)
solver.IntVar(0, max_num_collections, "s[%d]" % i)
for i in range(num_distinct_collections)
]
@@ -138,19 +134,17 @@ def AggregateItemCollectionsOptimally(item_collections, max_num_collections,
# 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)
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(num_overall_item[i], -1)
for j in range(num_distinct_collections):
ct.SetCoefficient(num_selections_of_collection[j],
item_collections[j][i])
ct.SetCoefficient(num_selections_of_collection[j], item_collections[j][i])
# 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')
num_all_items = solver.IntVar(0, upper_bound, "num_all_items")
solver.Add(solver.Sum(num_overall_item) == num_all_items)
# Sets the total number of workers.
@@ -158,15 +152,16 @@ def AggregateItemCollectionsOptimally(item_collections, max_num_collections,
# Objective variables.
deviation_vars = [
solver.NumVar(0, upper_bound, 'deviation_vars[%d]' % i)
for i in range(n)
solver.NumVar(0, upper_bound, "deviation_vars[%d]" % i) for i in range(n)
]
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.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.Maximize(num_all_items - solver.Sum(deviation_vars))
@@ -181,43 +176,57 @@ def AggregateItemCollectionsOptimally(item_collections, max_num_collections,
def GetOptimalSchedule(demand):
"""Computes the optimal schedule for the installation input.
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.
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.
"""
Returns:
The same output type as EnumerateAllKnapsacksWithRepetition.
"""
combinations = EnumerateAllKnapsacksWithRepetition(
[a[2] + _COMMUTE_TIME.value for a in demand], _LOAD_MIN.value,
_LOAD_MAX.value)
print(('Found %d possible day schedules ' % len(combinations) +
'(i.e. combination of appointments filling up one worker\'s day)'))
[a[2] + _COMMUTE_TIME.value for a in demand], _LOAD_MIN.value, _LOAD_MAX.value
)
print(
(
"Found %d possible day schedules " % len(combinations)
+ "(i.e. combination of appointments filling up one worker's day)"
)
)
selection = AggregateItemCollectionsOptimally(
combinations, _NUM_WORKERS.value, [a[0] / 100.0 for a in demand])
combinations, _NUM_WORKERS.value, [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]))
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(_):
demand = [(45.0, 'Type1', 90), (30.0, 'Type2', 120), (25.0, 'Type3', 180)]
print('*** input problem ***')
print('Appointments: ')
demand = [(45.0, "Type1", 90), (30.0, "Type2", 120), (25.0, "Type3", 180)]
print("*** input problem ***")
print("Appointments: ")
for a in demand:
print(' %.2f%% of %s : %d min' % (a[0], a[1], a[2]))
print('Commute time = %d' % _COMMUTE_TIME.value)
print('Acceptable duration of a work day = [%d..%d]' %
(_LOAD_MIN.value, _LOAD_MAX.value))
print('%d workers' % _NUM_WORKERS.value)
print(" %.2f%% of %s : %d min" % (a[0], a[1], a[2]))
print("Commute time = %d" % _COMMUTE_TIME.value)
print(
"Acceptable duration of a work day = [%d..%d]"
% (_LOAD_MIN.value, _LOAD_MAX.value)
)
print("%d workers" % _NUM_WORKERS.value)
selection = GetOptimalSchedule(demand)
print()
installed = 0
@@ -226,30 +235,30 @@ def main(_):
installed_per_type[a[1]] = 0
# [START print_solution]
print('*** output solution ***')
print("*** output solution ***")
for template in selection:
num_instances = template[0]
print('%d schedules with ' % num_instances)
print("%d schedules with " % num_instances)
for t in template[1]:
mult = t[0]
print(' %d installation of type %s' % (mult, t[1]))
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)
print("%d installations planned" % installed)
for a in demand:
name = a[1]
per_type = installed_per_type[name]
if installed != 0:
print(
f' {per_type} ({per_type * 100.0 / installed}%) installations of type {name} planned'
f" {per_type} ({per_type * 100.0 / installed}%) installations of type {name} planned"
)
else:
print(f' {per_type} installations of type {name} planned')
print(f" {per_type} installations of type {name} planned")
# [END print_solution]
if __name__ == '__main__':
if __name__ == "__main__":
app.run(main)
# [END program]

View File

@@ -11,6 +11,7 @@
# 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.
"""Solve an assignment problem with combination constraints on workers."""
from typing import Sequence
@@ -21,19 +22,27 @@ from ortools.sat.python import cp_model
def solve_assignment():
"""Solve the assignment problem."""
# Data.
cost = [[90, 76, 75, 70, 50, 74], [35, 85, 55, 65, 48, 101],
[125, 95, 90, 105, 59, 120], [45, 110, 95, 115, 104, 83],
[60, 105, 80, 75, 59, 62], [45, 65, 110, 95, 47, 31],
[38, 51, 107, 41, 69, 99], [47, 85, 57, 71, 92, 77],
[39, 63, 97, 49, 118, 56], [47, 101, 71, 60, 88, 109],
[17, 39, 103, 64, 61, 92], [101, 45, 83, 59, 92, 27]]
cost = [
[90, 76, 75, 70, 50, 74],
[35, 85, 55, 65, 48, 101],
[125, 95, 90, 105, 59, 120],
[45, 110, 95, 115, 104, 83],
[60, 105, 80, 75, 59, 62],
[45, 65, 110, 95, 47, 31],
[38, 51, 107, 41, 69, 99],
[47, 85, 57, 71, 92, 77],
[39, 63, 97, 49, 118, 56],
[47, 101, 71, 60, 88, 109],
[17, 39, 103, 64, 61, 92],
[101, 45, 83, 59, 92, 27],
]
group1 = [
[0, 0, 1, 1], # Workers 2, 3
[0, 1, 0, 1], # Workers 1, 3
[0, 1, 1, 0], # Workers 1, 2
[1, 1, 0, 0], # Workers 0, 1
[1, 0, 1, 0]
[1, 0, 1, 0],
] # Workers 0, 2
group2 = [
@@ -41,7 +50,7 @@ def solve_assignment():
[0, 1, 0, 1], # Workers 5, 7
[0, 1, 1, 0], # Workers 5, 6
[1, 1, 0, 0], # Workers 4, 5
[1, 0, 0, 1]
[1, 0, 0, 1],
] # Workers 4, 7
group3 = [
@@ -49,7 +58,7 @@ def solve_assignment():
[0, 1, 0, 1], # Workers 9, 11
[0, 1, 1, 0], # Workers 9, 10
[1, 0, 1, 0], # Workers 8, 10
[1, 0, 0, 1]
[1, 0, 0, 1],
] # Workers 8, 11
sizes = [10, 7, 3, 12, 15, 4, 11, 5]
@@ -63,10 +72,10 @@ def solve_assignment():
model = cp_model.CpModel()
# Variables
selected = [[model.NewBoolVar('x[%i,%i]' % (i, j))
for j in all_tasks]
for i in all_workers]
works = [model.NewBoolVar('works[%i]' % i) for i in all_workers]
selected = [
[model.NewBoolVar("x[%i,%i]" % (i, j)) for j in all_tasks] for i in all_workers
]
works = [model.NewBoolVar("works[%i]" % i) for i in all_workers]
# Constraints
@@ -80,49 +89,45 @@ def solve_assignment():
# Total task size for each worker is at most total_size_max
for i in all_workers:
model.Add(
sum(sizes[j] * selected[i][j] for j in all_tasks) <= total_size_max)
model.Add(sum(sizes[j] * selected[i][j] for j in all_tasks) <= total_size_max)
# Group constraints.
model.AddAllowedAssignments([works[0], works[1], works[2], works[3]],
group1)
model.AddAllowedAssignments([works[4], works[5], works[6], works[7]],
group2)
model.AddAllowedAssignments([works[8], works[9], works[10], works[11]],
group3)
model.AddAllowedAssignments([works[0], works[1], works[2], works[3]], group1)
model.AddAllowedAssignments([works[4], works[5], works[6], works[7]], group2)
model.AddAllowedAssignments([works[8], works[9], works[10], works[11]], group3)
# Objective
model.Minimize(
sum(selected[i][j] * cost[i][j]
for j in all_tasks
for i in all_workers))
sum(selected[i][j] * cost[i][j] for j in all_tasks for i in all_workers)
)
# Solve and output solution.
solver = cp_model.CpSolver()
status = solver.Solve(model)
if status == cp_model.OPTIMAL:
print('Total cost = %i' % solver.ObjectiveValue())
print("Total cost = %i" % solver.ObjectiveValue())
print()
for i in all_workers:
for j in all_tasks:
if solver.BooleanValue(selected[i][j]):
print('Worker ', i, ' assigned to task ', j, ' Cost = ',
cost[i][j])
print(
"Worker ", i, " assigned to task ", j, " Cost = ", cost[i][j]
)
print()
print('Statistics')
print(' - conflicts : %i' % solver.NumConflicts())
print(' - branches : %i' % solver.NumBranches())
print(' - wall time : %f s' % solver.WallTime())
print("Statistics")
print(" - conflicts : %i" % solver.NumConflicts())
print(" - branches : %i" % solver.NumBranches())
print(" - wall time : %f s" % solver.WallTime())
def main(argv: Sequence[str]) -> None:
if len(argv) > 1:
raise app.UsageError('Too many command-line arguments.')
raise app.UsageError("Too many command-line arguments.")
solve_assignment()
if __name__ == '__main__':
if __name__ == "__main__":
app.run(main)

View File

@@ -11,6 +11,7 @@
# 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.
"""We are trying to group items in equal sized groups.
Each item has a color and a value. We want the sum of values of each group to
@@ -37,10 +38,10 @@ class SolutionPrinter(cp_model.CpSolverSolutionCallback):
self.__item_in_group = item_in_group
def on_solution_callback(self):
print('Solution %i' % self.__solution_count)
print("Solution %i" % self.__solution_count)
self.__solution_count += 1
print(' objective value = %i' % self.ObjectiveValue())
print(" objective value = %i" % self.ObjectiveValue())
groups = {}
sums = {}
for g in self.__all_groups:
@@ -53,19 +54,19 @@ class SolutionPrinter(cp_model.CpSolverSolutionCallback):
for g in self.__all_groups:
group = groups[g]
print('group %i: sum = %0.2f [' % (g, sums[g]), end='')
print("group %i: sum = %0.2f [" % (g, sums[g]), end="")
for item in group:
value = self.__values[item]
color = self.__colors[item]
print(' (%i, %i, %i)' % (item, value, color), end='')
print(']')
print(" (%i, %i, %i)" % (item, value, color), end="")
print("]")
def main(argv: Sequence[str]) -> None:
"""Solves a group balancing problem."""
if len(argv) > 1:
raise app.UsageError('Too many command-line arguments.')
raise app.UsageError("Too many command-line arguments.")
# Data.
num_groups = 10
num_items = 100
@@ -94,9 +95,11 @@ def main(argv: Sequence[str]) -> None:
if colors[i] == c:
items_per_color[c].append(i)
print('Model has %i items, %i groups, and %i colors' %
(num_items, num_groups, num_colors))
print(' average sum per group = %i' % average_sum_per_group)
print(
"Model has %i items, %i groups, and %i colors"
% (num_items, num_groups, num_colors)
)
print(" average sum per group = %i" % average_sum_per_group)
# Model.
@@ -105,43 +108,42 @@ def main(argv: Sequence[str]) -> None:
item_in_group = {}
for i in all_items:
for g in all_groups:
item_in_group[(i, g)] = model.NewBoolVar('item %d in group %d' %
(i, g))
item_in_group[(i, g)] = model.NewBoolVar("item %d in group %d" % (i, g))
# Each group must have the same size.
for g in all_groups:
model.Add(
sum(item_in_group[(i, g)]
for i in all_items) == num_items_per_group)
model.Add(sum(item_in_group[(i, g)] for i in all_items) == num_items_per_group)
# One item must belong to exactly one group.
for i in all_items:
model.Add(sum(item_in_group[(i, g)] for g in all_groups) == 1)
# The deviation of the sum of each items in a group against the average.
e = model.NewIntVar(0, 550, 'epsilon')
e = model.NewIntVar(0, 550, "epsilon")
# Constrain the sum of values in one group around the average sum per group.
for g in all_groups:
model.Add(
sum(item_in_group[(i, g)] * values[i]
for i in all_items) <= average_sum_per_group + e)
sum(item_in_group[(i, g)] * values[i] for i in all_items)
<= average_sum_per_group + e
)
model.Add(
sum(item_in_group[(i, g)] * values[i]
for i in all_items) >= average_sum_per_group - e)
sum(item_in_group[(i, g)] * values[i] for i in all_items)
>= average_sum_per_group - e
)
# color_in_group variables.
color_in_group = {}
for g in all_groups:
for c in all_colors:
color_in_group[(c, g)] = model.NewBoolVar(
'color %d is in group %d' % (c, g))
"color %d is in group %d" % (c, g)
)
# Item is in a group implies its color is in that group.
for i in all_items:
for g in all_groups:
model.AddImplication(item_in_group[(i, g)],
color_in_group[(colors[i], g)])
model.AddImplication(item_in_group[(i, g)], color_in_group[(colors[i], g)])
# If a color is in a group, it must contains at least
# min_items_of_same_color_per_group items from that color.
@@ -149,8 +151,9 @@ def main(argv: Sequence[str]) -> None:
for g in all_groups:
literal = color_in_group[(c, g)]
model.Add(
sum(item_in_group[(i, g)] for i in items_per_color[c]) >=
min_items_of_same_color_per_group).OnlyEnforceIf(literal)
sum(item_in_group[(i, g)] for i in items_per_color[c])
>= min_items_of_same_color_per_group
).OnlyEnforceIf(literal)
# Compute the maximum number of colors in a group.
max_color = num_items_per_group // min_items_of_same_color_per_group
@@ -158,8 +161,7 @@ def main(argv: Sequence[str]) -> None:
# Redundant constraint, it helps with solving time.
if max_color < num_colors:
for g in all_groups:
model.Add(
sum(color_in_group[(c, g)] for c in all_colors) <= max_color)
model.Add(sum(color_in_group[(c, g)] for c in all_colors) <= max_color)
# Minimize epsilon
model.Minimize(e)
@@ -167,19 +169,20 @@ def main(argv: Sequence[str]) -> None:
solver = cp_model.CpSolver()
# solver.parameters.log_search_progress = True
solver.parameters.num_workers = 16
solution_printer = SolutionPrinter(values, colors, all_groups, all_items,
item_in_group)
solution_printer = SolutionPrinter(
values, colors, all_groups, all_items, item_in_group
)
status = solver.Solve(model, solution_printer)
if status == cp_model.OPTIMAL:
print('Optimal epsilon: %i' % solver.ObjectiveValue())
print('Statistics')
print(' - conflicts : %i' % solver.NumConflicts())
print(' - branches : %i' % solver.NumBranches())
print(' - wall time : %f s' % solver.WallTime())
print("Optimal epsilon: %i" % solver.ObjectiveValue())
print("Statistics")
print(" - conflicts : %i" % solver.NumConflicts())
print(" - branches : %i" % solver.NumBranches())
print(" - wall time : %f s" % solver.WallTime())
else:
print('No solution found')
print("No solution found")
if __name__ == '__main__':
if __name__ == "__main__":
app.run(main)

3449
examples/python/bus_driver_scheduling_sat.py Executable file → Normal file

File diff suppressed because it is too large Load Diff

28
examples/python/chemical_balance_sat.py Executable file → Normal file
View File

@@ -11,6 +11,7 @@
# 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.
"""We are trying to group items in equal sized groups.
Each item has a color and a value. We want the sum of values of each group to be
@@ -59,9 +60,13 @@ def chemical_balance():
max_set = [
int(
math.ceil(
min(max_quantities[q][1] * 1000 / chemical_set[s][q + 1]
min(
max_quantities[q][1] * 1000 / chemical_set[s][q + 1]
for q in all_products
if chemical_set[s][q + 1] != 0)))
if chemical_set[s][q + 1] != 0
)
)
)
for s in all_sets
]
@@ -71,14 +76,13 @@ def chemical_balance():
for p in all_products:
model.Add(
sum(
int(chemical_set[s][p + 1] * 10) * set_vars[s]
for s in all_sets) <= int(max_quantities[p][1] * 10000))
sum(int(chemical_set[s][p + 1] * 10) * set_vars[s] for s in all_sets)
<= int(max_quantities[p][1] * 10000)
)
model.Add(
sum(
int(chemical_set[s][p + 1] * 10) * set_vars[s]
for s in all_sets) >= int(max_quantities[p][1] * 10000) -
epsilon)
sum(int(chemical_set[s][p + 1] * 10) * set_vars[s] for s in all_sets)
>= int(max_quantities[p][1] * 10000) - epsilon
)
model.Minimize(epsilon)
@@ -90,15 +94,15 @@ def chemical_balance():
print(f"Optimal objective value = {solver.ObjectiveValue() / 10000.0}")
for s in all_sets:
print(f" {chemical_set[s][0]} = {solver.Value(set_vars[s]) / 1000.0}",
end=" ")
print(f" {chemical_set[s][0]} = {solver.Value(set_vars[s]) / 1000.0}", end=" ")
print()
for p in all_products:
name = max_quantities[p][0]
max_quantity = max_quantities[p][1]
quantity = sum(
solver.Value(set_vars[s]) / 1000.0 * chemical_set[s][p + 1]
for s in all_sets)
for s in all_sets
)
print(f"{name}: {quantity} out of {max_quantity}")

File diff suppressed because it is too large Load Diff

View File

@@ -11,6 +11,7 @@
# 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.
"""Fill a 60x50 rectangle by a minimum number of non-overlapping squares."""
from typing import Sequence
@@ -34,16 +35,16 @@ def cover_rectangle(num_squares):
# Creates intervals for the NoOverlap2D and size variables.
for i in range(num_squares):
size = model.NewIntVar(1, size_y, 'size_%i' % i)
start_x = model.NewIntVar(0, size_x, 'sx_%i' % i)
end_x = model.NewIntVar(0, size_x, 'ex_%i' % i)
start_y = model.NewIntVar(0, size_y, 'sy_%i' % i)
end_y = model.NewIntVar(0, size_y, 'ey_%i' % i)
size = model.NewIntVar(1, size_y, "size_%i" % i)
start_x = model.NewIntVar(0, size_x, "sx_%i" % i)
end_x = model.NewIntVar(0, size_x, "ex_%i" % i)
start_y = model.NewIntVar(0, size_y, "sy_%i" % i)
end_y = model.NewIntVar(0, size_y, "ey_%i" % i)
interval_x = model.NewIntervalVar(start_x, size, end_x, 'ix_%i' % i)
interval_y = model.NewIntervalVar(start_y, size, end_y, 'iy_%i' % i)
interval_x = model.NewIntervalVar(start_x, size, end_x, "ix_%i" % i)
interval_y = model.NewIntervalVar(start_y, size, end_y, "iy_%i" % i)
area = model.NewIntVar(1, size_y * size_y, 'area_%i' % i)
area = model.NewIntVar(1, size_y * size_y, "area_%i" % i)
model.AddMultiplicationEquality(area, [size, size])
areas.append(area)
@@ -68,7 +69,7 @@ def cover_rectangle(num_squares):
model.Add(sizes[i] <= sizes[i + 1])
# Define same to be true iff sizes[i] == sizes[i + 1]
same = model.NewBoolVar('')
same = model.NewBoolVar("")
model.Add(sizes[i] == sizes[i + 1]).OnlyEnforceIf(same)
model.Add(sizes[i] < sizes[i + 1]).OnlyEnforceIf(same.Not())
@@ -83,36 +84,38 @@ def cover_rectangle(num_squares):
solver = cp_model.CpSolver()
solver.parameters.num_workers = 8
status = solver.Solve(model)
print('%s found in %0.2fs' % (solver.StatusName(status), solver.WallTime()))
print("%s found in %0.2fs" % (solver.StatusName(status), solver.WallTime()))
# Prints solution.
if status == cp_model.OPTIMAL:
display = [[' ' for _ in range(size_x)] for _ in range(size_y)]
display = [[" " for _ in range(size_x)] for _ in range(size_y)]
for i in range(num_squares):
sol_x = solver.Value(x_starts[i])
sol_y = solver.Value(y_starts[i])
sol_s = solver.Value(sizes[i])
char = format(i, '01x')
char = format(i, "01x")
for j in range(sol_s):
for k in range(sol_s):
if display[sol_y + j][sol_x + k] != ' ':
print('ERROR between %s and %s' %
(display[sol_y + j][sol_x + k], char))
if display[sol_y + j][sol_x + k] != " ":
print(
"ERROR between %s and %s"
% (display[sol_y + j][sol_x + k], char)
)
display[sol_y + j][sol_x + k] = char
for line in range(size_y):
print(' '.join(display[line]))
print(" ".join(display[line]))
return status == cp_model.OPTIMAL
def main(argv: Sequence[str]) -> None:
if len(argv) > 1:
raise app.UsageError('Too many command-line arguments.')
raise app.UsageError("Too many command-line arguments.")
for num_squares in range(1, 15):
print('Trying with size =', num_squares)
print("Trying with size =", num_squares)
if cover_rectangle(num_squares):
break
if __name__ == '__main__':
if __name__ == "__main__":
app.run(main)

View File

@@ -11,6 +11,7 @@
# 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.
"""Use CP-SAT to solve a simple cryptarithmetic problem: SEND+MORE=MONEY.
"""
@@ -19,28 +20,27 @@ from ortools.sat.python import cp_model
def send_more_money():
"""Solve the cryptarithmic puzzle SEND+MORE=MONEY.
"""
"""Solve the cryptarithmic puzzle SEND+MORE=MONEY."""
model = cp_model.CpModel()
# Create variables.
# Since s is a leading digit, it can't be 0.
s = model.NewIntVar(1, 9, 's')
e = model.NewIntVar(0, 9, 'e')
n = model.NewIntVar(0, 9, 'n')
d = model.NewIntVar(0, 9, 'd')
s = model.NewIntVar(1, 9, "s")
e = model.NewIntVar(0, 9, "e")
n = model.NewIntVar(0, 9, "n")
d = model.NewIntVar(0, 9, "d")
# Since m is a leading digit, it can't be 0.
m = model.NewIntVar(1, 9, 'm')
o = model.NewIntVar(0, 9, 'o')
r = model.NewIntVar(0, 9, 'r')
y = model.NewIntVar(0, 9, 'y')
m = model.NewIntVar(1, 9, "m")
o = model.NewIntVar(0, 9, "o")
r = model.NewIntVar(0, 9, "r")
y = model.NewIntVar(0, 9, "y")
# Create carry variables. c0 is true if the first column of addends carries
# a 1, c2 is true if the second column carries a 1, and so on.
c0 = model.NewBoolVar('c0')
c1 = model.NewBoolVar('c1')
c2 = model.NewBoolVar('c2')
c3 = model.NewBoolVar('c3')
c0 = model.NewBoolVar("c0")
c1 = model.NewBoolVar("c1")
c2 = model.NewBoolVar("c2")
c3 = model.NewBoolVar("c3")
# Force all letters to take on different values.
model.AddAllDifferent(s, e, n, d, m, o, r, y)
@@ -63,20 +63,20 @@ def send_more_money():
# Solve model.
solver = cp_model.CpSolver()
if solver.Solve(model) == cp_model.OPTIMAL:
print('Optimal solution found!')
print('s:', solver.Value(s))
print('e:', solver.Value(e))
print('n:', solver.Value(n))
print('d:', solver.Value(d))
print('m:', solver.Value(m))
print('o:', solver.Value(o))
print('r:', solver.Value(r))
print('y:', solver.Value(y))
print("Optimal solution found!")
print("s:", solver.Value(s))
print("e:", solver.Value(e))
print("n:", solver.Value(n))
print("d:", solver.Value(d))
print("m:", solver.Value(m))
print("o:", solver.Value(o))
print("r:", solver.Value(r))
print("y:", solver.Value(y))
def main(_):
send_more_money()
if __name__ == '__main__':
if __name__ == "__main__":
app.run(main)

58
examples/python/flexible_job_shop_sat.py Executable file → Normal file
View File

@@ -11,6 +11,7 @@
# 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.
"""Solves a flexible jobshop problems with the CP-SAT solver.
A jobshop is a standard scheduling problem when you must sequence a
@@ -38,8 +39,10 @@ class SolutionPrinter(cp_model.CpSolverSolutionCallback):
def on_solution_callback(self):
"""Called at each new solution."""
print('Solution %i, time = %f s, objective = %i' %
(self.__solution_count, self.WallTime(), self.ObjectiveValue()))
print(
"Solution %i, time = %f s, objective = %i"
% (self.__solution_count, self.WallTime(), self.ObjectiveValue())
)
self.__solution_count += 1
@@ -81,7 +84,7 @@ def flexible_jobshop():
max_task_duration = max(max_task_duration, alternative[0])
horizon += max_task_duration
print('Horizon = %i' % horizon)
print("Horizon = %i" % horizon)
# Global storage of variables.
intervals_per_resources = collections.defaultdict(list)
@@ -109,13 +112,15 @@ def flexible_jobshop():
max_duration = max(max_duration, alt_duration)
# Create main interval for the task.
suffix_name = '_j%i_t%i' % (job_id, task_id)
start = model.NewIntVar(0, horizon, 'start' + suffix_name)
duration = model.NewIntVar(min_duration, max_duration,
'duration' + suffix_name)
end = model.NewIntVar(0, horizon, 'end' + suffix_name)
interval = model.NewIntervalVar(start, duration, end,
'interval' + suffix_name)
suffix_name = "_j%i_t%i" % (job_id, task_id)
start = model.NewIntVar(0, horizon, "start" + suffix_name)
duration = model.NewIntVar(
min_duration, max_duration, "duration" + suffix_name
)
end = model.NewIntVar(0, horizon, "end" + suffix_name)
interval = model.NewIntervalVar(
start, duration, end, "interval" + suffix_name
)
# Store the start for the solution.
starts[(job_id, task_id)] = start
@@ -129,14 +134,14 @@ def flexible_jobshop():
if num_alternatives > 1:
l_presences = []
for alt_id in all_alternatives:
alt_suffix = '_j%i_t%i_a%i' % (job_id, task_id, alt_id)
l_presence = model.NewBoolVar('presence' + alt_suffix)
l_start = model.NewIntVar(0, horizon, 'start' + alt_suffix)
alt_suffix = "_j%i_t%i_a%i" % (job_id, task_id, alt_id)
l_presence = model.NewBoolVar("presence" + alt_suffix)
l_start = model.NewIntVar(0, horizon, "start" + alt_suffix)
l_duration = task[alt_id][0]
l_end = model.NewIntVar(0, horizon, 'end' + alt_suffix)
l_end = model.NewIntVar(0, horizon, "end" + alt_suffix)
l_interval = model.NewOptionalIntervalVar(
l_start, l_duration, l_end, l_presence,
'interval' + alt_suffix)
l_start, l_duration, l_end, l_presence, "interval" + alt_suffix
)
l_presences.append(l_presence)
# Link the primary/global variables with the local ones.
@@ -165,7 +170,7 @@ def flexible_jobshop():
model.AddNoOverlap(intervals)
# Makespan objective
makespan = model.NewIntVar(0, horizon, 'makespan')
makespan = model.NewIntVar(0, horizon, "makespan")
model.AddMaxEquality(makespan, job_ends)
model.Minimize(makespan)
@@ -176,7 +181,7 @@ def flexible_jobshop():
# Print final solution.
for job_id in all_jobs:
print('Job %i:' % job_id)
print("Job %i:" % job_id)
for task_id in range(len(jobs[job_id])):
start_value = solver.Value(starts[(job_id, task_id)])
machine = -1
@@ -188,15 +193,16 @@ def flexible_jobshop():
machine = jobs[job_id][task_id][alt_id][1]
selected = alt_id
print(
' task_%i_%i starts at %i (alt %i, machine %i, duration %i)' %
(job_id, task_id, start_value, selected, machine, duration))
" task_%i_%i starts at %i (alt %i, machine %i, duration %i)"
% (job_id, task_id, start_value, selected, machine, duration)
)
print('Solve status: %s' % solver.StatusName(status))
print('Optimal objective value: %i' % solver.ObjectiveValue())
print('Statistics')
print(' - conflicts : %i' % solver.NumConflicts())
print(' - branches : %i' % solver.NumBranches())
print(' - wall time : %f s' % solver.WallTime())
print("Solve status: %s" % solver.StatusName(status))
print("Optimal objective value: %i" % solver.ObjectiveValue())
print("Statistics")
print(" - conflicts : %i" % solver.NumConflicts())
print(" - branches : %i" % solver.NumBranches())
print(" - wall time : %f s" % solver.WallTime())
flexible_jobshop()

62
examples/python/gate_scheduling_sat.py Executable file → Normal file
View File

@@ -11,6 +11,7 @@
# 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.
"""Gate Scheduling problem.
We have a set of jobs to perform (duration, width).
@@ -47,7 +48,7 @@ def main(_):
[1, 2],
[6, 8],
[4, 5],
[3, 7]
[3, 7],
]
max_width = 10
@@ -66,31 +67,31 @@ def main(_):
for i in all_jobs:
# Create main interval.
start = model.NewIntVar(0, horizon, 'start_%i' % i)
start = model.NewIntVar(0, horizon, "start_%i" % i)
duration = jobs[i][0]
end = model.NewIntVar(0, horizon, 'end_%i' % i)
interval = model.NewIntervalVar(start, duration, end, 'interval_%i' % i)
end = model.NewIntVar(0, horizon, "end_%i" % i)
interval = model.NewIntervalVar(start, duration, end, "interval_%i" % i)
starts.append(start)
intervals.append(interval)
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_on_m0 = model.NewBoolVar("perform_%i_on_m0" % i)
performed.append(performed_on_m0)
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)
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
)
intervals0.append(interval0)
# Create an optional copy of interval to be executed on machine 1.
start1 = model.NewIntVar(0, horizon, 'start_%i_on_m1' % i)
end1 = model.NewIntVar(0, horizon, 'end_%i_on_m1' % i)
interval1 = model.NewOptionalIntervalVar(start1, duration, end1,
performed_on_m0.Not(),
'interval_%i_on_m1' % i)
start1 = model.NewIntVar(0, horizon, "start_%i_on_m1" % i)
end1 = model.NewIntVar(0, horizon, "end_%i_on_m1" % i)
interval1 = model.NewOptionalIntervalVar(
start1, duration, end1, performed_on_m0.Not(), "interval_%i_on_m1" % i
)
intervals1.append(interval1)
# We only propagate the constraint if the tasks is performed on the machine.
@@ -105,7 +106,7 @@ def main(_):
model.AddNoOverlap(intervals1)
# Objective variable.
makespan = model.NewIntVar(0, horizon, 'makespan')
makespan = model.NewIntVar(0, horizon, "makespan")
model.AddMaxEquality(makespan, ends)
model.Minimize(makespan)
@@ -118,9 +119,8 @@ def main(_):
# Output solution.
if visualization.RunFromIPython():
output = visualization.SvgWrapper(solver.ObjectiveValue(), max_width,
40.0)
output.AddTitle('Makespan = %i' % solver.ObjectiveValue())
output = visualization.SvgWrapper(solver.ObjectiveValue(), max_width, 40.0)
output.AddTitle("Makespan = %i" % solver.ObjectiveValue())
color_manager = visualization.ColorManager()
color_manager.SeedRandomColor(0)
@@ -130,25 +130,27 @@ def main(_):
d_x = jobs[i][0]
d_y = jobs[i][1]
s_y = performed_machine * (max_width - d_y)
output.AddRectangle(start, s_y, d_x, d_y,
color_manager.RandomColor(), 'black', 'j%i' % i)
output.AddRectangle(
start, s_y, d_x, d_y, color_manager.RandomColor(), "black", "j%i" % i
)
output.AddXScale()
output.AddYScale()
output.Display()
else:
print('Solution')
print(' - makespan = %i' % solver.ObjectiveValue())
print("Solution")
print(" - makespan = %i" % solver.ObjectiveValue())
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('Statistics')
print(' - conflicts : %i' % solver.NumConflicts())
print(' - branches : %i' % solver.NumBranches())
print(' - wall time : %f s' % solver.WallTime())
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())
print(" - wall time : %f s" % solver.WallTime())
if __name__ == '__main__':
if __name__ == "__main__":
app.run(main)

View File

@@ -11,6 +11,7 @@
# 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.
"""This is the Golomb ruler problem.
This model aims at maximizing radar interferences in a minimum space.
@@ -34,13 +35,13 @@ FLAGS = flags.FLAGS
def main(_):
# Create the solver.
solver = pywrapcp.Solver('golomb ruler')
solver = pywrapcp.Solver("golomb ruler")
size = 8
var_max = size * size
all_vars = list(range(0, size))
marks = [solver.IntVar(0, var_max, 'marks_%d' % i) for i in all_vars]
marks = [solver.IntVar(0, var_max, "marks_%d" % i) for i in all_vars]
objective = solver.Minimize(marks[size - 1], 1)
@@ -62,21 +63,28 @@ def main(_):
collector = solver.AllSolutionCollector(solution)
solver.Solve(
solver.Phase(marks, solver.CHOOSE_FIRST_UNBOUND,
solver.ASSIGN_MIN_VALUE), [objective, collector])
solver.Phase(marks, solver.CHOOSE_FIRST_UNBOUND, solver.ASSIGN_MIN_VALUE),
[objective, collector],
)
for i in range(0, collector.SolutionCount()):
obj_value = collector.Value(i, marks[size - 1])
time = collector.WallTime(i)
branches = collector.Branches(i)
failures = collector.Failures(i)
print(('Solution #%i: value = %i, failures = %i, branches = %i,'
'time = %i ms') % (i, obj_value, failures, branches, time))
print(
("Solution #%i: value = %i, failures = %i, branches = %i," "time = %i ms")
% (i, obj_value, failures, branches, time)
)
time = solver.WallTime()
branches = solver.Branches()
failures = solver.Failures()
print(('Total run : failures = %i, branches = %i, time = %i ms' %
(failures, branches, time)))
print(
(
"Total run : failures = %i, branches = %i, time = %i ms"
% (failures, branches, time)
)
)
if __name__ == '__main__':
if __name__ == "__main__":
app.run(main)

34
examples/python/golomb_sat.py Executable file → Normal file
View File

@@ -11,6 +11,7 @@
# 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.
"""This is the Golomb ruler problem.
This model aims at maximizing radar interferences in a minimum space.
@@ -29,11 +30,12 @@ from absl import flags
from google.protobuf import text_format
from ortools.sat.python import cp_model
_ORDER = flags.DEFINE_integer('order', 8, 'Order of the ruler.')
_ORDER = flags.DEFINE_integer("order", 8, "Order of the ruler.")
_PARAMS = flags.DEFINE_string(
'params',
'num_search_workers:16,log_search_progress:true,max_time_in_seconds:45',
'Sat solver parameters.')
"params",
"num_search_workers:16,log_search_progress:true,max_time_in_seconds:45",
"Sat solver parameters.",
)
def solve_golomb_ruler(order, params):
@@ -44,7 +46,7 @@ def solve_golomb_ruler(order, params):
var_max = order * order
all_vars = list(range(0, order))
marks = [model.NewIntVar(0, var_max, f'marks_{i}') for i in all_vars]
marks = [model.NewIntVar(0, var_max, f"marks_{i}") for i in all_vars]
model.Add(marks[0] == 0)
for i in range(order - 2):
@@ -53,7 +55,7 @@ def solve_golomb_ruler(order, params):
diffs = []
for i in range(order - 1):
for j in range(i + 1, order):
diff = model.NewIntVar(0, var_max, f'diff [{j},{i}]')
diff = model.NewIntVar(0, var_max, f"diff [{j},{i}]")
model.Add(diff == marks[j] - marks[i])
diffs.append(diff)
model.AddAllDifferent(diffs)
@@ -70,29 +72,29 @@ def solve_golomb_ruler(order, params):
if params:
text_format.Parse(params, solver.parameters)
solution_printer = cp_model.ObjectiveSolutionPrinter()
print(f'Golomb ruler(order={order})')
print(f"Golomb ruler(order={order})")
status = solver.Solve(model, solution_printer)
# Print solution.
print(f'status: {solver.StatusName(status)}')
print(f"status: {solver.StatusName(status)}")
if status in (cp_model.OPTIMAL, cp_model.FEASIBLE):
for idx, var in enumerate(marks):
print(f'mark[{idx}]: {solver.Value(var)}')
print(f"mark[{idx}]: {solver.Value(var)}")
intervals = [solver.Value(diff) for diff in diffs]
intervals.sort()
print(f'intervals: {intervals}')
print(f"intervals: {intervals}")
print('Statistics:')
print(f'- conflicts: {solver.NumConflicts()}')
print(f'- branches : {solver.NumBranches()}')
print(f'- wall time: {solver.WallTime()}s\n')
print("Statistics:")
print(f"- conflicts: {solver.NumConflicts()}")
print(f"- branches : {solver.NumBranches()}")
print(f"- wall time: {solver.WallTime()}s\n")
def main(argv: Sequence[str]) -> None:
if len(argv) > 1:
raise app.UsageError('Too many command-line arguments.')
raise app.UsageError("Too many command-line arguments.")
solve_golomb_ruler(_ORDER.value, _PARAMS.value)
if __name__ == '__main__':
if __name__ == "__main__":
app.run(main)

126
examples/python/hidato_sat.py Normal file → Executable file
View File

@@ -11,6 +11,7 @@
# 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.
"""Solves the Hidato problem with the CP-SAT solver."""
from absl import app
@@ -21,25 +22,29 @@ from ortools.sat.python import cp_model
def build_pairs(rows, cols):
"""Build closeness pairs for consecutive numbers.
Build set of allowed pairs such that two consecutive numbers touch
each other in the grid.
Build set of allowed pairs such that two consecutive numbers touch
each other in the grid.
Returns:
A list of pairs for allowed consecutive position of numbers.
Returns:
A list of pairs for allowed consecutive position of numbers.
Args:
rows: the number of rows in the grid
cols: the number of columns in the grid
"""
Args:
rows: the number of rows in the grid
cols: the number of columns in the grid
"""
result = []
for x in range(rows):
for y in range(cols):
for dx in (-1, 0, 1):
for dy in (-1, 0, 1):
if (x + dx >= 0 and x + dx < rows and y + dy >= 0 and
y + dy < cols and (dx != 0 or dy != 0)):
result.append(
(x * cols + y, (x + dx) * cols + (y + dy)))
if (
x + dx >= 0
and x + dx < rows
and y + dy >= 0
and y + dy < cols
and (dx != 0 or dy != 0)
):
result.append((x * cols + y, (x + dx) * cols + (y + dy)))
return result
@@ -54,7 +59,7 @@ def print_solution(positions, rows, cols):
position = positions[k]
board[position // cols][position % cols] = k + 1
# Print the board.
print('Solution')
print("Solution")
print_matrix(board)
@@ -63,12 +68,12 @@ def print_matrix(game):
rows = len(game)
cols = len(game[0])
for i in range(rows):
line = ''
line = ""
for j in range(cols):
if game[i][j] == 0:
line += ' .'
line += " ."
else:
line += '% 3s' % game[i][j]
line += "% 3s" % game[i][j]
print(line)
@@ -84,34 +89,60 @@ def build_puzzle(problem):
puzzle = [[6, 0, 9], [0, 2, 8], [1, 0, 0]]
elif problem == 2:
puzzle = [[0, 44, 41, 0, 0, 0, 0], [0, 43, 0, 28, 29, 0, 0],
[0, 1, 0, 0, 0, 33, 0], [0, 2, 25, 4, 34, 0, 36],
[49, 16, 0, 23, 0, 0, 0], [0, 19, 0, 0, 12, 7, 0],
[0, 0, 0, 14, 0, 0, 0]]
puzzle = [
[0, 44, 41, 0, 0, 0, 0],
[0, 43, 0, 28, 29, 0, 0],
[0, 1, 0, 0, 0, 33, 0],
[0, 2, 25, 4, 34, 0, 36],
[49, 16, 0, 23, 0, 0, 0],
[0, 19, 0, 0, 12, 7, 0],
[0, 0, 0, 14, 0, 0, 0],
]
elif problem == 3:
# Problems from the book:
# Gyora Bededek: "Hidato: 2000 Pure Logic Puzzles"
# Problem 1 (Practice)
puzzle = [[0, 0, 20, 0, 0], [0, 0, 0, 16, 18], [22, 0, 15, 0, 0],
[23, 0, 1, 14, 11], [0, 25, 0, 0, 12]]
puzzle = [
[0, 0, 20, 0, 0],
[0, 0, 0, 16, 18],
[22, 0, 15, 0, 0],
[23, 0, 1, 14, 11],
[0, 25, 0, 0, 12],
]
elif problem == 4:
# problem 2 (Practice)
puzzle = [[0, 0, 0, 0, 14], [0, 18, 12, 0, 0], [0, 0, 17, 4, 5],
[0, 0, 7, 0, 0], [9, 8, 25, 1, 0]]
puzzle = [
[0, 0, 0, 0, 14],
[0, 18, 12, 0, 0],
[0, 0, 17, 4, 5],
[0, 0, 7, 0, 0],
[9, 8, 25, 1, 0],
]
elif problem == 5:
# problem 3 (Beginner)
puzzle = [[0, 26, 0, 0, 0, 18], [0, 0, 27, 0, 0, 19],
[31, 23, 0, 0, 14, 0], [0, 33, 8, 0, 15, 1],
[0, 0, 0, 5, 0, 0], [35, 36, 0, 10, 0, 0]]
puzzle = [
[0, 26, 0, 0, 0, 18],
[0, 0, 27, 0, 0, 19],
[31, 23, 0, 0, 14, 0],
[0, 33, 8, 0, 15, 1],
[0, 0, 0, 5, 0, 0],
[35, 36, 0, 10, 0, 0],
]
elif problem == 6:
# Problem 15 (Intermediate)
puzzle = [[64, 0, 0, 0, 0, 0, 0, 0], [1, 63, 0, 59, 15, 57, 53, 0],
[0, 4, 0, 14, 0, 0, 0, 0], [3, 0, 11, 0, 20, 19, 0, 50],
[0, 0, 0, 0, 22, 0, 48, 40], [9, 0, 0, 32, 23, 0, 0, 41],
[27, 0, 0, 0, 36, 0, 46, 0], [28, 30, 0, 35, 0, 0, 0, 0]]
puzzle = [
[64, 0, 0, 0, 0, 0, 0, 0],
[1, 63, 0, 59, 15, 57, 53, 0],
[0, 4, 0, 14, 0, 0, 0, 0],
[3, 0, 11, 0, 20, 19, 0, 50],
[0, 0, 0, 0, 22, 0, 48, 40],
[9, 0, 0, 32, 23, 0, 0, 41],
[27, 0, 0, 0, 36, 0, 46, 0],
[28, 30, 0, 35, 0, 0, 0, 0],
]
return puzzle
@@ -123,18 +154,16 @@ def solve_hidato(puzzle, index):
r = len(puzzle)
c = len(puzzle[0])
if not visualization.RunFromIPython():
print('')
print('----- Solving problem %i -----' % index)
print('')
print(('Initial game (%i x %i)' % (r, c)))
print("")
print("----- Solving problem %i -----" % index)
print("")
print(("Initial game (%i x %i)" % (r, c)))
print_matrix(puzzle)
#
# declare variables
#
positions = [
model.NewIntVar(0, r * c - 1, 'p[%i]' % i) for i in range(r * c)
]
positions = [model.NewIntVar(0, r * c - 1, "p[%i]" % i) for i in range(r * c)]
#
# constraints
@@ -153,8 +182,7 @@ def solve_hidato(puzzle, index):
# We use an allowed assignment constraint to model it.
close_tuples = build_pairs(r, c)
for k in range(0, r * c - 1):
model.AddAllowedAssignments([positions[k], positions[k + 1]],
close_tuples)
model.AddAllowedAssignments([positions[k], positions[k + 1]], close_tuples)
#
# solution and search
@@ -170,12 +198,10 @@ def solve_hidato(puzzle, index):
val = solver.Value(var)
x = val % c
y = val // c
color = 'white' if puzzle[y][x] == 0 else 'lightgreen'
output.AddRectangle(x, r - y - 1, 1, 1, color, 'black',
str(i + 1))
color = "white" if puzzle[y][x] == 0 else "lightgreen"
output.AddRectangle(x, r - y - 1, 1, 1, color, "black", str(i + 1))
output.AddTitle('Puzzle %i solved in %f s' %
(index, solver.WallTime()))
output.AddTitle("Puzzle %i solved in %f s" % (index, solver.WallTime()))
output.Display()
else:
print_solution(
@@ -183,10 +209,10 @@ def solve_hidato(puzzle, index):
r,
c,
)
print('Statistics')
print(' - conflicts : %i' % solver.NumConflicts())
print(' - branches : %i' % solver.NumBranches())
print(' - wall time : %f s' % solver.WallTime())
print("Statistics")
print(" - conflicts : %i" % solver.NumConflicts())
print(" - branches : %i" % solver.NumBranches())
print(" - wall time : %f s" % solver.WallTime())
def main(_):
@@ -194,5 +220,5 @@ def main(_):
solve_hidato(build_puzzle(pb), pb)
if __name__ == '__main__':
if __name__ == "__main__":
app.run(main)

60
examples/python/integer_programming.py Normal file → Executable file
View File

@@ -11,15 +11,16 @@
# 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.
"""Integer programming examples that show how to use the APIs."""
from ortools.linear_solver import pywraplp
from ortools.init import pywrapinit
def Announce(solver, api_type):
print('---- Integer programming example with ' + solver + ' (' + api_type +
') -----')
print(
"---- Integer programming example with " + solver + " (" + api_type + ") -----"
)
def RunIntegerExampleNaturalLanguageAPI(optimization_problem_type):
@@ -29,12 +30,12 @@ def RunIntegerExampleNaturalLanguageAPI(optimization_problem_type):
if not solver:
return
Announce(optimization_problem_type, 'natural language API')
Announce(optimization_problem_type, "natural language API")
infinity = solver.infinity()
# x1 and x2 are integer non-negative variables.
x1 = solver.IntVar(0.0, infinity, 'x1')
x2 = solver.IntVar(0.0, infinity, 'x2')
x1 = solver.IntVar(0.0, infinity, "x1")
x2 = solver.IntVar(0.0, infinity, "x2")
solver.Minimize(x1 + 2 * x2)
solver.Add(3 * x1 + 2 * x2 >= 17)
@@ -48,12 +49,12 @@ def RunIntegerExampleCppStyleAPI(optimization_problem_type):
if not solver:
return
Announce(optimization_problem_type, 'C++ style API')
Announce(optimization_problem_type, "C++ style API")
infinity = solver.infinity()
# x1 and x2 are integer non-negative variables.
x1 = solver.IntVar(0.0, infinity, 'x1')
x2 = solver.IntVar(0.0, infinity, 'x2')
x1 = solver.IntVar(0.0, infinity, "x1")
x2 = solver.IntVar(0.0, infinity, "x2")
# Minimize x1 + 2 * x2.
objective = solver.Objective()
@@ -70,8 +71,8 @@ def RunIntegerExampleCppStyleAPI(optimization_problem_type):
def SolveAndPrint(solver, variable_list):
"""Solve the problem and print the solution."""
print('Number of variables = %d' % solver.NumVariables())
print('Number of constraints = %d' % solver.NumConstraints())
print("Number of variables = %d" % solver.NumVariables())
print("Number of constraints = %d" % solver.NumConstraints())
result_status = solver.Solve()
@@ -82,33 +83,33 @@ def SolveAndPrint(solver, variable_list):
# GLOP_LINEAR_PROGRAMMING, verifying the solution is highly recommended!).
assert solver.VerifySolution(1e-7, True)
print('Problem solved in %f milliseconds' % solver.wall_time())
print("Problem solved in %f milliseconds" % solver.wall_time())
# The objective value of the solution.
print('Optimal objective value = %f' % solver.Objective().Value())
print("Optimal objective value = %f" % solver.Objective().Value())
# The value of each variable in the solution.
for variable in variable_list:
print('%s = %f' % (variable.name(), variable.solution_value()))
print("%s = %f" % (variable.name(), variable.solution_value()))
print('Advanced usage:')
print('Problem solved in %d branch-and-bound nodes' % solver.nodes())
print("Advanced usage:")
print("Problem solved in %d branch-and-bound nodes" % solver.nodes())
def RunAllIntegerExampleNaturalLanguageAPI():
RunIntegerExampleNaturalLanguageAPI('GLPK')
RunIntegerExampleNaturalLanguageAPI('CBC')
RunIntegerExampleNaturalLanguageAPI('SCIP')
RunIntegerExampleNaturalLanguageAPI('SAT')
RunIntegerExampleNaturalLanguageAPI('Gurobi')
RunIntegerExampleNaturalLanguageAPI("GLPK")
# Disabling due to ASAN errors with CBC.
# RunIntegerExampleNaturalLanguageAPI('CBC')
RunIntegerExampleNaturalLanguageAPI("SCIP")
RunIntegerExampleNaturalLanguageAPI("SAT")
def RunAllIntegerExampleCppStyleAPI():
RunIntegerExampleCppStyleAPI('GLPK')
RunIntegerExampleCppStyleAPI('CBC')
RunIntegerExampleCppStyleAPI('SCIP')
RunIntegerExampleCppStyleAPI('SAT')
RunIntegerExampleCppStyleAPI('Gurobi')
RunIntegerExampleCppStyleAPI("GLPK")
# Disabling due to ASAN errors with CBC.
# RunIntegerExampleCppStyleAPI('CBC')
RunIntegerExampleCppStyleAPI("SCIP")
RunIntegerExampleCppStyleAPI("SAT")
def main():
@@ -116,10 +117,5 @@ def main():
RunAllIntegerExampleCppStyleAPI()
if __name__ == '__main__':
pywrapinit.CppBridge.InitLogging('integer_programming.py')
cpp_flags = pywrapinit.CppFlags()
cpp_flags.stderrthreshold = 0
cpp_flags.log_prefix = False
pywrapinit.CppBridge.SetFlags(cpp_flags)
if __name__ == "__main__":
main()

56
examples/python/jobshop_ft06_distance_sat.py Normal file → Executable file
View File

@@ -11,6 +11,7 @@
# 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.
"""This model implements a variation of the ft06 jobshop.
A jobshop is a standard scheduling problem when you must sequence a
@@ -45,29 +46,42 @@ def jobshop_ft06_distance():
all_machines = range(0, machines_count)
all_jobs = range(0, jobs_count)
durations = [[1, 3, 6, 7, 3, 6], [8, 5, 10, 10, 10, 4], [5, 4, 8, 9, 1, 7],
[5, 5, 5, 3, 8, 9], [9, 3, 5, 4, 3, 1], [3, 3, 9, 10, 4, 1]]
durations = [
[1, 3, 6, 7, 3, 6],
[8, 5, 10, 10, 10, 4],
[5, 4, 8, 9, 1, 7],
[5, 5, 5, 3, 8, 9],
[9, 3, 5, 4, 3, 1],
[3, 3, 9, 10, 4, 1],
]
machines = [[2, 0, 1, 3, 5, 4], [1, 2, 4, 5, 0, 3], [2, 3, 5, 0, 1, 4],
[1, 0, 2, 3, 4, 5], [2, 1, 4, 5, 0, 3], [1, 3, 5, 0, 4, 2]]
machines = [
[2, 0, 1, 3, 5, 4],
[1, 2, 4, 5, 0, 3],
[2, 3, 5, 0, 1, 4],
[1, 0, 2, 3, 4, 5],
[2, 1, 4, 5, 0, 3],
[1, 3, 5, 0, 4, 2],
]
# Computes horizon statically.
horizon = 150
task_type = collections.namedtuple('task_type', 'start end interval')
task_type = collections.namedtuple("task_type", "start end interval")
# Creates jobs.
all_tasks = {}
for i in all_jobs:
for j in all_machines:
start_var = model.NewIntVar(0, horizon, 'start_%i_%i' % (i, j))
start_var = model.NewIntVar(0, horizon, "start_%i_%i" % (i, j))
duration = durations[i][j]
end_var = model.NewIntVar(0, horizon, 'end_%i_%i' % (i, j))
interval_var = model.NewIntervalVar(start_var, duration, end_var,
'interval_%i_%i' % (i, j))
all_tasks[(i, j)] = task_type(start=start_var,
end=end_var,
interval=interval_var)
end_var = model.NewIntVar(0, horizon, "end_%i_%i" % (i, j))
interval_var = model.NewIntervalVar(
start_var, duration, end_var, "interval_%i_%i" % (i, j)
)
all_tasks[(i, j)] = task_type(
start=start_var, end=end_var, interval=interval_var
)
# Create disjuctive constraints.
for i in all_machines:
@@ -87,23 +101,24 @@ def jobshop_ft06_distance():
arcs = []
for j1 in range(len(job_intervals)):
# Initial arc from the dummy node (0) to a task.
start_lit = model.NewBoolVar('%i is first job' % j1)
start_lit = model.NewBoolVar("%i is first job" % j1)
arcs.append([0, j1 + 1, start_lit])
# Final arc from an arc to the dummy node.
arcs.append([j1 + 1, 0, model.NewBoolVar('%i is last job' % j1)])
arcs.append([j1 + 1, 0, model.NewBoolVar("%i is last job" % j1)])
for j2 in range(len(job_intervals)):
if j1 == j2:
continue
lit = model.NewBoolVar('%i follows %i' % (j2, j1))
lit = model.NewBoolVar("%i follows %i" % (j2, j1))
arcs.append([j1 + 1, j2 + 1, lit])
# We add the reified precedence to link the literal with the
# times of the two tasks.
min_distance = distance_between_jobs(j1, j2)
model.Add(job_starts[j2] >= job_ends[j1] +
min_distance).OnlyEnforceIf(lit)
model.Add(job_starts[j2] >= job_ends[j1] + min_distance).OnlyEnforceIf(
lit
)
model.AddCircuit(arcs)
@@ -113,9 +128,10 @@ def jobshop_ft06_distance():
model.Add(all_tasks[(i, j + 1)].start >= all_tasks[(i, j)].end)
# Makespan objective.
obj_var = model.NewIntVar(0, horizon, 'makespan')
obj_var = model.NewIntVar(0, horizon, "makespan")
model.AddMaxEquality(
obj_var, [all_tasks[(i, machines_count - 1)].end for i in all_jobs])
obj_var, [all_tasks[(i, machines_count - 1)].end for i in all_jobs]
)
model.Minimize(obj_var)
# Solve model.
@@ -124,7 +140,7 @@ def jobshop_ft06_distance():
# Output solution.
if status == cp_model.OPTIMAL:
print('Optimal makespan: %i' % solver.ObjectiveValue())
print("Optimal makespan: %i" % solver.ObjectiveValue())
jobshop_ft06_distance()

54
examples/python/jobshop_ft06_sat.py Normal file → Executable file
View File

@@ -11,6 +11,7 @@
# 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.
"""This model implements a simple jobshop named ft06.
A jobshop is a standard scheduling problem when you must sequence a
@@ -38,29 +39,42 @@ def jobshop_ft06():
all_machines = range(0, machines_count)
all_jobs = range(0, jobs_count)
durations = [[1, 3, 6, 7, 3, 6], [8, 5, 10, 10, 10, 4], [5, 4, 8, 9, 1, 7],
[5, 5, 5, 3, 8, 9], [9, 3, 5, 4, 3, 1], [3, 3, 9, 10, 4, 1]]
durations = [
[1, 3, 6, 7, 3, 6],
[8, 5, 10, 10, 10, 4],
[5, 4, 8, 9, 1, 7],
[5, 5, 5, 3, 8, 9],
[9, 3, 5, 4, 3, 1],
[3, 3, 9, 10, 4, 1],
]
machines = [[2, 0, 1, 3, 5, 4], [1, 2, 4, 5, 0, 3], [2, 3, 5, 0, 1, 4],
[1, 0, 2, 3, 4, 5], [2, 1, 4, 5, 0, 3], [1, 3, 5, 0, 4, 2]]
machines = [
[2, 0, 1, 3, 5, 4],
[1, 2, 4, 5, 0, 3],
[2, 3, 5, 0, 1, 4],
[1, 0, 2, 3, 4, 5],
[2, 1, 4, 5, 0, 3],
[1, 3, 5, 0, 4, 2],
]
# Computes horizon dynamically.
horizon = sum([sum(durations[i]) for i in all_jobs])
task_type = collections.namedtuple('task_type', 'start end interval')
task_type = collections.namedtuple("task_type", "start end interval")
# Creates jobs.
all_tasks = {}
for i in all_jobs:
for j in all_machines:
start_var = model.NewIntVar(0, horizon, 'start_%i_%i' % (i, j))
start_var = model.NewIntVar(0, horizon, "start_%i_%i" % (i, j))
duration = durations[i][j]
end_var = model.NewIntVar(0, horizon, 'end_%i_%i' % (i, j))
interval_var = model.NewIntervalVar(start_var, duration, end_var,
'interval_%i_%i' % (i, j))
all_tasks[(i, j)] = task_type(start=start_var,
end=end_var,
interval=interval_var)
end_var = model.NewIntVar(0, horizon, "end_%i_%i" % (i, j))
interval_var = model.NewIntervalVar(
start_var, duration, end_var, "interval_%i_%i" % (i, j)
)
all_tasks[(i, j)] = task_type(
start=start_var, end=end_var, interval=interval_var
)
# Create disjuctive constraints.
machine_to_jobs = {}
@@ -79,9 +93,10 @@ def jobshop_ft06():
model.Add(all_tasks[(i, j + 1)].start >= all_tasks[(i, j)].end)
# Makespan objective.
obj_var = model.NewIntVar(0, horizon, 'makespan')
obj_var = model.NewIntVar(0, horizon, "makespan")
model.AddMaxEquality(
obj_var, [all_tasks[(i, machines_count - 1)].end for i in all_jobs])
obj_var, [all_tasks[(i, machines_count - 1)].end for i in all_jobs]
)
model.Minimize(obj_var)
# Solve model.
@@ -92,12 +107,13 @@ def jobshop_ft06():
# Output solution.
if status == cp_model.OPTIMAL:
if visualization.RunFromIPython():
starts = [[
solver.Value(all_tasks[(i, j)][0]) for j in all_machines
] for i in all_jobs]
visualization.DisplayJobshop(starts, durations, machines, 'FT06')
starts = [
[solver.Value(all_tasks[(i, j)][0]) for j in all_machines]
for i in all_jobs
]
visualization.DisplayJobshop(starts, durations, machines, "FT06")
else:
print('Optimal makespan: %i' % solver.ObjectiveValue())
print("Optimal makespan: %i" % solver.ObjectiveValue())
jobshop_ft06()

View File

@@ -11,6 +11,7 @@
# 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.
"""Jobshop with maintenance tasks using the CP-SAT solver."""
import collections
@@ -28,8 +29,10 @@ class SolutionPrinter(cp_model.CpSolverSolutionCallback):
def on_solution_callback(self):
"""Called at each new solution."""
print('Solution %i, time = %f s, objective = %i' %
(self.__solution_count, self.WallTime(), self.ObjectiveValue()))
print(
"Solution %i, time = %f s, objective = %i"
% (self.__solution_count, self.WallTime(), self.ObjectiveValue())
)
self.__solution_count += 1
@@ -51,10 +54,11 @@ def jobshop_with_maintenance():
horizon = sum(task[1] for job in jobs_data for task in job)
# Named tuple to store information about created variables.
task_type = collections.namedtuple('Task', 'start end interval')
task_type = collections.namedtuple("Task", "start end interval")
# Named tuple to manipulate solution information.
assigned_task_type = collections.namedtuple('assigned_task_type',
'start job index duration')
assigned_task_type = collections.namedtuple(
"assigned_task_type", "start job index duration"
)
# Creates job intervals and add to the corresponding machine lists.
all_tasks = {}
@@ -64,18 +68,19 @@ def jobshop_with_maintenance():
for task_id, task in enumerate(job):
machine = task[0]
duration = task[1]
suffix = '_%i_%i' % (job_id, task_id)
start_var = model.NewIntVar(0, horizon, 'start' + suffix)
end_var = model.NewIntVar(0, horizon, 'end' + suffix)
interval_var = model.NewIntervalVar(start_var, duration, end_var,
'interval' + suffix)
all_tasks[job_id, task_id] = task_type(start=start_var,
end=end_var,
interval=interval_var)
suffix = "_%i_%i" % (job_id, task_id)
start_var = model.NewIntVar(0, horizon, "start" + suffix)
end_var = model.NewIntVar(0, horizon, "end" + suffix)
interval_var = model.NewIntervalVar(
start_var, duration, end_var, "interval" + suffix
)
all_tasks[job_id, task_id] = task_type(
start=start_var, end=end_var, interval=interval_var
)
machine_to_intervals[machine].append(interval_var)
# Add maintenance interval (machine 0 is not available on time {4, 5, 6, 7}).
machine_to_intervals[0].append(model.NewIntervalVar(4, 4, 8, 'weekend_0'))
machine_to_intervals[0].append(model.NewIntervalVar(4, 4, 8, "weekend_0"))
# Create and add disjunctive constraints.
for machine in all_machines:
@@ -84,15 +89,16 @@ def jobshop_with_maintenance():
# Precedences inside a job.
for job_id, job in enumerate(jobs_data):
for task_id in range(len(job) - 1):
model.Add(all_tasks[job_id, task_id +
1].start >= all_tasks[job_id, task_id].end)
model.Add(
all_tasks[job_id, task_id + 1].start >= all_tasks[job_id, task_id].end
)
# Makespan objective.
obj_var = model.NewIntVar(0, horizon, 'makespan')
model.AddMaxEquality(obj_var, [
all_tasks[job_id, len(job) - 1].end
for job_id, job in enumerate(jobs_data)
])
obj_var = model.NewIntVar(0, horizon, "makespan")
model.AddMaxEquality(
obj_var,
[all_tasks[job_id, len(job) - 1].end for job_id, job in enumerate(jobs_data)],
)
model.Minimize(obj_var)
# Solve model.
@@ -108,50 +114,52 @@ def jobshop_with_maintenance():
for task_id, task in enumerate(job):
machine = task[0]
assigned_jobs[machine].append(
assigned_task_type(start=solver.Value(
all_tasks[job_id, task_id].start),
job=job_id,
index=task_id,
duration=task[1]))
assigned_task_type(
start=solver.Value(all_tasks[job_id, task_id].start),
job=job_id,
index=task_id,
duration=task[1],
)
)
# Create per machine output lines.
output = ''
output = ""
for machine in all_machines:
# Sort by starting time.
assigned_jobs[machine].sort()
sol_line_tasks = 'Machine ' + str(machine) + ': '
sol_line = ' '
sol_line_tasks = "Machine " + str(machine) + ": "
sol_line = " "
for assigned_task in assigned_jobs[machine]:
name = 'job_%i_%i' % (assigned_task.job, assigned_task.index)
name = "job_%i_%i" % (assigned_task.job, assigned_task.index)
# Add spaces to output to align columns.
sol_line_tasks += '%-10s' % name
sol_line_tasks += "%-10s" % name
start = assigned_task.start
duration = assigned_task.duration
sol_tmp = '[%i,%i]' % (start, start + duration)
sol_tmp = "[%i,%i]" % (start, start + duration)
# Add spaces to output to align columns.
sol_line += '%-10s' % sol_tmp
sol_line += "%-10s" % sol_tmp
sol_line += '\n'
sol_line_tasks += '\n'
sol_line += "\n"
sol_line_tasks += "\n"
output += sol_line_tasks
output += sol_line
# Finally print the solution found.
print('Optimal Schedule Length: %i' % solver.ObjectiveValue())
print("Optimal Schedule Length: %i" % solver.ObjectiveValue())
print(output)
print('Statistics')
print(' - conflicts : %i' % solver.NumConflicts())
print(' - branches : %i' % solver.NumBranches())
print(' - wall time : %f s' % solver.WallTime())
print("Statistics")
print(" - conflicts : %i" % solver.NumConflicts())
print(" - branches : %i" % solver.NumBranches())
print(" - wall time : %f s" % solver.WallTime())
def main(argv: Sequence[str]) -> None:
if len(argv) > 1:
raise app.UsageError('Too many command-line arguments.')
raise app.UsageError("Too many command-line arguments.")
jobshop_with_maintenance()
if __name__ == '__main__':
if __name__ == "__main__":
app.run(main)

202
examples/python/knapsack_2d_sat.py Executable file → Normal file
View File

@@ -11,6 +11,7 @@
# 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.
"""Solver a 2D rectangle knapsack problem.
This code is adapted from
@@ -29,14 +30,16 @@ from google.protobuf import text_format
from ortools.sat.python import cp_model
_OUTPUT_PROTO = flags.DEFINE_string(
'output_proto', '', 'Output file to write the cp_model proto to.')
_PARAMS = flags.DEFINE_string(
'params',
'num_search_workers:16,log_search_progress:true,max_time_in_seconds:45',
'Sat solver parameters.',
"output_proto", "", "Output file to write the cp_model proto to."
)
_PARAMS = flags.DEFINE_string(
"params",
"num_search_workers:16,log_search_progress:true,max_time_in_seconds:45",
"Sat solver parameters.",
)
_MODEL = flags.DEFINE_string(
"model", "rotation", "'duplicate' or 'rotation' or 'optional'"
)
_MODEL = flags.DEFINE_string('model', 'rotation',
'\'duplicate\' or \'rotation\' or \'optional\'')
def build_data():
@@ -55,25 +58,25 @@ def build_data():
k10 9 11 5 369.560 cyan
"""
data = pd.read_table(io.StringIO(data), sep=r'\s+')
print('Input data')
data = pd.read_table(io.StringIO(data), sep=r"\s+")
print("Input data")
print(data)
max_height = 20
max_width = 30
print(f'Container max_width:{max_width} max_height:{max_height}')
print(f'#Items: {len(data.index)}')
print(f"Container max_width:{max_width} max_height:{max_height}")
print(f"#Items: {len(data.index)}")
return (data, max_height, max_width)
def solve_with_duplicate_items(data, max_height, max_width):
"""Solve the problem by building 2 items (rotated or not) for each item."""
# Derived data (expanded to individual items).
data_widths = data['width'].to_numpy()
data_heights = data['height'].to_numpy()
data_availability = data['available'].to_numpy()
data_values = data['value'].to_numpy()
data_widths = data["width"].to_numpy()
data_heights = data["height"].to_numpy()
data_availability = data["available"].to_numpy()
data_values = data["value"].to_numpy()
# Non duplicated items data.
base_item_widths = np.repeat(data_widths, data_availability)
@@ -102,21 +105,25 @@ def solve_with_duplicate_items(data, max_height, max_width):
for i in range(num_items):
## Is the item used?
is_used.append(model.NewBoolVar(f'is_used{i}'))
is_used.append(model.NewBoolVar(f"is_used{i}"))
## Item coordinates.
x_starts.append(model.NewIntVar(0, max_width, f'x_start{i}'))
x_ends.append(model.NewIntVar(0, max_width, f'x_end{i}'))
y_starts.append(model.NewIntVar(0, max_height, f'y_start{i}'))
y_ends.append(model.NewIntVar(0, max_height, f'y_end{i}'))
x_starts.append(model.NewIntVar(0, max_width, f"x_start{i}"))
x_ends.append(model.NewIntVar(0, max_width, f"x_end{i}"))
y_starts.append(model.NewIntVar(0, max_height, f"y_start{i}"))
y_ends.append(model.NewIntVar(0, max_height, f"y_end{i}"))
## Interval variables.
x_intervals.append(
model.NewIntervalVar(x_starts[i], item_widths[i] * is_used[i],
x_ends[i], f'x_interval{i}'))
model.NewIntervalVar(
x_starts[i], item_widths[i] * is_used[i], x_ends[i], f"x_interval{i}"
)
)
y_intervals.append(
model.NewIntervalVar(y_starts[i], item_heights[i] * is_used[i],
y_ends[i], f'y_interval{i}'))
model.NewIntervalVar(
y_starts[i], item_heights[i] * is_used[i], y_ends[i], f"y_interval{i}"
)
)
# Unused boxes are fixed at (0.0).
model.Add(x_starts[i] == 0).OnlyEnforceIf(is_used[i].Not())
@@ -136,8 +143,8 @@ def solve_with_duplicate_items(data, max_height, max_width):
# Output proto to file.
if _OUTPUT_PROTO.value:
print(f'Writing proto to {_OUTPUT_PROTO.value}')
with open(_OUTPUT_PROTO.value, 'w') as text_file:
print(f"Writing proto to {_OUTPUT_PROTO.value}")
with open(_OUTPUT_PROTO.value, "w") as text_file:
text_file.write(str(model))
# Solve model.
@@ -150,25 +157,27 @@ def solve_with_duplicate_items(data, max_height, max_width):
# Report solution.
if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:
used = {i for i in range(num_items) if solver.BooleanValue(is_used[i])}
data = pd.DataFrame({
'x_start': [solver.Value(x_starts[i]) for i in used],
'y_start': [solver.Value(y_starts[i]) for i in used],
'item_width': [item_widths[i] for i in used],
'item_height': [item_heights[i] for i in used],
'x_end': [solver.Value(x_ends[i]) for i in used],
'y_end': [solver.Value(y_ends[i]) for i in used],
'item_value': [item_values[i] for i in used]
})
data = pd.DataFrame(
{
"x_start": [solver.Value(x_starts[i]) for i in used],
"y_start": [solver.Value(y_starts[i]) for i in used],
"item_width": [item_widths[i] for i in used],
"item_height": [item_heights[i] for i in used],
"x_end": [solver.Value(x_ends[i]) for i in used],
"y_end": [solver.Value(y_ends[i]) for i in used],
"item_value": [item_values[i] for i in used],
}
)
print(data)
def solve_with_duplicate_optional_items(data, max_height, max_width):
"""Solve the problem by building 2 optional items (rotated or not) for each item."""
# Derived data (expanded to individual items).
data_widths = data['width'].to_numpy()
data_heights = data['height'].to_numpy()
data_availability = data['available'].to_numpy()
data_values = data['value'].to_numpy()
data_widths = data["width"].to_numpy()
data_heights = data["height"].to_numpy()
data_availability = data["available"].to_numpy()
data_values = data["value"].to_numpy()
# Non duplicated items data.
base_item_widths = np.repeat(data_widths, data_availability)
@@ -195,22 +204,27 @@ def solve_with_duplicate_optional_items(data, max_height, max_width):
for i in range(num_items):
## Is the item used?
is_used.append(model.NewBoolVar(f'is_used{i}'))
is_used.append(model.NewBoolVar(f"is_used{i}"))
## Item coordinates.
x_starts.append(
model.NewIntVar(0, max_width - int(item_widths[i]), f'x_start{i}'))
model.NewIntVar(0, max_width - int(item_widths[i]), f"x_start{i}")
)
y_starts.append(
model.NewIntVar(0, max_height - int(item_heights[i]),
f'y_start{i}'))
model.NewIntVar(0, max_height - int(item_heights[i]), f"y_start{i}")
)
## Interval variables.
x_intervals.append(
model.NewOptionalFixedSizeIntervalVar(x_starts[i], item_widths[i],
is_used[i], f'x_interval{i}'))
model.NewOptionalFixedSizeIntervalVar(
x_starts[i], item_widths[i], is_used[i], f"x_interval{i}"
)
)
y_intervals.append(
model.NewOptionalFixedSizeIntervalVar(y_starts[i], item_heights[i],
is_used[i], f'y_interval{i}'))
model.NewOptionalFixedSizeIntervalVar(
y_starts[i], item_heights[i], is_used[i], f"y_interval{i}"
)
)
# Unused boxes are fixed at (0.0).
model.Add(x_starts[i] == 0).OnlyEnforceIf(is_used[i].Not())
model.Add(y_starts[i] == 0).OnlyEnforceIf(is_used[i].Not())
@@ -229,8 +243,8 @@ def solve_with_duplicate_optional_items(data, max_height, max_width):
# Output proto to file.
if _OUTPUT_PROTO.value:
print(f'Writing proto to {_OUTPUT_PROTO.value}')
with open(_OUTPUT_PROTO.value, 'w') as text_file:
print(f"Writing proto to {_OUTPUT_PROTO.value}")
with open(_OUTPUT_PROTO.value, "w") as text_file:
text_file.write(str(model))
# Solve model.
@@ -243,27 +257,27 @@ def solve_with_duplicate_optional_items(data, max_height, max_width):
# Report solution.
if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:
used = {i for i in range(num_items) if solver.BooleanValue(is_used[i])}
data = pd.DataFrame({
'x_start': [solver.Value(x_starts[i]) for i in used],
'y_start': [solver.Value(y_starts[i]) for i in used],
'item_width': [item_widths[i] for i in used],
'item_height': [item_heights[i] for i in used],
'x_end': [solver.Value(x_starts[i]) + item_widths[i] for i in used],
'y_end': [
solver.Value(y_starts[i]) + item_heights[i] for i in used
],
'item_value': [item_values[i] for i in used]
})
data = pd.DataFrame(
{
"x_start": [solver.Value(x_starts[i]) for i in used],
"y_start": [solver.Value(y_starts[i]) for i in used],
"item_width": [item_widths[i] for i in used],
"item_height": [item_heights[i] for i in used],
"x_end": [solver.Value(x_starts[i]) + item_widths[i] for i in used],
"y_end": [solver.Value(y_starts[i]) + item_heights[i] for i in used],
"item_value": [item_values[i] for i in used],
}
)
print(data)
def solve_with_rotations(data, max_height, max_width):
"""Solve the problem by rotating items."""
# Derived data (expanded to individual items).
data_widths = data['width'].to_numpy()
data_heights = data['height'].to_numpy()
data_availability = data['available'].to_numpy()
data_values = data['value'].to_numpy()
data_widths = data["width"].to_numpy()
data_heights = data["height"].to_numpy()
data_availability = data["available"].to_numpy()
data_values = data["value"].to_numpy()
item_widths = np.repeat(data_widths, data_availability)
item_heights = np.repeat(data_heights, data_availability)
@@ -287,26 +301,26 @@ def solve_with_rotations(data, max_height, max_width):
for i in range(num_items):
sizes = [0, int(item_widths[i]), int(item_heights[i])]
# X coordinates.
x_starts.append(model.NewIntVar(0, max_width, f'x_start{i}'))
x_starts.append(model.NewIntVar(0, max_width, f"x_start{i}"))
x_sizes.append(
model.NewIntVarFromDomain(cp_model.Domain.FromValues(sizes),
f'x_size{i}'))
x_ends.append(model.NewIntVar(0, max_width, f'x_end{i}'))
model.NewIntVarFromDomain(cp_model.Domain.FromValues(sizes), f"x_size{i}")
)
x_ends.append(model.NewIntVar(0, max_width, f"x_end{i}"))
# Y coordinates.
y_starts.append(model.NewIntVar(0, max_height, f'y_start{i}'))
y_starts.append(model.NewIntVar(0, max_height, f"y_start{i}"))
y_sizes.append(
model.NewIntVarFromDomain(cp_model.Domain.FromValues(sizes),
f'y_size{i}'))
y_ends.append(model.NewIntVar(0, max_height, f'y_end{i}'))
model.NewIntVarFromDomain(cp_model.Domain.FromValues(sizes), f"y_size{i}")
)
y_ends.append(model.NewIntVar(0, max_height, f"y_end{i}"))
## Interval variables
x_intervals.append(
model.NewIntervalVar(x_starts[i], x_sizes[i], x_ends[i],
f'x_interval{i}'))
model.NewIntervalVar(x_starts[i], x_sizes[i], x_ends[i], f"x_interval{i}")
)
y_intervals.append(
model.NewIntervalVar(y_starts[i], y_sizes[i], y_ends[i],
f'y_interval{i}'))
model.NewIntervalVar(y_starts[i], y_sizes[i], y_ends[i], f"y_interval{i}")
)
# is_used[i] == True if and only if item i is selected.
is_used = []
@@ -315,9 +329,9 @@ def solve_with_rotations(data, max_height, max_width):
## for each item, decide is unselected, no_rotation, rotated.
for i in range(num_items):
not_selected = model.NewBoolVar(f'not_selected_{i}')
no_rotation = model.NewBoolVar(f'no_rotation_{i}')
rotated = model.NewBoolVar(f'rotated_{i}')
not_selected = model.NewBoolVar(f"not_selected_{i}")
no_rotation = model.NewBoolVar(f"no_rotation_{i}")
rotated = model.NewBoolVar(f"rotated_{i}")
### Exactly one state must be chosen.
model.AddExactlyOne(not_selected, no_rotation, rotated)
@@ -346,8 +360,8 @@ def solve_with_rotations(data, max_height, max_width):
# Output proto to file.
if _OUTPUT_PROTO.value:
print(f'Writing proto to {_OUTPUT_PROTO.value}')
with open(_OUTPUT_PROTO.value, 'w') as text_file:
print(f"Writing proto to {_OUTPUT_PROTO.value}")
with open(_OUTPUT_PROTO.value, "w") as text_file:
text_file.write(str(model))
# Solve model.
@@ -360,28 +374,30 @@ def solve_with_rotations(data, max_height, max_width):
# Report solution.
if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:
used = {i for i in range(num_items) if solver.BooleanValue(is_used[i])}
data = pd.DataFrame({
'x_start': [solver.Value(x_starts[i]) for i in used],
'y_start': [solver.Value(y_starts[i]) for i in used],
'item_width': [solver.Value(x_sizes[i]) for i in used],
'item_height': [solver.Value(y_sizes[i]) for i in used],
'x_end': [solver.Value(x_ends[i]) for i in used],
'y_end': [solver.Value(y_ends[i]) for i in used],
'item_value': [item_values[i] for i in used]
})
data = pd.DataFrame(
{
"x_start": [solver.Value(x_starts[i]) for i in used],
"y_start": [solver.Value(y_starts[i]) for i in used],
"item_width": [solver.Value(x_sizes[i]) for i in used],
"item_height": [solver.Value(y_sizes[i]) for i in used],
"x_end": [solver.Value(x_ends[i]) for i in used],
"y_end": [solver.Value(y_ends[i]) for i in used],
"item_value": [item_values[i] for i in used],
}
)
print(data)
def main(_):
"""Solve the problem with all models."""
data, max_height, max_width = build_data()
if _MODEL.value == 'duplicate':
if _MODEL.value == "duplicate":
solve_with_duplicate_items(data, max_height, max_width)
elif _MODEL.value == 'optional':
elif _MODEL.value == "optional":
solve_with_duplicate_optional_items(data, max_height, max_width)
else:
solve_with_rotations(data, max_height, max_width)
if __name__ == '__main__':
if __name__ == "__main__":
app.run(main)

112
examples/python/line_balancing_sat.py Executable file → Normal file
View File

@@ -11,6 +11,7 @@
# 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.
"""Reader and solver of the single assembly line balancing problem.
from https://assembly-line-balancing.de/salbp/:
@@ -36,12 +37,14 @@ from google.protobuf import text_format
from ortools.sat.python import cp_model
_INPUT = flags.DEFINE_string('input', '', 'Input file to parse and solve.')
_PARAMS = flags.DEFINE_string('params', '', 'Sat solver parameters.')
_INPUT = flags.DEFINE_string("input", "", "Input file to parse and solve.")
_PARAMS = flags.DEFINE_string("params", "", "Sat solver parameters.")
_OUTPUT_PROTO = flags.DEFINE_string(
'output_proto', '', 'Output file to write the cp_model proto to.')
_MODEL = flags.DEFINE_string('model', 'boolean',
'Model used: boolean, scheduling, greedy')
"output_proto", "", "Output file to write the cp_model proto to."
)
_MODEL = flags.DEFINE_string(
"model", "boolean", "Model used: boolean, scheduling, greedy"
)
class SectionInfo(object):
@@ -54,13 +57,13 @@ class SectionInfo(object):
def __str__(self):
if self.index_map:
return f'SectionInfo(index_map={self.index_map})'
return f"SectionInfo(index_map={self.index_map})"
elif self.set_of_pairs:
return f'SectionInfo(set_of_pairs={self.set_of_pairs})'
return f"SectionInfo(set_of_pairs={self.set_of_pairs})"
elif self.value is not None:
return f'SectionInfo(value={self.value})'
return f"SectionInfo(value={self.value})"
else:
return 'SectionInfo()'
return "SectionInfo()"
def read_model(filename):
@@ -69,64 +72,64 @@ def read_model(filename):
current_info = SectionInfo()
model = {}
with open(filename, 'r') as input_file:
print(f'Reading model from \'{filename}\'')
section_name = ''
with open(filename, "r") as input_file:
print(f"Reading model from '{filename}'")
section_name = ""
for line in input_file:
stripped_line = line.strip()
if not stripped_line:
continue
match_section_def = re.match(r'<([\w\s]+)>', stripped_line)
match_section_def = re.match(r"<([\w\s]+)>", stripped_line)
if match_section_def:
section_name = match_section_def.group(1)
if section_name == 'end':
if section_name == "end":
continue
current_info = SectionInfo()
model[section_name] = current_info
continue
match_single_number = re.match(r'^([0-9]+)$', stripped_line)
match_single_number = re.match(r"^([0-9]+)$", stripped_line)
if match_single_number:
current_info.value = int(match_single_number.group(1))
continue
match_key_value = re.match(r'^([0-9]+)\s+([0-9]+)$', stripped_line)
match_key_value = re.match(r"^([0-9]+)\s+([0-9]+)$", stripped_line)
if match_key_value:
key = int(match_key_value.group(1))
value = int(match_key_value.group(2))
current_info.index_map[key] = value
continue
match_pair = re.match(r'^([0-9]+),([0-9]+)$', stripped_line)
match_pair = re.match(r"^([0-9]+),([0-9]+)$", stripped_line)
if match_pair:
left = int(match_pair.group(1))
right = int(match_pair.group(2))
current_info.set_of_pairs.add((left, right))
continue
print(f'Unrecognized line \'{stripped_line}\'')
print(f"Unrecognized line '{stripped_line}'")
return model
def print_stats(model):
print('Model Statistics')
print("Model Statistics")
for key, value in model.items():
print(f' - {key}: {value}')
print(f" - {key}: {value}")
def solve_model_greedily(model):
"""Compute a greedy solution."""
print('Solving using a Greedy heuristics')
print("Solving using a Greedy heuristics")
num_tasks = model['number of tasks'].value
num_tasks = model["number of tasks"].value
all_tasks = range(1, num_tasks + 1) # Tasks are 1 based in the data.
precedences = model['precedence relations'].set_of_pairs
durations = model['task times'].index_map
cycle_time = model['cycle time'].value
precedences = model["precedence relations"].set_of_pairs
durations = model["task times"].index_map
cycle_time = model["cycle time"].value
weights = collections.defaultdict(int)
successors = collections.defaultdict(list)
@@ -145,7 +148,7 @@ def solve_model_greedily(model):
while len(assignment) < num_tasks:
if not candidates:
print('error empty')
print("error empty")
break
best = -1
@@ -175,7 +178,7 @@ def solve_model_greedily(model):
candidates.add(succ)
del weights[succ]
print(f' greedy solution uses {current_pod + 1} pods.')
print(f" greedy solution uses {current_pod + 1} pods.")
return assignment
@@ -183,13 +186,13 @@ def solve_model_greedily(model):
def solve_boolean_model(model, hint):
"""Solve the given model."""
print('Solving using the Boolean model')
print("Solving using the Boolean model")
# Model data
num_tasks = model['number of tasks'].value
num_tasks = model["number of tasks"].value
all_tasks = range(1, num_tasks + 1) # Tasks are 1 based in the model.
durations = model['task times'].index_map
precedences = model['precedence relations'].set_of_pairs
cycle_time = model['cycle time'].value
durations = model["task times"].index_map
precedences = model["precedence relations"].set_of_pairs
cycle_time = model["cycle time"].value
num_pods = max(p for _, p in hint.items()) + 1 if hint else num_tasks - 1
all_pods = range(num_pods)
@@ -204,11 +207,11 @@ def solve_boolean_model(model, hint):
# Create the variables
for t in all_tasks:
for p in all_pods:
assign[t, p] = model.NewBoolVar(f'assign_{t}_{p}')
possible[t, p] = model.NewBoolVar(f'possible_{t}_{p}')
assign[t, p] = model.NewBoolVar(f"assign_{t}_{p}")
possible[t, p] = model.NewBoolVar(f"possible_{t}_{p}")
# active[p] indicates if pod p is active.
active = [model.NewBoolVar(f'active_{p}') for p in all_pods]
active = [model.NewBoolVar(f"active_{p}") for p in all_pods]
# Each task is done on exactly one pod.
for t in all_tasks:
@@ -216,8 +219,7 @@ def solve_boolean_model(model, hint):
# Total tasks assigned to one pod cannot exceed cycle time.
for p in all_pods:
model.Add(
sum(assign[t, p] * durations[t] for t in all_tasks) <= cycle_time)
model.Add(sum(assign[t, p] * durations[t] for t in all_tasks) <= cycle_time)
# Maintain the possible variables:
# possible at pod p -> possible at any pod after p
@@ -235,8 +237,7 @@ def solve_boolean_model(model, hint):
# Precedences.
for before, after in precedences:
for p in range(1, num_pods):
model.AddImplication(assign[before, p], possible[after,
p - 1].Not())
model.AddImplication(assign[before, p], possible[after, p - 1].Not())
# Link active variables with the assign one.
for p in all_pods:
@@ -260,7 +261,7 @@ def solve_boolean_model(model, hint):
model.AddHint(assign[t, hint[t]], 1)
if _OUTPUT_PROTO.value:
print(f'Writing proto to {_OUTPUT_PROTO.value}')
print(f"Writing proto to {_OUTPUT_PROTO.value}")
model.ExportToFile(_OUTPUT_PROTO.value)
# Solve model.
@@ -274,13 +275,13 @@ def solve_boolean_model(model, hint):
def solve_scheduling_model(model, hint):
"""Solve the given model using a cumutive model."""
print('Solving using the scheduling model')
print("Solving using the scheduling model")
# Model data
num_tasks = model['number of tasks'].value
num_tasks = model["number of tasks"].value
all_tasks = range(1, num_tasks + 1) # Tasks are 1 based in the data.
durations = model['task times'].index_map
precedences = model['precedence relations'].set_of_pairs
cycle_time = model['cycle time'].value
durations = model["task times"].index_map
precedences = model["precedence relations"].set_of_pairs
cycle_time = model["cycle time"].value
num_pods = max(p for _, p in hint.items()) + 1 if hint else num_tasks
@@ -289,21 +290,20 @@ def solve_scheduling_model(model, hint):
# pod[t] indicates on which pod the task is performed.
pods = {}
for t in all_tasks:
pods[t] = model.NewIntVar(0, num_pods - 1, f'pod_{t}')
pods[t] = model.NewIntVar(0, num_pods - 1, f"pod_{t}")
# Create the variables
intervals = []
demands = []
for t in all_tasks:
interval = model.NewFixedSizeIntervalVar(pods[t], 1, '')
interval = model.NewFixedSizeIntervalVar(pods[t], 1, "")
intervals.append(interval)
demands.append(durations[t])
# Add terminating interval as the objective.
obj_var = model.NewIntVar(1, num_pods, 'obj_var')
obj_size = model.NewIntVar(1, num_pods, 'obj_duration')
obj_interval = model.NewIntervalVar(obj_var, obj_size, num_pods + 1,
'obj_interval')
obj_var = model.NewIntVar(1, num_pods, "obj_var")
obj_size = model.NewIntVar(1, num_pods, "obj_duration")
obj_interval = model.NewIntervalVar(obj_var, obj_size, num_pods + 1, "obj_interval")
intervals.append(obj_interval)
demands.append(cycle_time)
@@ -322,7 +322,7 @@ def solve_scheduling_model(model, hint):
model.AddHint(pods[t], hint[t])
if _OUTPUT_PROTO.value:
print(f'Writing proto to{_OUTPUT_PROTO.value}')
print(f"Writing proto to{_OUTPUT_PROTO.value}")
model.ExportToFile(_OUTPUT_PROTO.value)
# Solve model.
@@ -335,17 +335,17 @@ def solve_scheduling_model(model, hint):
def main(argv: Sequence[str]) -> None:
if len(argv) > 1:
raise app.UsageError('Too many command-line arguments.')
raise app.UsageError("Too many command-line arguments.")
model = read_model(_INPUT.value)
print_stats(model)
greedy_solution = solve_model_greedily(model)
if _MODEL.value == 'boolean':
if _MODEL.value == "boolean":
solve_boolean_model(model, greedy_solution)
elif _MODEL.value == 'scheduling':
elif _MODEL.value == "scheduling":
solve_scheduling_model(model, greedy_solution)
if __name__ == '__main__':
if __name__ == "__main__":
app.run(main)

View File

@@ -11,6 +11,7 @@
# 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.
"""Test linear sum assignment on a 4x4 matrix.
Example taken from:
@@ -27,8 +28,7 @@ def run_assignment_on_4x4_matrix():
"""Test linear sum assignment on a 4x4 matrix."""
num_sources = 4
num_targets = 4
cost = [[90, 76, 75, 80], [35, 85, 55, 65], [125, 95, 90, 105],
[45, 110, 95, 115]]
cost = [[90, 76, 75, 80], [35, 85, 55, 65], [125, 95, 90, 105], [45, 110, 95, 115]]
expected_cost = cost[0][3] + cost[1][2] + cost[2][1] + cost[3][0]
assignment = linear_sum_assignment.SimpleLinearSumAssignment()
@@ -38,23 +38,24 @@ def run_assignment_on_4x4_matrix():
solve_status = assignment.solve()
if solve_status == assignment.OPTIMAL:
print('Successful solve.')
print('Total cost', assignment.optimal_cost(), '/', expected_cost)
print("Successful solve.")
print("Total cost", assignment.optimal_cost(), "/", expected_cost)
for i in range(0, assignment.num_nodes()):
print('Left node %d assigned to right node %d with cost %d.' %
(i, assignment.right_mate(i), assignment.assignment_cost(i)))
print(
"Left node %d assigned to right node %d with cost %d."
% (i, assignment.right_mate(i), assignment.assignment_cost(i))
)
elif solve_status == assignment.INFEASIBLE:
print('No perfect matching exists.')
print("No perfect matching exists.")
elif solve_status == assignment.POSSIBLE_OVERFLOW:
print(
'Some input costs are too large and may cause an integer overflow.')
print("Some input costs are too large and may cause an integer overflow.")
def main(argv: Sequence[str]) -> None:
if len(argv) > 1:
raise app.UsageError('Too many command-line arguments.')
raise app.UsageError("Too many command-line arguments.")
run_assignment_on_4x4_matrix()
if __name__ == '__main__':
if __name__ == "__main__":
app.run(main)

View File

@@ -11,14 +11,16 @@
# 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.
"""Linear programming examples that show how to use the APIs."""
from ortools.linear_solver import pywraplp
def Announce(solver, api_type):
print('---- Linear programming example with ' + solver + ' (' + api_type +
') -----')
print(
"---- Linear programming example with " + solver + " (" + api_type + ") -----"
)
def RunLinearExampleNaturalLanguageAPI(optimization_problem_type):
@@ -28,23 +30,25 @@ def RunLinearExampleNaturalLanguageAPI(optimization_problem_type):
if not solver:
return
Announce(optimization_problem_type, 'natural language API')
Announce(optimization_problem_type, "natural language API")
infinity = solver.infinity()
# x1, x2 and x3 are continuous non-negative variables.
x1 = solver.NumVar(0.0, infinity, 'x1')
x2 = solver.NumVar(0.0, infinity, 'x2')
x3 = solver.NumVar(0.0, infinity, 'x3')
x1 = solver.NumVar(0.0, infinity, "x1")
x2 = solver.NumVar(0.0, infinity, "x2")
x3 = solver.NumVar(0.0, infinity, "x3")
solver.Maximize(10 * x1 + 6 * x2 + 4 * x3)
c0 = solver.Add(10 * x1 + 4 * x2 + 5 * x3 <= 600, 'ConstraintName0')
c0 = solver.Add(10 * x1 + 4 * x2 + 5 * x3 <= 600, "ConstraintName0")
c1 = solver.Add(2 * x1 + 2 * x2 + 6 * x3 <= 300)
sum_of_vars = sum([x1, x2, x3])
c2 = solver.Add(sum_of_vars <= 100.0, 'OtherConstraintName')
c2 = solver.Add(sum_of_vars <= 100.0, "OtherConstraintName")
SolveAndPrint(solver, [x1, x2, x3], [c0, c1, c2], optimization_problem_type != 'PDLP')
SolveAndPrint(
solver, [x1, x2, x3], [c0, c1, c2], optimization_problem_type != "PDLP"
)
# Print a linear expression's solution value.
print('Sum of vars: %s = %s' % (sum_of_vars, sum_of_vars.solution_value()))
print("Sum of vars: %s = %s" % (sum_of_vars, sum_of_vars.solution_value()))
def RunLinearExampleCppStyleAPI(optimization_problem_type):
@@ -53,13 +57,13 @@ def RunLinearExampleCppStyleAPI(optimization_problem_type):
if not solver:
return
Announce(optimization_problem_type, 'C++ style API')
Announce(optimization_problem_type, "C++ style API")
infinity = solver.infinity()
# x1, x2 and x3 are continuous non-negative variables.
x1 = solver.NumVar(0.0, infinity, 'x1')
x2 = solver.NumVar(0.0, infinity, 'x2')
x3 = solver.NumVar(0.0, infinity, 'x3')
x1 = solver.NumVar(0.0, infinity, "x1")
x2 = solver.NumVar(0.0, infinity, "x2")
x3 = solver.NumVar(0.0, infinity, "x3")
# Maximize 10 * x1 + 6 * x2 + 4 * x3.
objective = solver.Objective()
@@ -69,31 +73,32 @@ def RunLinearExampleCppStyleAPI(optimization_problem_type):
objective.SetMaximization()
# x1 + x2 + x3 <= 100.
c0 = solver.Constraint(-infinity, 100.0, 'c0')
c0 = solver.Constraint(-infinity, 100.0, "c0")
c0.SetCoefficient(x1, 1)
c0.SetCoefficient(x2, 1)
c0.SetCoefficient(x3, 1)
# 10 * x1 + 4 * x2 + 5 * x3 <= 600.
c1 = solver.Constraint(-infinity, 600.0, 'c1')
c1 = solver.Constraint(-infinity, 600.0, "c1")
c1.SetCoefficient(x1, 10)
c1.SetCoefficient(x2, 4)
c1.SetCoefficient(x3, 5)
# 2 * x1 + 2 * x2 + 6 * x3 <= 300.
c2 = solver.Constraint(-infinity, 300.0, 'c2')
c2 = solver.Constraint(-infinity, 300.0, "c2")
c2.SetCoefficient(x1, 2)
c2.SetCoefficient(x2, 2)
c2.SetCoefficient(x3, 6)
SolveAndPrint(solver, [x1, x2, x3], [c0, c1, c2],
optimization_problem_type != 'PDLP')
SolveAndPrint(
solver, [x1, x2, x3], [c0, c1, c2], optimization_problem_type != "PDLP"
)
def SolveAndPrint(solver, variable_list, constraint_list, is_precise):
"""Solve the problem and print the solution."""
print('Number of variables = %d' % solver.NumVariables())
print('Number of constraints = %d' % solver.NumConstraints())
print("Number of variables = %d" % solver.NumVariables())
print("Number of constraints = %d" % solver.NumConstraints())
result_status = solver.Solve()
@@ -105,38 +110,40 @@ def SolveAndPrint(solver, variable_list, constraint_list, is_precise):
if is_precise:
assert solver.VerifySolution(1e-7, True)
print('Problem solved in %f milliseconds' % solver.wall_time())
print("Problem solved in %f milliseconds" % solver.wall_time())
# The objective value of the solution.
print('Optimal objective value = %f' % solver.Objective().Value())
print("Optimal objective value = %f" % solver.Objective().Value())
# The value of each variable in the solution.
for variable in variable_list:
print('%s = %f' % (variable.name(), variable.solution_value()))
print("%s = %f" % (variable.name(), variable.solution_value()))
print('Advanced usage:')
print('Problem solved in %d iterations' % solver.iterations())
print("Advanced usage:")
print("Problem solved in %d iterations" % solver.iterations())
for variable in variable_list:
print('%s: reduced cost = %f' %
(variable.name(), variable.reduced_cost()))
print("%s: reduced cost = %f" % (variable.name(), variable.reduced_cost()))
activities = solver.ComputeConstraintActivities()
for i, constraint in enumerate(constraint_list):
print(('constraint %d: dual value = %f\n'
' activity = %f' %
(i, constraint.dual_value(), activities[constraint.index()])))
print(
(
"constraint %d: dual value = %f\n activity = %f"
% (i, constraint.dual_value(), activities[constraint.index()])
)
)
def main():
RunLinearExampleNaturalLanguageAPI('GLOP')
RunLinearExampleNaturalLanguageAPI('GLPK_LP')
RunLinearExampleNaturalLanguageAPI('CLP')
RunLinearExampleNaturalLanguageAPI('PDLP')
RunLinearExampleNaturalLanguageAPI("GLOP")
RunLinearExampleNaturalLanguageAPI("GLPK_LP")
RunLinearExampleNaturalLanguageAPI("CLP")
RunLinearExampleNaturalLanguageAPI("PDLP")
RunLinearExampleCppStyleAPI('GLOP')
RunLinearExampleCppStyleAPI('GLPK_LP')
RunLinearExampleCppStyleAPI('CLP')
RunLinearExampleCppStyleAPI('PDLP')
RunLinearExampleCppStyleAPI("GLOP")
RunLinearExampleCppStyleAPI("GLPK_LP")
RunLinearExampleCppStyleAPI("CLP")
RunLinearExampleCppStyleAPI("PDLP")
if __name__ == '__main__':
if __name__ == "__main__":
main()

View File

@@ -11,6 +11,7 @@
# 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.
"""Magic sequence problem.
This models aims at building a sequence of numbers such that the number of
@@ -30,12 +31,12 @@ FLAGS = flags.FLAGS
def main(argv):
# Create the solver.
solver = pywrapcp.Solver('magic sequence')
solver = pywrapcp.Solver("magic sequence")
# Create an array of IntVars to hold the answers.
size = int(argv[1]) if len(argv) > 1 else 100
all_values = list(range(0, size))
all_vars = [solver.IntVar(0, size, 'vars_%d' % i) for i in all_values]
all_vars = [solver.IntVar(0, size, "vars_%d" % i) for i in all_values]
# The number of variables equal to j shall be the value of all_vars[j].
solver.Add(solver.Distribute(all_vars, all_values, all_vars))
@@ -45,12 +46,12 @@ def main(argv):
solver.Add(solver.Sum(all_vars) == size)
solver.NewSearch(
solver.Phase(all_vars, solver.CHOOSE_FIRST_UNBOUND,
solver.ASSIGN_MIN_VALUE))
solver.Phase(all_vars, solver.CHOOSE_FIRST_UNBOUND, solver.ASSIGN_MIN_VALUE)
)
solver.NextSolution()
print(all_vars)
solver.EndSearch()
if __name__ == '__main__':
if __name__ == "__main__":
app.run(main)

View File

@@ -11,6 +11,7 @@
# 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.
"""Excape the maze while collecting treasures in order.
The path must begin at the 'start' position, finish at the 'end' position,
@@ -27,23 +28,29 @@ from google.protobuf import text_format
from ortools.sat.python import cp_model
_OUTPUT_PROTO = flags.DEFINE_string(
'output_proto', '', 'Output file to write the cp_model proto to.')
_PARAMS = flags.DEFINE_string('params',
'num_search_workers:8,log_search_progress:true',
'Sat solver parameters.')
"output_proto", "", "Output file to write the cp_model proto to."
)
_PARAMS = flags.DEFINE_string(
"params", "num_search_workers:8,log_search_progress:true", "Sat solver parameters."
)
def add_neighbor(size, x, y, z, dx, dy, dz, model, index_map, position_to_rank,
arcs):
def add_neighbor(size, x, y, z, dx, dy, dz, model, index_map, position_to_rank, arcs):
"""Checks if the neighbor is valid, and adds it to the model."""
if (x + dx < 0 or x + dx >= size or y + dy < 0 or y + dy >= size or
z + dz < 0 or z + dz >= size):
if (
x + dx < 0
or x + dx >= size
or y + dy < 0
or y + dy >= size
or z + dz < 0
or z + dz >= size
):
return
before_index = index_map[(x, y, z)]
before_rank = position_to_rank[(x, y, z)]
after_index = index_map[(x + dx, y + dy, z + dz)]
after_rank = position_to_rank[(x + dx, y + dy, z + dz)]
move_literal = model.NewBoolVar('')
move_literal = model.NewBoolVar("")
model.Add(after_rank == before_rank + 1).OnlyEnforceIf(move_literal)
arcs.append((before_index, after_index, move_literal))
@@ -72,8 +79,7 @@ def escape_the_maze(params, output_proto):
position_to_rank = {}
for coord in reverse_map:
position_to_rank[coord] = model.NewIntVar(0, counter - 1,
f'rank_{coord}')
position_to_rank[coord] = model.NewIntVar(0, counter - 1, f"rank_{coord}")
# Path constraints.
model.Add(position_to_rank[start] == 0)
@@ -87,18 +93,24 @@ def escape_the_maze(params, output_proto):
for x in range(size):
for y in range(size):
for z in range(size):
add_neighbor(size, x, y, z, -1, 0, 0, model, index_map,
position_to_rank, arcs)
add_neighbor(size, x, y, z, 1, 0, 0, model, index_map,
position_to_rank, arcs)
add_neighbor(size, x, y, z, 0, -1, 0, model, index_map,
position_to_rank, arcs)
add_neighbor(size, x, y, z, 0, 1, 0, model, index_map,
position_to_rank, arcs)
add_neighbor(size, x, y, z, 0, 0, -1, model, index_map,
position_to_rank, arcs)
add_neighbor(size, x, y, z, 0, 0, 1, model, index_map,
position_to_rank, arcs)
add_neighbor(
size, x, y, z, -1, 0, 0, model, index_map, position_to_rank, arcs
)
add_neighbor(
size, x, y, z, 1, 0, 0, model, index_map, position_to_rank, arcs
)
add_neighbor(
size, x, y, z, 0, -1, 0, model, index_map, position_to_rank, arcs
)
add_neighbor(
size, x, y, z, 0, 1, 0, model, index_map, position_to_rank, arcs
)
add_neighbor(
size, x, y, z, 0, 0, -1, model, index_map, position_to_rank, arcs
)
add_neighbor(
size, x, y, z, 0, 0, 1, model, index_map, position_to_rank, arcs
)
# Closes the loop as the constraint expects a circuit, not a path.
arcs.append((index_map[end], index_map[start], True))
@@ -119,30 +131,30 @@ def escape_the_maze(params, output_proto):
# Prints solution.
if result == cp_model.OPTIMAL:
path = [''] * counter
path = [""] * counter
for x in range(size):
for y in range(size):
for z in range(size):
position = (x, y, z)
rank = solver.Value(position_to_rank[position])
msg = f'({x}, {y}, {z})'
msg = f"({x}, {y}, {z})"
if position == start:
msg += ' [start]'
msg += " [start]"
elif position == end:
msg += ' [end]'
msg += " [end]"
else:
for b in range(len(boxes)):
if position == boxes[b]:
msg += f' [boxes {b}]'
msg += f" [boxes {b}]"
path[rank] = msg
print(path)
def main(argv: Sequence[str]) -> None:
if len(argv) > 1:
raise app.UsageError('Too many command-line arguments.')
raise app.UsageError("Too many command-line arguments.")
escape_the_maze(_PARAMS.value, _OUTPUT_PROTO.value)
if __name__ == '__main__':
if __name__ == "__main__":
app.run(main)

View File

@@ -11,6 +11,7 @@
# 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.
"""Scheduling cooking tasks in a bakery using no-wait jobshop scheduling.
We are scheduling a full day of baking:
@@ -27,34 +28,35 @@ from absl import flags
from google.protobuf import text_format
from ortools.sat.python import cp_model
_PARAMS = flags.DEFINE_string('params',
'num_search_workers:16, max_time_in_seconds:30',
'Sat solver parameters.')
_PARAMS = flags.DEFINE_string(
"params", "num_search_workers:16, max_time_in_seconds:30", "Sat solver parameters."
)
_PROTO_FILE = flags.DEFINE_string(
'proto_file', '', 'If not empty, output the proto to this file.')
"proto_file", "", "If not empty, output the proto to this file."
)
# Recipes
CROISSANT = 'croissant'
APPLE_PIE = 'apple pie'
BRIOCHE = 'brioche'
CHOCOLATE_CAKE = 'chocolate cake'
CROISSANT = "croissant"
APPLE_PIE = "apple pie"
BRIOCHE = "brioche"
CHOCOLATE_CAKE = "chocolate cake"
# Skills
BAKING = 'baking'
PROOFING = 'proofing'
COOKING = 'cooking'
COOLING = 'cooling'
DECORATING = 'decorating'
DISPLAY = 'display'
BAKING = "baking"
PROOFING = "proofing"
COOKING = "cooking"
COOLING = "cooling"
DECORATING = "decorating"
DISPLAY = "display"
class Task(object):
"""A unit baking task.
- Simple baking tasks have a fixed duration. They are performed by workers.
- Waiting/cooling/proofing tasks have a min and a max duration.
They are performed by machine or they use space resources.
"""
- Simple baking tasks have a fixed duration. They are performed by workers.
- Waiting/cooling/proofing tasks have a min and a max duration.
They are performed by machine or they use space resources.
"""
def __init__(self, name, min_duration, max_duration):
self.name = name
@@ -86,13 +88,13 @@ class Recipe(object):
class Resource(object):
"""A resource is a worker, a machine, or just some space for cakes to rest.
- Workers have a capacity of 1 and can have variable efficiency.
- Machines and spaces have a capacity greater or equal to one, but the
efficiency is fixed to 100.
- Workers have a capacity of 1 and can have variable efficiency.
- Machines and spaces have a capacity greater or equal to one, but the
efficiency is fixed to 100.
For a worker with efficiency k and a task of duration t, the resulting
work will have a duration `ceil(t * k)`.
"""
For a worker with efficiency k and a task of duration t, the resulting
work will have a duration `ceil(t * k)`.
"""
def __init__(self, name, capacity):
self.name = name
@@ -110,12 +112,12 @@ class Order(object):
def __init__(self, unique_id, recipe_name, due_date, quantity):
"""Builds an order.
Args:
unique_id: A unique identifier for the order. Used to display the result.
recipe_name: The name of the recipe. It must match one of the recipes.
due_date: The due date in minutes since midnight.
quantity: How many cakes to prepare.
"""
Args:
unique_id: A unique identifier for the order. Used to display the result.
recipe_name: The name of the recipe. It must match one of the recipes.
due_date: The due date in minutes since midnight.
quantity: How many cakes to prepare.
"""
self.unique_id = unique_id
self.recipe_name = recipe_name
self.due_date = due_date
@@ -151,32 +153,41 @@ def set_up_data():
chocolate_cake_recipe.add_task(DECORATING, 15, 15)
chocolate_cake_recipe.add_task(DISPLAY, 5, 5 * 60)
recipes = [
croissant_recipe, apple_pie_recipe, brioche_recipe,
chocolate_cake_recipe
croissant_recipe,
apple_pie_recipe,
brioche_recipe,
chocolate_cake_recipe,
]
# Resources.
baker1 = Resource('baker1', 1).add_skill(BAKING, 1.0)
baker2 = Resource('baker2', 1).add_skill(BAKING, 1.0)
decorator1 = Resource('decorator1', 1).add_skill(DECORATING, 1.0)
waiting_space = Resource('waiting_space', 4).add_skill(PROOFING, 1.0)
oven = Resource('oven', 4).add_skill(COOKING, 1.0)
display_space = Resource('display_space', 12).add_skill(DISPLAY, 1.0)
baker1 = Resource("baker1", 1).add_skill(BAKING, 1.0)
baker2 = Resource("baker2", 1).add_skill(BAKING, 1.0)
decorator1 = Resource("decorator1", 1).add_skill(DECORATING, 1.0)
waiting_space = Resource("waiting_space", 4).add_skill(PROOFING, 1.0)
oven = Resource("oven", 4).add_skill(COOKING, 1.0)
display_space = Resource("display_space", 12).add_skill(DISPLAY, 1.0)
resources = [baker1, baker2, decorator1, waiting_space, oven, display_space]
# Orders
croissant_7am = Order('croissant_7am', CROISSANT, 7 * 60, 3)
croissant_8am = Order('croissant_8am', CROISSANT, 8 * 60, 3)
croissant_9am = Order('croissant_9am', CROISSANT, 9 * 60, 2)
croissant_10am = Order('croissant_10am', CROISSANT, 10 * 60, 1)
croissant_11am = Order('croissant_11am', CROISSANT, 11 * 60, 1)
brioche_10am = Order('brioche_10am', BRIOCHE, 10 * 60, 8)
brioche_12pm = Order('brioche_12pm', BRIOCHE, 12 * 60, 8)
apple_pie_1pm = Order('apple_pie_1pm', APPLE_PIE, 13 * 60, 10)
chocolate_4pm = Order('chocolate_4pm', CHOCOLATE_CAKE, 16 * 60, 10)
croissant_7am = Order("croissant_7am", CROISSANT, 7 * 60, 3)
croissant_8am = Order("croissant_8am", CROISSANT, 8 * 60, 3)
croissant_9am = Order("croissant_9am", CROISSANT, 9 * 60, 2)
croissant_10am = Order("croissant_10am", CROISSANT, 10 * 60, 1)
croissant_11am = Order("croissant_11am", CROISSANT, 11 * 60, 1)
brioche_10am = Order("brioche_10am", BRIOCHE, 10 * 60, 8)
brioche_12pm = Order("brioche_12pm", BRIOCHE, 12 * 60, 8)
apple_pie_1pm = Order("apple_pie_1pm", APPLE_PIE, 13 * 60, 10)
chocolate_4pm = Order("chocolate_4pm", CHOCOLATE_CAKE, 16 * 60, 10)
orders = [
croissant_7am, croissant_8am, croissant_9am, croissant_10am,
croissant_11am, brioche_10am, brioche_12pm, apple_pie_1pm, chocolate_4pm
croissant_7am,
croissant_8am,
croissant_9am,
croissant_10am,
croissant_11am,
brioche_10am,
brioche_12pm,
apple_pie_1pm,
chocolate_4pm,
]
return recipes, resources, orders
@@ -210,50 +221,48 @@ def solve_with_cp_sat(recipes, resources, orders):
tardiness_vars = []
for order in orders:
for batch in range(order.quantity):
order_id = f'{order.unique_id}_{batch}'
order_id = f"{order.unique_id}_{batch}"
sorted_orders.append(order_id)
previous_end = None
due_date = order.due_date
recipe = recipe_by_name[order.recipe_name]
for task in recipe.tasks:
skill_name = task.name
suffix = f'_{order.unique_id}_batch{batch}_{skill_name}'
suffix = f"_{order.unique_id}_batch{batch}_{skill_name}"
start = None
if previous_end is None:
start = model.NewIntVar(start_work, horizon,
f'start{suffix}')
start = model.NewIntVar(start_work, horizon, f"start{suffix}")
orders_sequence_of_events[order_id].append(
(start, f'start{suffix}'))
(start, f"start{suffix}")
)
else:
start = previous_end
size = model.NewIntVar(task.min_duration, task.max_duration,
f'size{suffix}')
size = model.NewIntVar(
task.min_duration, task.max_duration, f"size{suffix}"
)
end = None
if task == recipe.tasks[-1]:
# The order must end after the due_date. Ideally, exactly at the
# due_date.
tardiness = model.NewIntVar(0, horizon - due_date,
f'end{suffix}')
tardiness = model.NewIntVar(0, horizon - due_date, f"end{suffix}")
end = tardiness + due_date
# Store the end_var for the objective.
tardiness_vars.append(tardiness)
else:
end = model.NewIntVar(start_work, horizon, f'end{suffix}')
orders_sequence_of_events[order_id].append(
(end, f'end{suffix}'))
end = model.NewIntVar(start_work, horizon, f"end{suffix}")
orders_sequence_of_events[order_id].append((end, f"end{suffix}"))
previous_end = end
# Per resource copy.
presence_literals = []
for resource in resource_list_by_skill_name[skill_name]:
presence = model.NewBoolVar(
f'presence{suffix}_{resource.name}')
presence = model.NewBoolVar(f"presence{suffix}_{resource.name}")
copy = model.NewOptionalIntervalVar(
start, size, end, presence,
f'interval{suffix}_{resource.name}')
start, size, end, presence, f"interval{suffix}_{resource.name}"
)
interval_list_by_resource_name[resource.name].append(copy)
presence_literals.append(presence)
@@ -266,8 +275,7 @@ def solve_with_cp_sat(recipes, resources, orders):
if resource.capacity == 1:
model.AddNoOverlap(intervals)
else:
model.AddCumulative(intervals, [1] * len(intervals),
resource.capacity)
model.AddCumulative(intervals, [1] * len(intervals), resource.capacity)
# The objective is to minimize the sum of the tardiness values of each jobs.
# The tardiness is difference between the end time of an order and its
@@ -283,19 +291,19 @@ def solve_with_cp_sat(recipes, resources, orders):
if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:
for order_id in sorted_orders:
print(f'{order_id}:')
print(f"{order_id}:")
for time_expr, event_id in orders_sequence_of_events[order_id]:
time = solver.Value(time_expr)
print(f' {event_id} at {time // 60}:{time % 60:02}')
print(f" {event_id} at {time // 60}:{time % 60:02}")
def main(argv: Sequence[str]) -> None:
if len(argv) > 1:
raise app.UsageError('Too many command-line arguments.')
raise app.UsageError("Too many command-line arguments.")
recipes, resources, orders = set_up_data()
solve_with_cp_sat(recipes, resources, orders)
if __name__ == '__main__':
if __name__ == "__main__":
app.run(main)

View File

@@ -11,6 +11,7 @@
# 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.
"""CP/SAT model for the N-queens problem."""
import time
@@ -19,7 +20,7 @@ from absl import app
from absl import flags
from ortools.sat.python import cp_model
_SIZE = flags.DEFINE_integer('size', 8, 'Number of queens.')
_SIZE = flags.DEFINE_integer("size", 8, "Number of queens.")
class NQueenSolutionPrinter(cp_model.CpSolverSolutionCallback):
@@ -36,8 +37,10 @@ class NQueenSolutionPrinter(cp_model.CpSolverSolutionCallback):
def on_solution_callback(self):
current_time = time.time()
print('Solution %i, time = %f s' %
(self.__solution_count, current_time - self.__start_time))
print(
"Solution %i, time = %f s"
% (self.__solution_count, current_time - self.__start_time)
)
self.__solution_count += 1
all_queens = range(len(self.__queens))
@@ -45,9 +48,9 @@ class NQueenSolutionPrinter(cp_model.CpSolverSolutionCallback):
for j in all_queens:
if self.Value(self.__queens[j]) == i:
# There is a queen in column j, row i.
print('Q', end=' ')
print("Q", end=" ")
else:
print('_', end=' ')
print("_", end=" ")
print()
print()
@@ -60,9 +63,7 @@ def main(_):
### Creates the variables.
# The array index is the column, and the value is the row.
queens = [
model.NewIntVar(0, board_size - 1, 'x%i' % i) for i in range(board_size)
]
queens = [model.NewIntVar(0, board_size - 1, "x%i" % i) for i in range(board_size)]
### Creates the constraints.
@@ -74,8 +75,8 @@ def main(_):
diag1 = []
diag2 = []
for i in range(board_size):
q1 = model.NewIntVar(0, 2 * board_size, 'diag1_%i' % i)
q2 = model.NewIntVar(-board_size, board_size, 'diag2_%i' % i)
q1 = model.NewIntVar(0, 2 * board_size, "diag1_%i" % i)
q2 = model.NewIntVar(-board_size, board_size, "diag2_%i" % i)
diag1.append(q1)
diag2.append(q2)
model.Add(q1 == queens[i] + i)
@@ -92,12 +93,12 @@ def main(_):
solver.Solve(model, solution_printer)
print()
print('Statistics')
print(' - conflicts : %i' % solver.NumConflicts())
print(' - branches : %i' % solver.NumBranches())
print(' - wall time : %f s' % solver.WallTime())
print(' - solutions found : %i' % solution_printer.SolutionCount())
print("Statistics")
print(" - conflicts : %i" % solver.NumConflicts())
print(" - branches : %i" % solver.NumBranches())
print(" - wall time : %f s" % solver.WallTime())
print(" - solutions found : %i" % solution_printer.SolutionCount())
if __name__ == '__main__':
if __name__ == "__main__":
app.run(main)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -11,6 +11,7 @@
# 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.
"""MaxFlow and MinCostFlow examples."""
from typing import Sequence
@@ -21,7 +22,7 @@ from ortools.graph.python import min_cost_flow
def max_flow_api():
"""MaxFlow simple interface example."""
print('MaxFlow on a simple network.')
print("MaxFlow on a simple network.")
tails = [0, 0, 0, 0, 1, 2, 3, 3, 4]
heads = [1, 2, 3, 4, 3, 4, 4, 5, 5]
capacities = [5, 8, 5, 3, 4, 5, 6, 6, 4]
@@ -30,55 +31,57 @@ def max_flow_api():
for i in range(0, len(tails)):
smf.add_arc_with_capacity(tails[i], heads[i], capacities[i])
if smf.solve(0, 5) == smf.OPTIMAL:
print('Total flow', smf.optimal_flow(), '/', expected_total_flow)
print("Total flow", smf.optimal_flow(), "/", expected_total_flow)
for i in range(smf.num_arcs()):
print('From source %d to target %d: %d / %d' %
(smf.tail(i), smf.head(i), smf.flow(i), smf.capacity(i)))
print('Source side min-cut:', smf.get_source_side_min_cut())
print('Sink side min-cut:', smf.get_sink_side_min_cut())
print(
"From source %d to target %d: %d / %d"
% (smf.tail(i), smf.head(i), smf.flow(i), smf.capacity(i))
)
print("Source side min-cut:", smf.get_source_side_min_cut())
print("Sink side min-cut:", smf.get_sink_side_min_cut())
else:
print('There was an issue with the max flow input.')
print("There was an issue with the max flow input.")
def min_cost_flow_api():
"""MinCostFlow simple interface example.
Note that this example is actually a linear sum assignment example and will
be more efficiently solved with the pywrapgraph.LinearSumAssignment class.
"""
print('MinCostFlow on 4x4 matrix.')
Note that this example is actually a linear sum assignment example and will
be more efficiently solved with the pywrapgraph.LinearSumAssignment class.
"""
print("MinCostFlow on 4x4 matrix.")
num_sources = 4
num_targets = 4
costs = [[90, 75, 75, 80], [35, 85, 55, 65], [125, 95, 90, 105],
[45, 110, 95, 115]]
costs = [[90, 75, 75, 80], [35, 85, 55, 65], [125, 95, 90, 105], [45, 110, 95, 115]]
expected_cost = 275
smcf = min_cost_flow.SimpleMinCostFlow()
for source in range(0, num_sources):
for target in range(0, num_targets):
smcf.add_arc_with_capacity_and_unit_cost(source,
num_sources + target, 1,
costs[source][target])
smcf.add_arc_with_capacity_and_unit_cost(
source, num_sources + target, 1, costs[source][target]
)
for node in range(0, num_sources):
smcf.set_node_supply(node, 1)
smcf.set_node_supply(num_sources + node, -1)
status = smcf.solve()
if status == smcf.OPTIMAL:
print('Total flow', smcf.optimal_cost(), '/', expected_cost)
print("Total flow", smcf.optimal_cost(), "/", expected_cost)
for i in range(0, smcf.num_arcs()):
if smcf.flow(i) > 0:
print('From source %d to target %d: cost %d' %
(smcf.tail(i), smcf.head(i) - num_sources,
smcf.unit_cost(i)))
print(
"From source %d to target %d: cost %d"
% (smcf.tail(i), smcf.head(i) - num_sources, smcf.unit_cost(i))
)
else:
print('There was an issue with the min cost flow input.')
print("There was an issue with the min cost flow input.")
def main(argv: Sequence[str]) -> None:
if len(argv) > 1:
raise app.UsageError('Too many command-line arguments.')
raise app.UsageError("Too many command-line arguments.")
max_flow_api()
min_cost_flow_api()
if __name__ == '__main__':
if __name__ == "__main__":
app.run(main)

File diff suppressed because it is too large Load Diff

244
examples/python/shift_scheduling_sat.py Executable file → Normal file
View File

@@ -11,6 +11,7 @@
# 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.
"""Creates a shift scheduling problem and solves it."""
from absl import app
@@ -20,28 +21,30 @@ from ortools.sat.python import cp_model
from google.protobuf import text_format
_OUTPUT_PROTO = flags.DEFINE_string(
'output_proto', '', 'Output file to write the cp_model proto to.')
_PARAMS = flags.DEFINE_string('params', 'max_time_in_seconds:10.0',
'Sat solver parameters.')
"output_proto", "", "Output file to write the cp_model proto to."
)
_PARAMS = flags.DEFINE_string(
"params", "max_time_in_seconds:10.0", "Sat solver parameters."
)
def negated_bounded_span(works, start, length):
"""Filters an isolated sub-sequence of variables assined to True.
Extract the span of Boolean variables [start, start + length), negate them,
and if there is variables to the left/right of this span, surround the span by
them in non negated form.
Extract the span of Boolean variables [start, start + length), negate them,
and if there is variables to the left/right of this span, surround the span by
them in non negated form.
Args:
works: a list of variables to extract the span from.
start: the start to the span.
length: the length of the span.
Args:
works: a list of variables to extract the span from.
start: the start to the span.
length: the length of the span.
Returns:
a list of variables which conjunction will be false if the sub-list is
assigned to True, and correctly bounded by variables assigned to False,
or by the start or end of works.
"""
Returns:
a list of variables which conjunction will be false if the sub-list is
assigned to True, and correctly bounded by variables assigned to False,
or by the start or end of works.
"""
sequence = []
# Left border (start of works, or works[start - 1])
if start > 0:
@@ -54,35 +57,36 @@ def negated_bounded_span(works, start, length):
return sequence
def add_soft_sequence_constraint(model, works, hard_min, soft_min, min_cost,
soft_max, hard_max, max_cost, prefix):
def add_soft_sequence_constraint(
model, works, hard_min, soft_min, min_cost, soft_max, hard_max, max_cost, prefix
):
"""Sequence constraint on true variables with soft and hard bounds.
This constraint look at every maximal contiguous sequence of variables
assigned to true. If forbids sequence of length < hard_min or > hard_max.
Then it creates penalty terms if the length is < soft_min or > soft_max.
This constraint look at every maximal contiguous sequence of variables
assigned to true. If forbids sequence of length < hard_min or > hard_max.
Then it creates penalty terms if the length is < soft_min or > soft_max.
Args:
model: the sequence constraint is built on this model.
works: a list of Boolean variables.
hard_min: any sequence of true variables must have a length of at least
hard_min.
soft_min: any sequence should have a length of at least soft_min, or a
linear penalty on the delta will be added to the objective.
min_cost: the coefficient of the linear penalty if the length is less than
soft_min.
soft_max: any sequence should have a length of at most soft_max, or a linear
penalty on the delta will be added to the objective.
hard_max: any sequence of true variables must have a length of at most
hard_max.
max_cost: the coefficient of the linear penalty if the length is more than
soft_max.
prefix: a base name for penalty literals.
Args:
model: the sequence constraint is built on this model.
works: a list of Boolean variables.
hard_min: any sequence of true variables must have a length of at least
hard_min.
soft_min: any sequence should have a length of at least soft_min, or a
linear penalty on the delta will be added to the objective.
min_cost: the coefficient of the linear penalty if the length is less than
soft_min.
soft_max: any sequence should have a length of at most soft_max, or a linear
penalty on the delta will be added to the objective.
hard_max: any sequence of true variables must have a length of at most
hard_max.
max_cost: the coefficient of the linear penalty if the length is more than
soft_max.
prefix: a base name for penalty literals.
Returns:
a tuple (variables_list, coefficient_list) containing the different
penalties created by the sequence constraint.
"""
Returns:
a tuple (variables_list, coefficient_list) containing the different
penalties created by the sequence constraint.
"""
cost_literals = []
cost_coefficients = []
@@ -96,7 +100,7 @@ def add_soft_sequence_constraint(model, works, hard_min, soft_min, min_cost,
for length in range(hard_min, soft_min):
for start in range(len(works) - length + 1):
span = negated_bounded_span(works, start, length)
name = ': under_span(start=%i, length=%i)' % (start, length)
name = ": under_span(start=%i, length=%i)" % (start, length)
lit = model.NewBoolVar(prefix + name)
span.append(lit)
model.AddBoolOr(span)
@@ -110,7 +114,7 @@ def add_soft_sequence_constraint(model, works, hard_min, soft_min, min_cost,
for length in range(soft_max + 1, hard_max + 1):
for start in range(len(works) - length + 1):
span = negated_bounded_span(works, start, length)
name = ': over_span(start=%i, length=%i)' % (start, length)
name = ": over_span(start=%i, length=%i)" % (start, length)
lit = model.NewBoolVar(prefix + name)
span.append(lit)
model.AddBoolOr(span)
@@ -120,61 +124,61 @@ def add_soft_sequence_constraint(model, works, hard_min, soft_min, min_cost,
# Just forbid any sequence of true variables with length hard_max + 1
for start in range(len(works) - hard_max):
model.AddBoolOr(
[works[i].Not() for i in range(start, start + hard_max + 1)])
model.AddBoolOr([works[i].Not() for i in range(start, start + hard_max + 1)])
return cost_literals, cost_coefficients
def add_soft_sum_constraint(model, works, hard_min, soft_min, min_cost,
soft_max, hard_max, max_cost, prefix):
def add_soft_sum_constraint(
model, works, hard_min, soft_min, min_cost, soft_max, hard_max, max_cost, prefix
):
"""Sum constraint with soft and hard bounds.
This constraint counts the variables assigned to true from works.
If forbids sum < hard_min or > hard_max.
Then it creates penalty terms if the sum is < soft_min or > soft_max.
This constraint counts the variables assigned to true from works.
If forbids sum < hard_min or > hard_max.
Then it creates penalty terms if the sum is < soft_min or > soft_max.
Args:
model: the sequence constraint is built on this model.
works: a list of Boolean variables.
hard_min: any sequence of true variables must have a sum of at least
hard_min.
soft_min: any sequence should have a sum of at least soft_min, or a linear
penalty on the delta will be added to the objective.
min_cost: the coefficient of the linear penalty if the sum is less than
soft_min.
soft_max: any sequence should have a sum of at most soft_max, or a linear
penalty on the delta will be added to the objective.
hard_max: any sequence of true variables must have a sum of at most
hard_max.
max_cost: the coefficient of the linear penalty if the sum is more than
soft_max.
prefix: a base name for penalty variables.
Args:
model: the sequence constraint is built on this model.
works: a list of Boolean variables.
hard_min: any sequence of true variables must have a sum of at least
hard_min.
soft_min: any sequence should have a sum of at least soft_min, or a linear
penalty on the delta will be added to the objective.
min_cost: the coefficient of the linear penalty if the sum is less than
soft_min.
soft_max: any sequence should have a sum of at most soft_max, or a linear
penalty on the delta will be added to the objective.
hard_max: any sequence of true variables must have a sum of at most
hard_max.
max_cost: the coefficient of the linear penalty if the sum is more than
soft_max.
prefix: a base name for penalty variables.
Returns:
a tuple (variables_list, coefficient_list) containing the different
penalties created by the sequence constraint.
"""
Returns:
a tuple (variables_list, coefficient_list) containing the different
penalties created by the sequence constraint.
"""
cost_variables = []
cost_coefficients = []
sum_var = model.NewIntVar(hard_min, hard_max, '')
sum_var = model.NewIntVar(hard_min, hard_max, "")
# This adds the hard constraints on the sum.
model.Add(sum_var == sum(works))
# Penalize sums below the soft_min target.
if soft_min > hard_min and min_cost > 0:
delta = model.NewIntVar(-len(works), len(works), '')
delta = model.NewIntVar(-len(works), len(works), "")
model.Add(delta == soft_min - sum_var)
# TODO(user): Compare efficiency with only excess >= soft_min - sum_var.
excess = model.NewIntVar(0, 7, prefix + ': under_sum')
excess = model.NewIntVar(0, 7, prefix + ": under_sum")
model.AddMaxEquality(excess, [delta, 0])
cost_variables.append(excess)
cost_coefficients.append(min_cost)
# Penalize sums above the soft_max target.
if soft_max < hard_max and max_cost > 0:
delta = model.NewIntVar(-7, 7, '')
delta = model.NewIntVar(-7, 7, "")
model.Add(delta == sum_var - soft_max)
excess = model.NewIntVar(0, 7, prefix + ': over_sum')
excess = model.NewIntVar(0, 7, prefix + ": over_sum")
model.AddMaxEquality(excess, [delta, 0])
cost_variables.append(excess)
cost_coefficients.append(max_cost)
@@ -187,7 +191,7 @@ def solve_shift_scheduling(params, output_proto):
# Data
num_employees = 8
num_weeks = 3
shifts = ['O', 'M', 'A', 'N']
shifts = ["O", "M", "A", "N"]
# Fixed assignment: (employee, shift, day).
# This fixes the first 2 days of the schedule.
@@ -220,7 +224,7 @@ def solve_shift_scheduling(params, output_proto):
(5, 3, 10, -2),
# Employee 2 does not want a night shift on the first Friday (positive
# weight).
(2, 3, 4, 4)
(2, 3, 4, 4),
]
# Shift constraints on continuous sequence :
@@ -277,7 +281,7 @@ def solve_shift_scheduling(params, output_proto):
for e in range(num_employees):
for s in range(num_shifts):
for d in range(num_days):
work[e, s, d] = model.NewBoolVar('work%i_%i_%i' % (e, s, d))
work[e, s, d] = model.NewBoolVar("work%i_%i_%i" % (e, s, d))
# Linear terms of the objective in a minimization context.
obj_int_vars = []
@@ -305,9 +309,16 @@ def solve_shift_scheduling(params, output_proto):
for e in range(num_employees):
works = [work[e, shift, d] for d in range(num_days)]
variables, coeffs = add_soft_sequence_constraint(
model, works, hard_min, soft_min, min_cost, soft_max, hard_max,
model,
works,
hard_min,
soft_min,
min_cost,
soft_max,
hard_max,
max_cost,
'shift_constraint(employee %i, shift %i)' % (e, shift))
"shift_constraint(employee %i, shift %i)" % (e, shift),
)
obj_bool_vars.extend(variables)
obj_bool_coeffs.extend(coeffs)
@@ -318,10 +329,17 @@ def solve_shift_scheduling(params, output_proto):
for w in range(num_weeks):
works = [work[e, shift, d + w * 7] for d in range(7)]
variables, coeffs = add_soft_sum_constraint(
model, works, hard_min, soft_min, min_cost, soft_max,
hard_max, max_cost,
'weekly_sum_constraint(employee %i, shift %i, week %i)' %
(e, shift, w))
model,
works,
hard_min,
soft_min,
min_cost,
soft_max,
hard_max,
max_cost,
"weekly_sum_constraint(employee %i, shift %i, week %i)"
% (e, shift, w),
)
obj_int_vars.extend(variables)
obj_int_coeffs.extend(coeffs)
@@ -330,14 +348,15 @@ def solve_shift_scheduling(params, output_proto):
for e in range(num_employees):
for d in range(num_days - 1):
transition = [
work[e, previous_shift, d].Not(), work[e, next_shift,
d + 1].Not()
work[e, previous_shift, d].Not(),
work[e, next_shift, d + 1].Not(),
]
if cost == 0:
model.AddBoolOr(transition)
else:
trans_var = model.NewBoolVar(
'transition (employee=%i, day=%i)' % (e, d))
"transition (employee=%i, day=%i)" % (e, d)
)
transition.append(trans_var)
model.AddBoolOr(transition)
obj_bool_vars.append(trans_var)
@@ -350,28 +369,25 @@ def solve_shift_scheduling(params, output_proto):
works = [work[e, s, w * 7 + d] for e in range(num_employees)]
# Ignore Off shift.
min_demand = weekly_cover_demands[d][s - 1]
worked = model.NewIntVar(min_demand, num_employees, '')
worked = model.NewIntVar(min_demand, num_employees, "")
model.Add(worked == sum(works))
over_penalty = excess_cover_penalties[s - 1]
if over_penalty > 0:
name = 'excess_demand(shift=%i, week=%i, day=%i)' % (s, w,
d)
excess = model.NewIntVar(0, num_employees - min_demand,
name)
name = "excess_demand(shift=%i, week=%i, day=%i)" % (s, w, d)
excess = model.NewIntVar(0, num_employees - min_demand, name)
model.Add(excess == worked - min_demand)
obj_int_vars.append(excess)
obj_int_coeffs.append(over_penalty)
# Objective
model.Minimize(
sum(obj_bool_vars[i] * obj_bool_coeffs[i]
for i in range(len(obj_bool_vars))) +
sum(obj_int_vars[i] * obj_int_coeffs[i]
for i in range(len(obj_int_vars))))
sum(obj_bool_vars[i] * obj_bool_coeffs[i] for i in range(len(obj_bool_vars)))
+ sum(obj_int_vars[i] * obj_int_coeffs[i] for i in range(len(obj_int_vars)))
)
if output_proto:
print('Writing proto to %s' % output_proto)
with open(output_proto, 'w') as text_file:
print("Writing proto to %s" % output_proto)
with open(output_proto, "w") as text_file:
text_file.write(str(model))
# Solve the model.
@@ -384,43 +400,45 @@ def solve_shift_scheduling(params, output_proto):
# Print solution.
if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:
print()
header = ' '
header = " "
for w in range(num_weeks):
header += 'M T W T F S S '
header += "M T W T F S S "
print(header)
for e in range(num_employees):
schedule = ''
schedule = ""
for d in range(num_days):
for s in range(num_shifts):
if solver.BooleanValue(work[e, s, d]):
schedule += shifts[s] + ' '
print('worker %i: %s' % (e, schedule))
schedule += shifts[s] + " "
print("worker %i: %s" % (e, schedule))
print()
print('Penalties:')
print("Penalties:")
for i, var in enumerate(obj_bool_vars):
if solver.BooleanValue(var):
penalty = obj_bool_coeffs[i]
if penalty > 0:
print(' %s violated, penalty=%i' % (var.Name(), penalty))
print(" %s violated, penalty=%i" % (var.Name(), penalty))
else:
print(' %s fulfilled, gain=%i' % (var.Name(), -penalty))
print(" %s fulfilled, gain=%i" % (var.Name(), -penalty))
for i, var in enumerate(obj_int_vars):
if solver.Value(var) > 0:
print(' %s violated by %i, linear penalty=%i' %
(var.Name(), solver.Value(var), obj_int_coeffs[i]))
print(
" %s violated by %i, linear penalty=%i"
% (var.Name(), solver.Value(var), obj_int_coeffs[i])
)
print()
print('Statistics')
print(' - status : %s' % solver.StatusName(status))
print(' - conflicts : %i' % solver.NumConflicts())
print(' - branches : %i' % solver.NumBranches())
print(' - wall time : %f s' % solver.WallTime())
print("Statistics")
print(" - status : %s" % solver.StatusName(status))
print(" - conflicts : %i" % solver.NumConflicts())
print(" - branches : %i" % solver.NumBranches())
print(" - wall time : %f s" % solver.WallTime())
def main(_):
solve_shift_scheduling(_PARAMS.value, _OUTPUT_PROTO.value)
if __name__ == '__main__':
if __name__ == "__main__":
app.run(main)

View File

@@ -11,6 +11,7 @@
# 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.
"""Single machine jobshop with setup times, release dates and due dates."""
from typing import Sequence
@@ -19,19 +20,22 @@ from absl import flags
from google.protobuf import text_format
from ortools.sat.python import cp_model
#----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Command line arguments.
_OUTPUT_PROTO = flags.DEFINE_string(
'output_proto', '', 'Output file to write the cp_model proto to.')
"output_proto", "", "Output file to write the cp_model proto to."
)
_PARAMS = flags.DEFINE_string(
'params',
'num_search_workers:16,log_search_progress:true,max_time_in_seconds:45',
'Sat solver parameters.')
_PREPROCESS = flags.DEFINE_bool('--preprocess_times', True,
'Preprocess setup times and durations')
"params",
"num_search_workers:16,log_search_progress:true,max_time_in_seconds:45",
"Sat solver parameters.",
)
_PREPROCESS = flags.DEFINE_bool(
"--preprocess_times", True, "Preprocess setup times and durations"
)
#----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Intermediate solution printer
class SolutionPrinter(cp_model.CpSolverSolutionCallback):
"""Print intermediate solutions."""
@@ -42,8 +46,10 @@ class SolutionPrinter(cp_model.CpSolverSolutionCallback):
def on_solution_callback(self):
"""Called after each new solution found."""
print('Solution %i, time = %f s, objective = %i' %
(self.__solution_count, self.WallTime(), self.ObjectiveValue()))
print(
"Solution %i, time = %f s, objective = %i"
% (self.__solution_count, self.WallTime(), self.ObjectiveValue())
)
self.__solution_count += 1
@@ -53,110 +59,343 @@ def single_machine_scheduling():
parameters = _PARAMS.value
output_proto_file = _OUTPUT_PROTO.value
#----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Data.
job_durations = [
2546, 8589, 5953, 3710, 3630, 3016, 4148, 8706, 1604, 5502, 9983, 6209,
9920, 7860, 2176
2546,
8589,
5953,
3710,
3630,
3016,
4148,
8706,
1604,
5502,
9983,
6209,
9920,
7860,
2176,
]
setup_times = [
[
3559, 1638, 2000, 3676, 2741, 2439, 2406, 1526, 1600, 3356, 4324,
1923, 3663, 4103, 2215
3559,
1638,
2000,
3676,
2741,
2439,
2406,
1526,
1600,
3356,
4324,
1923,
3663,
4103,
2215,
],
[
1442, 3010, 1641, 4490, 2060, 2143, 3376, 3891, 3513, 2855, 2653,
1471, 2257, 1186, 2354
1442,
3010,
1641,
4490,
2060,
2143,
3376,
3891,
3513,
2855,
2653,
1471,
2257,
1186,
2354,
],
[
1728, 3583, 3243, 4080, 2191, 3644, 4023, 3510, 2135, 1346, 1410,
3565, 3181, 1126, 4169
1728,
3583,
3243,
4080,
2191,
3644,
4023,
3510,
2135,
1346,
1410,
3565,
3181,
1126,
4169,
],
[
1291, 1703, 3103, 4001, 1712, 1137, 3341, 3485, 2557, 2435, 1972,
1986, 1522, 4734, 2520
1291,
1703,
3103,
4001,
1712,
1137,
3341,
3485,
2557,
2435,
1972,
1986,
1522,
4734,
2520,
],
[
4134, 2200, 1502, 3995, 1277, 1808, 1020, 2078, 2999, 1605, 1697,
2323, 2268, 2288, 4856
4134,
2200,
1502,
3995,
1277,
1808,
1020,
2078,
2999,
1605,
1697,
2323,
2268,
2288,
4856,
],
[
4974, 2480, 2492, 4088, 2587, 4652, 1478, 3942, 1222, 3305, 1206,
1024, 2605, 3080, 3516
4974,
2480,
2492,
4088,
2587,
4652,
1478,
3942,
1222,
3305,
1206,
1024,
2605,
3080,
3516,
],
[
1903, 2584, 2104, 1609, 4745, 2691, 1539, 2544, 2499, 2074, 4793,
1756, 2190, 1298, 2605
1903,
2584,
2104,
1609,
4745,
2691,
1539,
2544,
2499,
2074,
4793,
1756,
2190,
1298,
2605,
],
[
1407, 2536, 2296, 1769, 1449, 3386, 3046, 1180, 4132, 4783, 3386,
3429, 2450, 3376, 3719
1407,
2536,
2296,
1769,
1449,
3386,
3046,
1180,
4132,
4783,
3386,
3429,
2450,
3376,
3719,
],
[
3026, 1637, 3628, 3096, 1498, 4947, 1912, 3703, 4107, 4730, 1805,
2189, 1789, 1985, 3586
3026,
1637,
3628,
3096,
1498,
4947,
1912,
3703,
4107,
4730,
1805,
2189,
1789,
1985,
3586,
],
[
3940, 1342, 1601, 2737, 1748, 3771, 4052, 1619, 2558, 3782, 4383,
3451, 4904, 1108, 1750
3940,
1342,
1601,
2737,
1748,
3771,
4052,
1619,
2558,
3782,
4383,
3451,
4904,
1108,
1750,
],
[
1348, 3162, 1507, 3936, 1453, 2953, 4182, 2968, 3134, 1042, 3175,
2805, 4901, 1735, 1654
1348,
3162,
1507,
3936,
1453,
2953,
4182,
2968,
3134,
1042,
3175,
2805,
4901,
1735,
1654,
],
[
1099, 1711, 1245, 1067, 4343, 3407, 1108, 1784, 4803, 2342, 3377,
2037, 3563, 1621, 2840
1099,
1711,
1245,
1067,
4343,
3407,
1108,
1784,
4803,
2342,
3377,
2037,
3563,
1621,
2840,
],
[
2573, 4222, 3164, 2563, 3231, 4731, 2395, 1033, 4795, 3288, 2335,
4935, 4066, 1440, 4979
2573,
4222,
3164,
2563,
3231,
4731,
2395,
1033,
4795,
3288,
2335,
4935,
4066,
1440,
4979,
],
[
3321, 1666, 3573, 2377, 4649, 4600, 1065, 2475, 3658, 3374, 1138,
4367, 4728, 3032, 2198
3321,
1666,
3573,
2377,
4649,
4600,
1065,
2475,
3658,
3374,
1138,
4367,
4728,
3032,
2198,
],
[
2986, 1180, 4095, 3132, 3987, 3880, 3526, 1460, 4885, 3827, 4945,
4419, 3486, 3805, 3804
2986,
1180,
4095,
3132,
3987,
3880,
3526,
1460,
4885,
3827,
4945,
4419,
3486,
3805,
3804,
],
[
4163, 3441, 1217, 2941, 1210, 3794, 1779, 1904, 4255, 4967, 4003,
3873, 1002, 2055, 4295
4163,
3441,
1217,
2941,
1210,
3794,
1779,
1904,
4255,
4967,
4003,
3873,
1002,
2055,
4295,
],
]
due_dates = [
-1, -1, 28569, -1, 98104, 27644, 55274, 57364, -1, -1, 60875, 96637,
77888, -1, -1
]
release_dates = [
0, 0, 0, 0, 19380, 0, 0, 48657, 0, 27932, 0, 0, 24876, 0, 0
-1,
-1,
28569,
-1,
98104,
27644,
55274,
57364,
-1,
-1,
60875,
96637,
77888,
-1,
-1,
]
release_dates = [0, 0, 0, 0, 19380, 0, 0, 48657, 0, 27932, 0, 0, 24876, 0, 0]
precedences = [(0, 2), (1, 2)]
#----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Helper data.
num_jobs = len(job_durations)
all_jobs = range(num_jobs)
#----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Preprocess.
if _PREPROCESS.value:
for job_id in all_jobs:
min_incoming_setup = min(
setup_times[j][job_id] for j in range(num_jobs + 1))
setup_times[j][job_id] for j in range(num_jobs + 1)
)
if release_dates[job_id] != 0:
min_incoming_setup = min(min_incoming_setup,
release_dates[job_id])
min_incoming_setup = min(min_incoming_setup, release_dates[job_id])
if min_incoming_setup == 0:
continue
print('job %i has a min incoming setup of %i' %
(job_id, min_incoming_setup))
print(
"job %i has a min incoming setup of %i" % (job_id, min_incoming_setup)
)
# We can transfer some setup times to the duration of the job.
job_durations[job_id] += min_incoming_setup
# Decrease corresponding incoming setup times.
@@ -166,36 +405,37 @@ def single_machine_scheduling():
if release_dates[job_id] != 0:
release_dates[job_id] -= min_incoming_setup
#----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Model.
model = cp_model.CpModel()
#----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Compute a maximum makespan greedily.
horizon = sum(job_durations) + sum(
max(setup_times[i][j]
for i in range(num_jobs + 1))
for j in range(num_jobs))
print('Greedy horizon =', horizon)
max(setup_times[i][j] for i in range(num_jobs + 1)) for j in range(num_jobs)
)
print("Greedy horizon =", horizon)
#----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Global storage of variables.
intervals = []
starts = []
ends = []
#----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Scan the jobs and create the relevant variables and intervals.
for job_id in all_jobs:
duration = job_durations[job_id]
release_date = release_dates[job_id]
due_date = due_dates[job_id] if due_dates[job_id] != -1 else horizon
print('job %2i: start = %5i, duration = %4i, end = %6i' %
(job_id, release_date, duration, due_date))
name_suffix = '_%i' % job_id
start = model.NewIntVar(release_date, due_date, 's' + name_suffix)
end = model.NewIntVar(release_date, due_date, 'e' + name_suffix)
interval = model.NewIntervalVar(start, duration, end, 'i' + name_suffix)
print(
"job %2i: start = %5i, duration = %4i, end = %6i"
% (job_id, release_date, duration, due_date)
)
name_suffix = "_%i" % job_id
start = model.NewIntVar(release_date, due_date, "s" + name_suffix)
end = model.NewIntVar(release_date, due_date, "e" + name_suffix)
interval = model.NewIntervalVar(start, duration, end, "i" + name_suffix)
starts.append(start)
ends.append(end)
intervals.append(interval)
@@ -203,24 +443,24 @@ def single_machine_scheduling():
# No overlap constraint.
model.AddNoOverlap(intervals)
#----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Transition times using a circuit constraint.
arcs = []
for i in all_jobs:
# Initial arc from the dummy node (0) to a task.
start_lit = model.NewBoolVar('')
start_lit = model.NewBoolVar("")
arcs.append([0, i + 1, start_lit])
# If this task is the first, set to minimum starting time.
min_start_time = max(release_dates[i], setup_times[0][i])
model.Add(starts[i] == min_start_time).OnlyEnforceIf(start_lit)
# Final arc from an arc to the dummy node.
arcs.append([i + 1, 0, model.NewBoolVar('')])
arcs.append([i + 1, 0, model.NewBoolVar("")])
for j in all_jobs:
if i == j:
continue
lit = model.NewBoolVar('%i follows %i' % (j, i))
lit = model.NewBoolVar("%i follows %i" % (j, i))
arcs.append([i + 1, j + 1, lit])
# We add the reified precedence to link the literal with the times of the
@@ -228,34 +468,36 @@ def single_machine_scheduling():
# If release_dates[j] == 0, we can strenghten this precedence into an
# equality as we are minimizing the makespan.
if release_dates[j] == 0:
model.Add(starts[j] == ends[i] +
setup_times[i + 1][j]).OnlyEnforceIf(lit)
model.Add(starts[j] == ends[i] + setup_times[i + 1][j]).OnlyEnforceIf(
lit
)
else:
model.Add(starts[j] >= ends[i] +
setup_times[i + 1][j]).OnlyEnforceIf(lit)
model.Add(starts[j] >= ends[i] + setup_times[i + 1][j]).OnlyEnforceIf(
lit
)
model.AddCircuit(arcs)
#----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Precedences.
for before, after in precedences:
print('job %i is after job %i' % (after, before))
print("job %i is after job %i" % (after, before))
model.Add(ends[before] <= starts[after])
#----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Objective.
makespan = model.NewIntVar(0, horizon, 'makespan')
makespan = model.NewIntVar(0, horizon, "makespan")
model.AddMaxEquality(makespan, ends)
model.Minimize(makespan)
#----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Write problem to file.
if output_proto_file:
print('Writing proto to %s' % output_proto_file)
with open(output_proto_file, 'w') as text_file:
print("Writing proto to %s" % output_proto_file)
with open(output_proto_file, "w") as text_file:
text_file.write(str(model))
#----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Solve.
solver = cp_model.CpSolver()
if parameters:
@@ -264,15 +506,16 @@ def single_machine_scheduling():
solver.Solve(model, solution_printer)
for job_id in all_jobs:
print(
'job %i starts at %i end ends at %i' %
(job_id, solver.Value(starts[job_id]), solver.Value(ends[job_id])))
"job %i starts at %i end ends at %i"
% (job_id, solver.Value(starts[job_id]), solver.Value(ends[job_id]))
)
def main(argv: Sequence[str]) -> None:
if len(argv) > 1:
raise app.UsageError('Too many command-line arguments.')
raise app.UsageError("Too many command-line arguments.")
single_machine_scheduling()
if __name__ == '__main__':
if __name__ == "__main__":
app.run(main)

View File

@@ -11,6 +11,7 @@
# 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.
"""Maximize the minimum of pairwise distances between n robots in a square space."""
import math
@@ -21,14 +22,14 @@ from absl import flags
from google.protobuf import text_format
from ortools.sat.python import cp_model
_NUM_ROBOTS = flags.DEFINE_integer('num_robots', 8,
'Number of robots to place.')
_ROOM_SIZE = flags.DEFINE_integer('room_size', 20,
'Size of the square room where robots are.')
_NUM_ROBOTS = flags.DEFINE_integer("num_robots", 8, "Number of robots to place.")
_ROOM_SIZE = flags.DEFINE_integer(
"room_size", 20, "Size of the square room where robots are."
)
_PARAMS = flags.DEFINE_string(
'params',
'num_search_workers:16, max_time_in_seconds:20',
'Sat solver parameters.',
"params",
"num_search_workers:16, max_time_in_seconds:20",
"Sat solver parameters.",
)
@@ -37,8 +38,8 @@ def spread_robots(num_robots: int, room_size: int, params: str):
model = cp_model.CpModel()
# Create the list of coordinates (x, y) for each robot.
x = [model.NewIntVar(1, room_size, f'x_{i}') for i in range(num_robots)]
y = [model.NewIntVar(1, room_size, f'y_{i}') for i in range(num_robots)]
x = [model.NewIntVar(1, room_size, f"x_{i}") for i in range(num_robots)]
y = [model.NewIntVar(1, room_size, f"y_{i}") for i in range(num_robots)]
# The specification of the problem is to maximize the minimum euclidian
# distance between any two robots. Unfortunately, the euclidian distance
@@ -57,30 +58,30 @@ def spread_robots(num_robots: int, room_size: int, params: str):
# forall i:
# scaled_min_square_distance <= scaling * (x_diff_sq[i] + y_diff_sq[i])
scaling = 1000
scaled_min_square_distance = model.NewIntVar(0, 2 * scaling * room_size**2,
'scaled_min_square_distance')
scaled_min_square_distance = model.NewIntVar(
0, 2 * scaling * room_size**2, "scaled_min_square_distance"
)
# Build intermediate variables and get the list of squared distances on
# each dimension.
for i in range(num_robots - 1):
for j in range(i + 1, num_robots):
# Compute the distance on each dimension between robot i and robot j.
x_diff = model.NewIntVar(-room_size, room_size, f'x_diff{i}')
y_diff = model.NewIntVar(-room_size, room_size, f'y_diff{i}')
x_diff = model.NewIntVar(-room_size, room_size, f"x_diff{i}")
y_diff = model.NewIntVar(-room_size, room_size, f"y_diff{i}")
model.Add(x_diff == x[i] - x[j])
model.Add(y_diff == y[i] - y[j])
# Compute the square of the previous differences.
x_diff_sq = model.NewIntVar(0, room_size**2, f'x_diff_sq{i}')
y_diff_sq = model.NewIntVar(0, room_size**2, f'y_diff_sq{i}')
x_diff_sq = model.NewIntVar(0, room_size**2, f"x_diff_sq{i}")
y_diff_sq = model.NewIntVar(0, room_size**2, f"y_diff_sq{i}")
model.AddMultiplicationEquality(x_diff_sq, x_diff, x_diff)
model.AddMultiplicationEquality(y_diff_sq, y_diff, y_diff)
# We just need to be <= to the scaled square distance as we are
# maximizing the min distance, which is equivalent as maximizing the min
# square distance.
model.Add(scaled_min_square_distance <= scaling *
(x_diff_sq + y_diff_sq))
model.Add(scaled_min_square_distance <= scaling * (x_diff_sq + y_diff_sq))
# Naive symmetry breaking.
for i in range(1, num_robots):
@@ -99,20 +100,22 @@ def spread_robots(num_robots: int, room_size: int, params: str):
# Prints the solution.
if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:
print(f'Spread {num_robots} with a min pairwise distance of'
f' {math.sqrt(solver.ObjectiveValue() / scaling)}')
print(
f"Spread {num_robots} with a min pairwise distance of"
f" {math.sqrt(solver.ObjectiveValue() / scaling)}"
)
for i in range(num_robots):
print(f'robot {i}: x={solver.Value(x[i])} y={solver.Value(y[i])}')
print(f"robot {i}: x={solver.Value(x[i])} y={solver.Value(y[i])}")
else:
print('No solution found.')
print("No solution found.")
def main(argv: Sequence[str]) -> None:
if len(argv) > 1:
raise app.UsageError('Too many command-line arguments.')
raise app.UsageError("Too many command-line arguments.")
spread_robots(_NUM_ROBOTS.value, _ROOM_SIZE.value, _PARAMS.value)
if __name__ == '__main__':
if __name__ == "__main__":
app.run(main)

View File

@@ -11,6 +11,7 @@
# 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.
"""Solves the Stell Mill Slab problem with 4 different techniques."""
# overloaded sum() clashes with pytype.
@@ -20,21 +21,54 @@ import time
from absl import app
from absl import flags
from google.protobuf import text_format
from ortools.sat.python import cp_model
_PROBLEM = flags.DEFINE_integer('problem', 2, 'Problem id to solve.')
_PROBLEM = flags.DEFINE_integer("problem", 2, "Problem id to solve.")
_BREAK_SYMMETRIES = flags.DEFINE_boolean(
'break_symmetries', True, 'Break symmetries between equivalent orders.')
"break_symmetries", True, "Break symmetries between equivalent orders."
)
_SOLVER = flags.DEFINE_string(
'solver', 'sat_column', 'Method used to solve: sat, sat_table, sat_column.')
"solver", "sat_column", "Method used to solve: sat, sat_table, sat_column."
)
_PARAMS = flags.DEFINE_string(
"params",
"max_time_in_seconds:20,num_workers:8,log_search_progress:true",
"CP-SAT parameters.",
)
def build_problem(problem_id):
"""Build problem data."""
capacities = None
num_colors = None
num_slabs = None
orders = None
if problem_id == 0:
capacities = [
0, 12, 14, 17, 18, 19, 20, 23, 24, 25, 26, 27, 28, 29, 30, 32, 35,
39, 42, 43, 44
0,
12,
14,
17,
18,
19,
20,
23,
24,
25,
26,
27,
28,
29,
30,
32,
35,
39,
42,
43,
44,
]
num_colors = 88
num_slabs = 111
@@ -149,7 +183,7 @@ def build_problem(problem_id):
(27, 85),
(27, 86),
(10, 87),
(3, 88)
(3, 88),
]
elif problem_id == 1:
capacities = [0, 17, 44]
@@ -191,7 +225,7 @@ def build_problem(problem_id):
(22, 20),
(5, 21),
(4, 22),
(10, 23)
(10, 23),
]
elif problem_id == 2:
capacities = [0, 17, 44]
@@ -225,7 +259,7 @@ def build_problem(problem_id):
(3, 6),
(22, 14),
(19, 15),
(19, 15)
(19, 15),
]
elif problem_id == 3:
@@ -243,7 +277,7 @@ def build_problem(problem_id):
(4, 7),
(7, 4),
(7, 8),
(3, 6)
(3, 6),
]
return (num_slabs, capacities, num_colors, orders)
@@ -267,21 +301,29 @@ class SteelMillSlabSolutionPrinter(cp_model.CpSolverSolutionCallback):
"""Called on each new solution."""
current_time = time.time()
objective = sum(self.Value(l) for l in self.__loss)
print('Solution %i, time = %f s, objective = %i' %
(self.__solution_count, current_time - self.__start_time,
objective))
print(
"Solution %i, time = %f s, objective = %i"
% (self.__solution_count, current_time - self.__start_time, objective)
)
self.__solution_count += 1
orders_in_slab = [[
o for o in self.__all_orders if self.Value(self.__assign[o][s])
] for s in self.__all_slabs]
orders_in_slab = [
[o for o in self.__all_orders if self.Value(self.__assign[o][s])]
for s in self.__all_slabs
]
for s in self.__all_slabs:
if orders_in_slab[s]:
line = ' - slab %i, load = %i, loss = %i, orders = [' % (
s, self.Value(self.__load[s]), self.Value(self.__loss[s]))
line = " - slab %i, load = %i, loss = %i, orders = [" % (
s,
self.Value(self.__load[s]),
self.Value(self.__loss[s]),
)
for o in orders_in_slab[s]:
line += '#%i(w%i, c%i) ' % (o, self.__orders[o][0],
self.__orders[o][1])
line += ']'
line += "#%i(w%i, c%i) " % (
o,
self.__orders[o][0],
self.__orders[o][1],
)
line += "]"
print(line)
@@ -295,16 +337,17 @@ def steel_mill_slab(problem, break_symmetries):
all_slabs = range(num_slabs)
all_colors = range(num_colors)
all_orders = range(len(orders))
print('Solving steel mill with %i orders, %i slabs, and %i capacities' %
(num_orders, num_slabs, num_capacities - 1))
print(
"Solving steel mill with %i orders, %i slabs, and %i capacities"
% (num_orders, num_slabs, num_capacities - 1)
)
# 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 = [
@@ -318,16 +361,15 @@ def steel_mill_slab(problem, break_symmetries):
# Create the model and the decision variables.
model = cp_model.CpModel()
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_of_slab_%i' % s)
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_of_slab_%i" % s) for s in all_slabs]
color_is_in_slab = [
[model.NewBoolVar("color_%i_in_slab_%i" % (c + 1, s)) for c in all_colors]
for s in all_slabs
]
color_is_in_slab = [[
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:
@@ -345,8 +387,7 @@ def steel_mill_slab(problem, break_symmetries):
for s in all_slabs:
for o in orders_per_color[c]:
model.AddImplication(assign[o][s], color_is_in_slab[s][c])
model.AddImplication(color_is_in_slab[s][c].Not(),
assign[o][s].Not())
model.AddImplication(color_is_in_slab[s][c].Not(), assign[o][s].Not())
# At most two colors per slab.
for s in all_slabs:
@@ -381,35 +422,39 @@ def steel_mill_slab(problem, break_symmetries):
if w not in local_width_to_order:
local_width_to_order[w] = []
local_width_to_order[w].append(o)
for w, os in local_width_to_order.items():
for _, 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]))
for w, os in width_to_unique_color_order.items():
for _, os in width_to_unique_color_order.items():
if len(os) > 1:
for p in range(len(os) - 1):
ordered_equivalent_orders.append((os[p], os[p + 1]))
# Create position variables if there are symmetries to be broken.
if break_symmetries and ordered_equivalent_orders:
print(' - creating %i symmetry breaking constraints' %
len(ordered_equivalent_orders))
print(
" - creating %i symmetry breaking constraints"
% len(ordered_equivalent_orders)
)
positions = {}
for p in ordered_equivalent_orders:
if p[0] not in positions:
positions[p[0]] = model.NewIntVar(0, num_slabs - 1,
'position_of_slab_%i' % p[0])
positions[p[0]] = model.NewIntVar(
0, num_slabs - 1, "position_of_slab_%i" % p[0]
)
model.AddMapDomain(positions[p[0]], assign[p[0]])
if p[1] not in positions:
positions[p[1]] = model.NewIntVar(0, num_slabs - 1,
'position_of_slab_%i' % p[1])
positions[p[1]] = model.NewIntVar(
0, num_slabs - 1, "position_of_slab_%i" % p[1]
)
model.AddMapDomain(positions[p[1]], assign[p[1]])
# Finally add the symmetry breaking constraint.
model.Add(positions[p[0]] <= positions[p[1]])
# Objective.
obj = model.NewIntVar(0, num_slabs * max_loss, 'obj')
losses = [model.NewIntVar(0, max_loss, 'loss_%i' % s) for s in all_slabs]
obj = model.NewIntVar(0, num_slabs * max_loss, "obj")
losses = [model.NewIntVar(0, max_loss, "loss_%i" % s) for s in all_slabs]
for s in all_slabs:
model.AddElement(loads[s], loss_array, losses[s])
model.Add(obj == sum(losses))
@@ -417,17 +462,19 @@ def steel_mill_slab(problem, break_symmetries):
### Solve model.
solver = cp_model.CpSolver()
solver.parameters.num_search_workers = 8
if _PARAMS.value:
text_format.Parse(_PARAMS.value, solver.parameters)
objective_printer = cp_model.ObjectiveSolutionPrinter()
status = solver.Solve(model, objective_printer)
### 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()))
"Loss = %i, time = %f s, %i conflicts"
% (solver.ObjectiveValue(), solver.WallTime(), solver.NumConflicts())
)
else:
print('No solution')
print("No solution")
def collect_valid_slabs_dp(capacities, colors, widths, loss_array):
@@ -436,13 +483,11 @@ def collect_valid_slabs_dp(capacities, colors, widths, loss_array):
max_capacity = max(capacities)
valid_assignment = collections.namedtuple('valid_assignment',
'orders load colors')
valid_assignment = collections.namedtuple("valid_assignment", "orders load colors")
all_valid_assignments = [valid_assignment(orders=[], load=0, colors=[])]
for order_id in range(len(colors)):
for order_id, new_color in enumerate(colors):
new_width = widths[order_id]
new_color = colors[order_id]
new_assignments = []
for assignment in all_valid_assignments:
if assignment.load + new_width > max_capacity:
@@ -452,18 +497,21 @@ def collect_valid_slabs_dp(capacities, colors, widths, loss_array):
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))]
solution = [0] * len(colors)
for i in assignment.orders:
solution[i] = 1
solution.append(loss_array[assignment.load])
@@ -483,16 +531,17 @@ def steel_mill_slab_with_valid_slabs(problem, break_symmetries):
all_slabs = range(num_slabs)
all_colors = range(num_colors)
all_orders = range(len(orders))
print('Solving steel mill with %i orders, %i slabs, and %i capacities' %
(num_orders, num_slabs, num_capacities - 1))
print(
"Solving steel mill with %i orders, %i slabs, and %i capacities"
% (num_orders, num_slabs, num_capacities - 1)
)
# 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)
@@ -500,21 +549,23 @@ def steel_mill_slab_with_valid_slabs(problem, break_symmetries):
# Create the model and the decision variables.
model = cp_model.CpModel()
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]
losses = [model.NewIntVar(0, max_loss, 'loss_%i' % s) for s in all_slabs]
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]
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)
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:
@@ -529,7 +580,7 @@ def steel_mill_slab_with_valid_slabs(problem, break_symmetries):
# Collect equivalent orders.
if break_symmetries:
print('Breaking symmetries')
print("Breaking symmetries")
width_to_unique_color_order = {}
ordered_equivalent_orders = []
orders_per_color = [
@@ -553,28 +604,32 @@ def steel_mill_slab_with_valid_slabs(problem, break_symmetries):
if w not in local_width_to_order:
local_width_to_order[w] = []
local_width_to_order[w].append(o)
for w, os in local_width_to_order.items():
for _, 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]))
for w, os in width_to_unique_color_order.items():
for _, os in width_to_unique_color_order.items():
if len(os) > 1:
for p in range(len(os) - 1):
ordered_equivalent_orders.append((os[p], os[p + 1]))
# Create position variables if there are symmetries to be broken.
if ordered_equivalent_orders:
print(' - creating %i symmetry breaking constraints' %
len(ordered_equivalent_orders))
print(
" - creating %i symmetry breaking constraints"
% len(ordered_equivalent_orders)
)
positions = {}
for p in ordered_equivalent_orders:
if p[0] not in positions:
positions[p[0]] = model.NewIntVar(
0, num_slabs - 1, 'position_of_slab_%i' % p[0])
0, num_slabs - 1, "position_of_slab_%i" % p[0]
)
model.AddMapDomain(positions[p[0]], assign[p[0]])
if p[1] not in positions:
positions[p[1]] = model.NewIntVar(
0, num_slabs - 1, 'position_of_slab_%i' % p[1])
0, num_slabs - 1, "position_of_slab_%i" % p[1]
)
model.AddMapDomain(positions[p[1]], assign[p[1]])
# Finally add the symmetry breaking constraint.
model.Add(positions[p[0]] <= positions[p[1]])
@@ -582,22 +637,24 @@ def steel_mill_slab_with_valid_slabs(problem, break_symmetries):
# Objective.
model.Minimize(sum(losses))
print('Model created')
print("Model created")
### Solve model.
solver = cp_model.CpSolver()
solver.num_search_workers = 8
solution_printer = SteelMillSlabSolutionPrinter(orders, assign, loads,
losses)
if _PARAMS.value:
text_format.Parse(_PARAMS.value, solver.parameters)
solution_printer = SteelMillSlabSolutionPrinter(orders, assign, loads, losses)
status = solver.Solve(model, solution_printer)
### Output the solution.
if status == cp_model.OPTIMAL:
print(
'Loss = %i, time = %.2f s, %i conflicts' %
(solver.ObjectiveValue(), solver.WallTime(), solver.NumConflicts()))
"Loss = %i, time = %.2f s, %i conflicts"
% (solver.ObjectiveValue(), solver.WallTime(), solver.NumConflicts())
)
else:
print('No solution')
print("No solution")
def steel_mill_slab_with_column_generation(problem):
@@ -608,77 +665,77 @@ def steel_mill_slab_with_column_generation(problem):
num_orders = len(orders)
num_capacities = len(capacities)
all_orders = range(len(orders))
print('Solving steel mill with %i orders, %i slabs, and %i capacities' %
(num_orders, num_slabs, num_capacities - 1))
print(
"Solving steel mill with %i orders, %i slabs, and %i capacities"
% (num_orders, num_slabs, num_capacities - 1)
)
# 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.
# Generate all valid slabs (columns)
unsorted_valid_slabs = collect_valid_slabs_dp(capacities, colors, widths,
loss_array)
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.
model = cp_model.CpModel()
selected = [model.NewBoolVar('selected_%i' % i) for i in all_valid_slabs]
selected = [model.NewBoolVar("selected_%i" % i) for i in all_valid_slabs]
for order_id in all_orders:
model.Add(
sum(selected[i]
for i, slab in enumerate(valid_slabs)
if slab[order_id]) == 1)
sum(selected[i] for i, slab in enumerate(valid_slabs) if slab[order_id])
== 1
)
# Redundant constraint (sum of loads == sum of widths).
model.Add(
sum(selected[i] * valid_slabs[i][-1]
for i in all_valid_slabs) == sum(widths))
sum(selected[i] * valid_slabs[i][-1] for i in all_valid_slabs) == sum(widths)
)
# Objective.
model.Minimize(
sum(selected[i] * valid_slabs[i][-2] for i in all_valid_slabs))
model.Minimize(sum(selected[i] * valid_slabs[i][-2] for i in all_valid_slabs))
print('Model created')
print("Model created")
### Solve model.
solver = cp_model.CpSolver()
solver.parameters.num_search_workers = 8
solver.parameters.log_search_progress = True
if _PARAMS.value:
text_format.Parse(_PARAMS.value, solver.parameters)
solution_printer = cp_model.ObjectiveSolutionPrinter()
status = solver.Solve(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()))
"Loss = %i, time = %.2f s, %i conflicts"
% (solver.ObjectiveValue(), solver.WallTime(), solver.NumConflicts())
)
else:
print('No solution')
print("No solution")
def main(_):
if _SOLVER.value == 'sat':
if _SOLVER.value == "sat":
steel_mill_slab(_PROBLEM.value, _BREAK_SYMMETRIES.value)
elif _SOLVER.value == 'sat_table':
steel_mill_slab_with_valid_slabs(_PROBLEM.value,
_BREAK_SYMMETRIES.value)
elif _SOLVER.value == 'sat_column':
elif _SOLVER.value == "sat_table":
steel_mill_slab_with_valid_slabs(_PROBLEM.value, _BREAK_SYMMETRIES.value)
elif _SOLVER.value == "sat_column":
steel_mill_slab_with_column_generation(_PROBLEM.value)
else:
print(f'Unknown model {_SOLVER.value}')
print(f"Unknown model {_SOLVER.value}")
if __name__ == '__main__':
if __name__ == "__main__":
app.run(main)

View File

@@ -11,6 +11,7 @@
# 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.
"""This model implements a sudoku solver."""
from ortools.sat.python import cp_model
@@ -26,16 +27,22 @@ def solve_sudoku():
line = list(range(0, line_size))
cell = list(range(0, cell_size))
initial_grid = [[0, 6, 0, 0, 5, 0, 0, 2, 0], [0, 0, 0, 3, 0, 0, 0, 9, 0],
[7, 0, 0, 6, 0, 0, 0, 1, 0], [0, 0, 6, 0, 3, 0, 4, 0, 0],
[0, 0, 4, 0, 7, 0, 1, 0, 0], [0, 0, 5, 0, 9, 0, 8, 0, 0],
[0, 4, 0, 0, 0, 1, 0, 0, 6], [0, 3, 0, 0, 0, 8, 0, 0, 0],
[0, 2, 0, 0, 4, 0, 0, 5, 0]]
initial_grid = [
[0, 6, 0, 0, 5, 0, 0, 2, 0],
[0, 0, 0, 3, 0, 0, 0, 9, 0],
[7, 0, 0, 6, 0, 0, 0, 1, 0],
[0, 0, 6, 0, 3, 0, 4, 0, 0],
[0, 0, 4, 0, 7, 0, 1, 0, 0],
[0, 0, 5, 0, 9, 0, 8, 0, 0],
[0, 4, 0, 0, 0, 1, 0, 0, 6],
[0, 3, 0, 0, 0, 8, 0, 0, 0],
[0, 2, 0, 0, 4, 0, 0, 5, 0],
]
grid = {}
for i in line:
for j in line:
grid[(i, j)] = model.NewIntVar(1, line_size, 'grid %i %i' % (i, j))
grid[(i, j)] = model.NewIntVar(1, line_size, "grid %i %i" % (i, j))
# AllDifferent on rows.
for i in line:
@@ -51,8 +58,7 @@ def solve_sudoku():
one_cell = []
for di in cell:
for dj in cell:
one_cell.append(grid[(i * cell_size + di,
j * cell_size + dj)])
one_cell.append(grid[(i * cell_size + di, j * cell_size + dj)])
model.AddAllDifferent(one_cell)

File diff suppressed because it is too large Load Diff

View File

@@ -11,6 +11,7 @@
# 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.
"""Tasks and workers to group assignment to average sum(cost) / #workers."""
from typing import Sequence
@@ -26,8 +27,10 @@ class ObjectivePrinter(cp_model.CpSolverSolutionCallback):
self.__solution_count = 0
def on_solution_callback(self):
print('Solution %i, time = %f s, objective = %i' %
(self.__solution_count, self.WallTime(), self.ObjectiveValue()))
print(
"Solution %i, time = %f s, objective = %i"
% (self.__solution_count, self.WallTime(), self.ObjectiveValue())
)
self.__solution_count += 1
@@ -50,13 +53,13 @@ def tasks_and_workers_assignment_sat():
x = {}
for i in all_workers:
for j in all_groups:
x[i, j] = model.NewBoolVar('x[%i,%i]' % (i, j))
x[i, j] = model.NewBoolVar("x[%i,%i]" % (i, j))
## y_kj is 1 if task k is assigned to group j
y = {}
for k in all_tasks:
for j in all_groups:
y[k, j] = model.NewBoolVar('x[%i,%i]' % (k, j))
y[k, j] = model.NewBoolVar("x[%i,%i]" % (k, j))
# Constraints
@@ -75,13 +78,11 @@ def tasks_and_workers_assignment_sat():
scaled_sum_of_costs_in_group = []
scaling = 1000 # We introduce scaling to deal with floating point average.
for j in all_groups:
n = model.NewIntVar(1, num_workers, 'num_workers_in_group_%i' % j)
n = model.NewIntVar(1, num_workers, "num_workers_in_group_%i" % j)
model.Add(n == sum(x[i, j] for i in all_workers))
c = model.NewIntVar(0, sum_of_costs * scaling,
'sum_of_costs_of_group_%i' % j)
c = model.NewIntVar(0, sum_of_costs * scaling, "sum_of_costs_of_group_%i" % j)
model.Add(c == sum(y[k, j] * task_cost[k] * scaling for k in all_tasks))
a = model.NewIntVar(0, sum_of_costs * scaling,
'average_cost_of_group_%i' % j)
a = model.NewIntVar(0, sum_of_costs * scaling, "average_cost_of_group_%i" % j)
model.AddDivisionEquality(a, c, n)
averages.append(a)
@@ -92,7 +93,7 @@ def tasks_and_workers_assignment_sat():
model.Add(sum(num_workers_in_group) == num_workers)
# Objective.
obj = model.NewIntVar(0, sum_of_costs * scaling, 'obj')
obj = model.NewIntVar(0, sum_of_costs * scaling, "obj")
model.AddMaxEquality(obj, averages)
model.Minimize(obj)
@@ -105,17 +106,18 @@ def tasks_and_workers_assignment_sat():
if status == cp_model.OPTIMAL:
for j in all_groups:
print('Group %i' % j)
print("Group %i" % j)
for i in all_workers:
if solver.BooleanValue(x[i, j]):
print(' - worker %i' % i)
print(" - worker %i" % i)
for k in all_tasks:
if solver.BooleanValue(y[k, j]):
print(' - task %i with cost %i' % (k, task_cost[k]))
print(' - sum_of_costs = %i' %
(solver.Value(scaled_sum_of_costs_in_group[j]) // scaling))
print(' - average cost = %f' %
(solver.Value(averages[j]) * 1.0 / scaling))
print(" - task %i with cost %i" % (k, task_cost[k]))
print(
" - sum_of_costs = %i"
% (solver.Value(scaled_sum_of_costs_in_group[j]) // scaling)
)
print(" - average cost = %f" % (solver.Value(averages[j]) * 1.0 / scaling))
tasks_and_workers_assignment_sat()
@@ -123,9 +125,9 @@ tasks_and_workers_assignment_sat()
def main(argv: Sequence[str]) -> None:
if len(argv) > 1:
raise app.UsageError('Too many command-line arguments.')
raise app.UsageError("Too many command-line arguments.")
tasks_and_workers_assignment_sat()
if __name__ == '__main__':
if __name__ == "__main__":
app.run(main)

File diff suppressed because it is too large Load Diff

View File

@@ -11,6 +11,7 @@
# 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.
"""Solves a simple shift scheduling problem."""
from typing import Sequence
@@ -21,8 +22,15 @@ from ortools.sat.python import cp_model
class SolutionPrinter(cp_model.CpSolverSolutionCallback):
"""Print intermediate solutions."""
def __init__(self, num_vendors, num_hours, possible_schedules,
selected_schedules, hours_stat, min_vendors):
def __init__(
self,
num_vendors,
num_hours,
possible_schedules,
selected_schedules,
hours_stat,
min_vendors,
):
cp_model.CpSolverSolutionCallback.__init__(self)
self.__solution_count = 0
self.__num_vendors = num_vendors
@@ -35,17 +43,18 @@ class SolutionPrinter(cp_model.CpSolverSolutionCallback):
def on_solution_callback(self):
"""Called at each new solution."""
self.__solution_count += 1
print('Solution %i: ', self.__solution_count)
print(' min vendors:', self.__min_vendors)
print("Solution %i: ", self.__solution_count)
print(" min vendors:", self.__min_vendors)
for i in range(self.__num_vendors):
print(
' - vendor %i: ' % i, self.__possible_schedules[self.Value(
self.__selected_schedules[i])])
" - vendor %i: " % i,
self.__possible_schedules[self.Value(self.__selected_schedules[i])],
)
print()
for j in range(self.__num_hours):
print(' - # workers on day%2i: ' % j, end=' ')
print(self.Value(self.__hours_stat[j]), end=' ')
print(" - # workers on day%2i: " % j, end=" ")
print(self.Value(self.__hours_stat[j]), end=" ")
print()
print()
@@ -72,12 +81,14 @@ def vendor_scheduling_sat():
# Last columns are :
# index_of_the_schedule, sum of worked hours (per work type).
# The index is useful for branching.
possible_schedules = [[1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 8],
[1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 4],
[0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 2, 5],
[0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 3, 4],
[1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 4, 3],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0]]
possible_schedules = [
[1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 8],
[1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 4],
[0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 2, 5],
[0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 3, 4],
[1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 4, 3],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0],
]
num_possible_schedules = len(possible_schedules)
selected_schedules = []
@@ -97,11 +108,10 @@ def vendor_scheduling_sat():
for v in all_vendors:
tmp = []
for h in all_hours:
x[v, h] = model.NewIntVar(0, num_work_types, 'x[%i,%i]' % (v, h))
x[v, h] = model.NewIntVar(0, num_work_types, "x[%i,%i]" % (v, h))
tmp.append(x[v, h])
selected_schedule = model.NewIntVar(0, num_possible_schedules - 1,
's[%i]' % v)
hours = model.NewIntVar(0, num_hours, 'h[%i]' % v)
selected_schedule = model.NewIntVar(0, num_possible_schedules - 1, "s[%i]" % v)
hours = model.NewIntVar(0, num_hours, "h[%i]" % v)
selected_schedules.append(selected_schedule)
vendors_stat.append(hours)
tmp.append(selected_schedule)
@@ -113,7 +123,7 @@ def vendor_scheduling_sat():
# Statistics and constraints for each hour
#
for h in all_hours:
workers = model.NewIntVar(0, 1000, 'workers[%i]' % h)
workers = model.NewIntVar(0, 1000, "workers[%i]" % h)
model.Add(workers == sum(x[v, h] for v in all_vendors))
hours_stat.append(workers)
model.Add(workers * max_traffic_per_vendor >= traffic[h])
@@ -127,25 +137,29 @@ def vendor_scheduling_sat():
# Solve model.
solver = cp_model.CpSolver()
solver.parameters.enumerate_all_solutions = True
solution_printer = SolutionPrinter(num_vendors, num_hours,
possible_schedules, selected_schedules,
hours_stat, min_vendors)
solution_printer = SolutionPrinter(
num_vendors,
num_hours,
possible_schedules,
selected_schedules,
hours_stat,
min_vendors,
)
status = solver.Solve(model, solution_printer)
print('Status = %s' % solver.StatusName(status))
print("Status = %s" % solver.StatusName(status))
print('Statistics')
print(' - conflicts : %i' % solver.NumConflicts())
print(' - branches : %i' % solver.NumBranches())
print(' - wall time : %f s' % solver.WallTime())
print(' - number of solutions found: %i' %
solution_printer.solution_count())
print("Statistics")
print(" - conflicts : %i" % solver.NumConflicts())
print(" - branches : %i" % solver.NumBranches())
print(" - wall time : %f s" % solver.WallTime())
print(" - number of solutions found: %i" % solution_printer.solution_count())
def main(argv: Sequence[str]) -> None:
if len(argv) > 1:
raise app.UsageError('Too many command-line arguments.')
raise app.UsageError("Too many command-line arguments.")
vendor_scheduling_sat()
if __name__ == '__main__':
if __name__ == "__main__":
app.run(main)

View File

@@ -11,6 +11,7 @@
# 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.
"""Finding an optimal wedding seating chart.
From
@@ -56,9 +57,10 @@ class WeddingChartPrinter(cp_model.CpSolverSolutionCallback):
def on_solution_callback(self):
current_time = time.time()
objective = self.ObjectiveValue()
print("Solution %i, time = %f s, objective = %i" %
(self.__solution_count, current_time - self.__start_time,
objective))
print(
"Solution %i, time = %f s, objective = %i"
% (self.__solution_count, current_time - self.__start_time, objective)
)
self.__solution_count += 1
for t in range(self.__num_tables):
@@ -85,38 +87,52 @@ def build_data():
# Connection matrix: who knows who, and how strong
# is the relation
connections = [[1, 50, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[50, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 50, 1, 1, 1, 1, 10, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 50, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 50, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 50, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 50, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 50, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 10, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 50, 1, 1, 1, 1, 1, 1],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 50, 1, 1, 1, 1, 1, 1, 1],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1]]
connections = [
[1, 50, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[50, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 50, 1, 1, 1, 1, 10, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 50, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 50, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 50, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 50, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 50, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 10, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 50, 1, 1, 1, 1, 1, 1],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 50, 1, 1, 1, 1, 1, 1, 1],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],
]
# Names of the guests. B: Bride side, G: Groom side
names = [
"Deb (B)", "John (B)", "Martha (B)", "Travis (B)", "Allan (B)",
"Lois (B)", "Jayne (B)", "Brad (B)", "Abby (B)", "Mary Helen (G)",
"Lee (G)", "Annika (G)", "Carl (G)", "Colin (G)", "Shirley (G)",
"DeAnn (G)", "Lori (G)"
"Deb (B)",
"John (B)",
"Martha (B)",
"Travis (B)",
"Allan (B)",
"Lois (B)",
"Jayne (B)",
"Brad (B)",
"Abby (B)",
"Mary Helen (G)",
"Lee (G)",
"Annika (G)",
"Carl (G)",
"Colin (G)",
"Shirley (G)",
"DeAnn (G)",
"Lori (G)",
]
return num_tables, table_capacity, min_known_neighbors, connections, names
def solve_with_discrete_model():
"""Discrete approach."""
num_tables, table_capacity, min_known_neighbors, connections, names = build_data(
)
num_tables, table_capacity, min_known_neighbors, connections, names = build_data()
num_guests = len(connections)
@@ -132,28 +148,32 @@ def solve_with_discrete_model():
seats = {}
for t in all_tables:
for g in all_guests:
seats[(t,
g)] = model.NewBoolVar("guest %i seats on table %i" % (g, t))
seats[(t, g)] = model.NewBoolVar("guest %i seats on table %i" % (g, t))
colocated = {}
for g1 in range(num_guests - 1):
for g2 in range(g1 + 1, num_guests):
colocated[(g1, g2)] = model.NewBoolVar(
"guest %i seats with guest %i" % (g1, g2))
"guest %i seats with guest %i" % (g1, g2)
)
same_table = {}
for g1 in range(num_guests - 1):
for g2 in range(g1 + 1, num_guests):
for t in all_tables:
same_table[(g1, g2, t)] = model.NewBoolVar(
"guest %i seats with guest %i on table %i" % (g1, g2, t))
"guest %i seats with guest %i on table %i" % (g1, g2, t)
)
# Objective
model.Maximize(
sum(connections[g1][g2] * colocated[g1, g2]
sum(
connections[g1][g2] * colocated[g1, g2]
for g1 in range(num_guests - 1)
for g2 in range(g1 + 1, num_guests)
if connections[g1][g2] > 0))
if connections[g1][g2] > 0
)
)
#
# Constraints
@@ -172,29 +192,38 @@ def solve_with_discrete_model():
for g2 in range(g1 + 1, num_guests):
for t in all_tables:
# Link same_table and seats.
model.AddBoolOr([
seats[(t, g1)].Not(), seats[(t, g2)].Not(),
same_table[(g1, g2, t)]
])
model.AddBoolOr(
[
seats[(t, g1)].Not(),
seats[(t, g2)].Not(),
same_table[(g1, g2, t)],
]
)
model.AddImplication(same_table[(g1, g2, t)], seats[(t, g1)])
model.AddImplication(same_table[(g1, g2, t)], seats[(t, g2)])
# Link colocated and same_table.
model.Add(
sum(same_table[(g1, g2, t)] for t in all_tables) == colocated[(
g1, g2)])
sum(same_table[(g1, g2, t)] for t in all_tables) == colocated[(g1, g2)]
)
# Min known neighbors rule.
for g in all_guests:
model.Add(
sum(same_table[(g, g2, t)]
sum(
same_table[(g, g2, t)]
for g2 in range(g + 1, num_guests)
for t in all_tables
if connections[g][g2] > 0) +
sum(same_table[(g1, g, t)]
if connections[g][g2] > 0
)
+ sum(
same_table[(g1, g, t)]
for g1 in range(g)
for t in all_tables
if connections[g1][g] > 0) >= min_known_neighbors)
if connections[g1][g] > 0
)
>= min_known_neighbors
)
# Symmetry breaking. First guest seats on the first table.
model.Add(seats[(0, 0)] == 1)

View File

@@ -11,6 +11,7 @@
# 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.
"""Solve a random Weighted Latency problem with the CP-SAT solver."""
import random
@@ -21,16 +22,16 @@ from absl import flags
from google.protobuf import text_format
from ortools.sat.python import cp_model
_NUM_NODES = flags.DEFINE_integer('num_nodes', 12, 'Number of nodes to visit.')
_GRID_SIZE = flags.DEFINE_integer('grid_size', 20,
'Size of the grid where nodes are.')
_PROFIT_RANGE = flags.DEFINE_integer('profit_range', 50, 'Range of profit.')
_SEED = flags.DEFINE_integer('seed', 0, 'Random seed.')
_PARAMS = flags.DEFINE_string('params',
'num_search_workers:16, max_time_in_seconds:5',
'Sat solver parameters.')
_NUM_NODES = flags.DEFINE_integer("num_nodes", 12, "Number of nodes to visit.")
_GRID_SIZE = flags.DEFINE_integer("grid_size", 20, "Size of the grid where nodes are.")
_PROFIT_RANGE = flags.DEFINE_integer("profit_range", 50, "Range of profit.")
_SEED = flags.DEFINE_integer("seed", 0, "Random seed.")
_PARAMS = flags.DEFINE_string(
"params", "num_search_workers:16, max_time_in_seconds:5", "Sat solver parameters."
)
_PROTO_FILE = flags.DEFINE_string(
'proto_file', '', 'If not empty, output the proto to this file.')
"proto_file", "", "If not empty, output the proto to this file."
)
def build_model():
@@ -60,10 +61,7 @@ def solve_with_cp_sat(x, y, profits):
# because of the manhattan distance, the sum of distances is bounded by this.
horizon = _GRID_SIZE.value * 2 * _NUM_NODES.value
times = [
model.NewIntVar(0, horizon, f'x_{i}')
for i in range(_NUM_NODES.value + 1)
]
times = [model.NewIntVar(0, horizon, f"x_{i}") for i in range(_NUM_NODES.value + 1)]
# Node 0 is the start node.
model.Add(times[0] == 0)
@@ -76,7 +74,7 @@ def solve_with_cp_sat(x, y, profits):
continue
# We use a manhattan distance between nodes.
distance = abs(x[i] - x[j]) + abs(y[i] - y[j])
lit = model.NewBoolVar(f'{i}_to_{j}')
lit = model.NewBoolVar(f"{i}_to_{j}")
arcs.append((i, j, lit))
# Add transitions between nodes.
@@ -103,12 +101,12 @@ def solve_with_cp_sat(x, y, profits):
def main(argv: Sequence[str]) -> None:
if len(argv) > 1:
raise app.UsageError('Too many command-line arguments.')
raise app.UsageError("Too many command-line arguments.")
x, y, profits = build_model()
solve_with_cp_sat(x, y, profits)
# TODO(user): Implement routing model.
if __name__ == '__main__':
if __name__ == "__main__":
app.run(main)

View File

@@ -11,6 +11,7 @@
# 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.
"""This is the zebra problem as invented by Lewis Caroll.
There are five houses.
@@ -43,42 +44,41 @@ def solve_zebra():
# Create the model.
model = cp_model.CpModel()
red = model.NewIntVar(1, 5, 'red')
green = model.NewIntVar(1, 5, 'green')
yellow = model.NewIntVar(1, 5, 'yellow')
blue = model.NewIntVar(1, 5, 'blue')
ivory = model.NewIntVar(1, 5, 'ivory')
red = model.NewIntVar(1, 5, "red")
green = model.NewIntVar(1, 5, "green")
yellow = model.NewIntVar(1, 5, "yellow")
blue = model.NewIntVar(1, 5, "blue")
ivory = model.NewIntVar(1, 5, "ivory")
englishman = model.NewIntVar(1, 5, 'englishman')
spaniard = model.NewIntVar(1, 5, 'spaniard')
japanese = model.NewIntVar(1, 5, 'japanese')
ukrainian = model.NewIntVar(1, 5, 'ukrainian')
norwegian = model.NewIntVar(1, 5, 'norwegian')
englishman = model.NewIntVar(1, 5, "englishman")
spaniard = model.NewIntVar(1, 5, "spaniard")
japanese = model.NewIntVar(1, 5, "japanese")
ukrainian = model.NewIntVar(1, 5, "ukrainian")
norwegian = model.NewIntVar(1, 5, "norwegian")
dog = model.NewIntVar(1, 5, 'dog')
snails = model.NewIntVar(1, 5, 'snails')
fox = model.NewIntVar(1, 5, 'fox')
zebra = model.NewIntVar(1, 5, 'zebra')
horse = model.NewIntVar(1, 5, 'horse')
dog = model.NewIntVar(1, 5, "dog")
snails = model.NewIntVar(1, 5, "snails")
fox = model.NewIntVar(1, 5, "fox")
zebra = model.NewIntVar(1, 5, "zebra")
horse = model.NewIntVar(1, 5, "horse")
tea = model.NewIntVar(1, 5, 'tea')
coffee = model.NewIntVar(1, 5, 'coffee')
water = model.NewIntVar(1, 5, 'water')
milk = model.NewIntVar(1, 5, 'milk')
fruit_juice = model.NewIntVar(1, 5, 'fruit juice')
tea = model.NewIntVar(1, 5, "tea")
coffee = model.NewIntVar(1, 5, "coffee")
water = model.NewIntVar(1, 5, "water")
milk = model.NewIntVar(1, 5, "milk")
fruit_juice = model.NewIntVar(1, 5, "fruit juice")
old_gold = model.NewIntVar(1, 5, 'old gold')
kools = model.NewIntVar(1, 5, 'kools')
chesterfields = model.NewIntVar(1, 5, 'chesterfields')
lucky_strike = model.NewIntVar(1, 5, 'lucky strike')
parliaments = model.NewIntVar(1, 5, 'parliaments')
old_gold = model.NewIntVar(1, 5, "old gold")
kools = model.NewIntVar(1, 5, "kools")
chesterfields = model.NewIntVar(1, 5, "chesterfields")
lucky_strike = model.NewIntVar(1, 5, "lucky strike")
parliaments = model.NewIntVar(1, 5, "parliaments")
model.AddAllDifferent(red, green, yellow, blue, ivory)
model.AddAllDifferent(englishman, spaniard, japanese, ukrainian, norwegian)
model.AddAllDifferent(dog, snails, fox, zebra, horse)
model.AddAllDifferent(tea, coffee, water, milk, fruit_juice)
model.AddAllDifferent(parliaments, kools, chesterfields, lucky_strike,
old_gold)
model.AddAllDifferent(parliaments, kools, chesterfields, lucky_strike, old_gold)
model.Add(englishman == red)
model.Add(spaniard == dog)
@@ -90,18 +90,18 @@ def solve_zebra():
model.Add(milk == 3)
model.Add(norwegian == 1)
diff_fox_chesterfields = model.NewIntVar(-4, 4, 'diff_fox_chesterfields')
diff_fox_chesterfields = model.NewIntVar(-4, 4, "diff_fox_chesterfields")
model.Add(diff_fox_chesterfields == fox - chesterfields)
model.AddAbsEquality(1, diff_fox_chesterfields)
diff_horse_kools = model.NewIntVar(-4, 4, 'diff_horse_kools')
diff_horse_kools = model.NewIntVar(-4, 4, "diff_horse_kools")
model.Add(diff_horse_kools == horse - kools)
model.AddAbsEquality(1, diff_horse_kools)
model.Add(lucky_strike == fruit_juice)
model.Add(japanese == parliaments)
diff_norwegian_blue = model.NewIntVar(-4, 4, 'diff_norwegian_blue')
diff_norwegian_blue = model.NewIntVar(-4, 4, "diff_norwegian_blue")
model.Add(diff_norwegian_blue == norwegian - blue)
model.AddAbsEquality(1, diff_norwegian_blue)
@@ -111,16 +111,12 @@ def solve_zebra():
if status == cp_model.OPTIMAL:
people = [englishman, spaniard, japanese, ukrainian, norwegian]
water_drinker = [
p for p in people if solver.Value(p) == solver.Value(water)
][0]
zebra_owner = [
p for p in people if solver.Value(p) == solver.Value(zebra)
][0]
print('The', water_drinker.Name(), 'drinks water.')
print('The', zebra_owner.Name(), 'owns the zebra.')
water_drinker = [p for p in people if solver.Value(p) == solver.Value(water)][0]
zebra_owner = [p for p in people if solver.Value(p) == solver.Value(zebra)][0]
print("The", water_drinker.Name(), "drinks water.")
print("The", zebra_owner.Name(), "owns the zebra.")
else:
print('No solutions to the zebra problem, this is unusual!')
print("No solutions to the zebra problem, this is unusual!")
solve_zebra()