use black on examples/python
This commit is contained in:
@@ -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]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
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
28
examples/python/chemical_balance_sat.py
Executable file → Normal 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
@@ -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)
|
||||
|
||||
@@ -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
58
examples/python/flexible_job_shop_sat.py
Executable file → Normal 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
62
examples/python/gate_scheduling_sat.py
Executable file → Normal 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)
|
||||
|
||||
@@ -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
34
examples/python/golomb_sat.py
Executable file → Normal 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
126
examples/python/hidato_sat.py
Normal file → Executable 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
60
examples/python/integer_programming.py
Normal file → Executable 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
56
examples/python/jobshop_ft06_distance_sat.py
Normal file → Executable 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
54
examples/python/jobshop_ft06_sat.py
Normal file → Executable 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()
|
||||
|
||||
@@ -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
202
examples/python/knapsack_2d_sat.py
Executable file → Normal 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
112
examples/python/line_balancing_sat.py
Executable file → Normal 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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
244
examples/python/shift_scheduling_sat.py
Executable file → Normal 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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user