more absl::string_view in the c++ code; sync examples/python, remove jniutils.h; move sat/python/visualization.py to sat/colab

This commit is contained in:
Laurent Perron
2023-01-29 21:20:58 +01:00
parent 576fb3fca5
commit a06d0ed7a0
51 changed files with 2516 additions and 1335 deletions

View File

@@ -250,6 +250,7 @@ file(GENERATE OUTPUT ${PYTHON_PROJECT_DIR}/pdlp/__init__.py CONTENT "")
file(GENERATE OUTPUT ${PYTHON_PROJECT_DIR}/pdlp/python/__init__.py CONTENT "")
file(GENERATE OUTPUT ${PYTHON_PROJECT_DIR}/sat/__init__.py CONTENT "")
file(GENERATE OUTPUT ${PYTHON_PROJECT_DIR}/sat/python/__init__.py CONTENT "")
file(GENERATE OUTPUT ${PYTHON_PROJECT_DIR}/sat/colab/__init__.py CONTENT "")
file(GENERATE OUTPUT ${PYTHON_PROJECT_DIR}/scheduling/__init__.py CONTENT "")
file(GENERATE OUTPUT ${PYTHON_PROJECT_DIR}/util/__init__.py CONTENT "")
file(GENERATE OUTPUT ${PYTHON_PROJECT_DIR}/util/python/__init__.py CONTENT "")
@@ -264,8 +265,10 @@ file(COPY
file(COPY
ortools/sat/python/cp_model.py
ortools/sat/python/cp_model_helper.py
ortools/sat/python/visualization.py
DESTINATION ${PYTHON_PROJECT_DIR}/sat/python)
file(COPY
ortools/sat/colab/visualization.py
DESTINATION ${PYTHON_PROJECT_DIR}/sat/colab)
# setup.py.in contains cmake variable e.g. @PYTHON_PROJECT@ and
# generator expression e.g. $<TARGET_FILE_NAME:pyFoo>

View File

@@ -17,14 +17,10 @@ load(":code_samples.bzl", "code_sample_compile_py", "code_sample_py")
# code_sample_py("arc_flow_cutting_stock_sat") # using pywraplp
code_sample_py("assignment2_sat")
code_sample_py("assignment_with_constraints_sat")
code_sample_py("balance_group_sat")
code_sample_py("bus_driver_scheduling_flow_sat")
code_sample_py("bus_driver_scheduling_sat")
code_sample_py("chemical_balance_sat")
@@ -63,8 +59,6 @@ code_sample_py("qubo_sat")
code_sample_compile_py("rcpsp_sat") # no input
code_sample_py("reallocate_sat")
code_sample_py("shift_scheduling_sat")
code_sample_py("single_machine_scheduling_with_setup_release_due_dates_sat")
@@ -83,6 +77,4 @@ code_sample_py("vendor_scheduling_sat")
code_sample_py("wedding_optimal_chart_sat")
code_sample_py("worker_schedule_sat")
code_sample_py("zebra_sat")

View File

@@ -21,7 +21,6 @@ ratio of appointment types.
"""
# overloaded sum() clashes with pytype.
# pytype: disable=wrong-arg-types
# [START import]
from absl import app

View File

@@ -1,81 +0,0 @@
# Copyright 2010-2022 Google LLC
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from ortools.sat.python import cp_model
def main():
# Instantiate a cp model.
cost = [[90, 76, 75, 70, 50, 74, 12, 68], [35, 85, 55, 65, 48, 101, 70, 83],
[125, 95, 90, 105, 59,
120, 36, 73], [45, 110, 95, 115, 104, 83, 37,
71], [60, 105, 80, 75, 59, 62, 93,
88], [45, 65, 110, 95, 47, 31, 81, 34],
[38, 51, 107, 41, 69, 99, 115,
48], [47, 85, 57, 71, 92, 77, 109,
36], [39, 63, 97, 49, 118, 56,
92, 61], [47, 101, 71, 60, 88, 109, 52, 90]]
sizes = [10, 7, 3, 12, 15, 4, 11, 5]
total_size_max = 15
num_workers = len(cost)
num_tasks = len(cost[1])
all_workers = range(num_workers)
all_tasks = range(num_tasks)
model = cp_model.CpModel()
# Variables
total_cost = model.NewIntVar(0, 1000, 'total_cost')
x = []
for i in all_workers:
t = []
for j in all_tasks:
t.append(model.NewBoolVar('x[%i,%i]' % (i, j)))
x.append(t)
# Constraints
# Each task is assigned to at least one worker.
[model.Add(sum(x[i][j] for i in all_workers) >= 1) for j in all_tasks]
# Total task size for each worker is at most total_size_max
for i in all_workers:
model.Add(sum(sizes[j] * x[i][j] for j in all_tasks) <= total_size_max)
# Total cost
model.Add(total_cost == sum(x[i][j] * cost[i][j]
for j in all_tasks for i in all_workers))
model.Minimize(total_cost)
solver = cp_model.CpSolver()
status = solver.Solve(model)
if status == cp_model.OPTIMAL:
print('Total cost = %i' % solver.ObjectiveValue())
print()
for i in all_workers:
for j in all_tasks:
if solver.Value(x[i][j]) == 1:
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())
if __name__ == '__main__':
main()

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env python3
# Copyright 2010-2022 Google LLC
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -12,21 +13,20 @@
# limitations under the License.
"""Solve an assignment problem with combination constraints on workers."""
from typing import Sequence
from absl import app
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
@@ -63,7 +63,8 @@ def solve_assignment():
model = cp_model.CpModel()
# Variables
selected = [[model.NewBoolVar('x[%i,%i]' % (i, j)) for j in all_tasks]
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]
@@ -92,7 +93,8 @@ def solve_assignment():
# Objective
model.Minimize(
sum(selected[i][j] * cost[i][j] for j in all_tasks
sum(selected[i][j] * cost[i][j]
for j in all_tasks
for i in all_workers))
# Solve and output solution.
@@ -116,4 +118,11 @@ def solve_assignment():
print(' - wall time : %f s' % solver.WallTime())
solve_assignment()
def main(argv: Sequence[str]) -> None:
if len(argv) > 1:
raise app.UsageError('Too many command-line arguments.')
solve_assignment()
if __name__ == '__main__':
app.run(main)

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env python3
# Copyright 2010-2022 Google LLC
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -17,8 +18,8 @@ be as close to the average as possible.
Furthermore, if one color is an a group, at least k items with this color must
be in that group.
"""
from typing import Sequence
from absl import app
from ortools.sat.python import cp_model
@@ -60,7 +61,11 @@ class SolutionPrinter(cp_model.CpSolverSolutionCallback):
print(']')
def main():
def main(argv: Sequence[str]) -> None:
"""Solves a group balancing problem."""
if len(argv) > 1:
raise app.UsageError('Too many command-line arguments.')
# Data.
num_groups = 10
num_items = 100
@@ -149,7 +154,8 @@ def main():
# Compute the maximum number of colors in a group.
max_color = num_items_per_group // min_items_of_same_color_per_group
# Redundant contraint: The problem does not solve in reasonable time without it.
# Redundant constraint, it helps with solving time.
if max_color < num_colors:
for g in all_groups:
model.Add(
@@ -158,9 +164,9 @@ def main():
# Minimize epsilon
model.Minimize(e)
model.ExportToFile('balance_group_sat.pbtxt')
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)
status = solver.Solve(model, solution_printer)
@@ -176,4 +182,4 @@ def main():
if __name__ == '__main__':
main()
app.run(main)

View File

@@ -1664,7 +1664,6 @@ SAMPLE_SHIFTS_LARGE = [
[1355, '00:57', '01:07', 1497, 1507, 10]
] # yapf:disable
# pytype: disable=wrong-arg-types
def bus_driver_scheduling(minimize_drivers, max_num_drivers):

View File

@@ -12,81 +12,101 @@
# 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 as close to the average as possible.
Furthermore, if one color is an a group, at least k items with this color must
be in that group."""
Each item has a color and a value. We want the sum of values of each group to be
as close to the average as possible. Furthermore, if one color is an a group, at
least k items with this color must be in that group.
"""
import math
from typing import Sequence
from absl import app
from ortools.sat.python import cp_model
# Data
max_quantities = [
["N_Total", 1944],
["P2O5", 1166.4],
["K2O", 1822.5],
["CaO", 1458],
["MgO", 486],
["Fe", 9.7],
["B", 2.4],
]
def chemical_balance():
"""Solves the chemical balance problem."""
# Data
max_quantities = [
["N_Total", 1944],
["P2O5", 1166.4],
["K2O", 1822.5],
["CaO", 1458],
["MgO", 486],
["Fe", 9.7],
["B", 2.4],
]
chemical_set = [
["A", 0, 0, 510, 540, 0, 0, 0],
["B", 110, 0, 0, 0, 160, 0, 0],
["C", 61, 149, 384, 0, 30, 1, 0.2],
["D", 148, 70, 245, 0, 15, 1, 0.2],
["E", 160, 158, 161, 0, 10, 1, 0.2],
]
chemical_set = [
["A", 0, 0, 510, 540, 0, 0, 0],
["B", 110, 0, 0, 0, 160, 0, 0],
["C", 61, 149, 384, 0, 30, 1, 0.2],
["D", 148, 70, 245, 0, 15, 1, 0.2],
["E", 160, 158, 161, 0, 10, 1, 0.2],
]
NUM_PRODUCTS = len(max_quantities)
ALL_PRODUCTS = range(NUM_PRODUCTS)
num_products = len(max_quantities)
all_products = range(num_products)
NUM_SETS = len(chemical_set)
ALL_SETS = range(NUM_SETS)
num_sets = len(chemical_set)
all_sets = range(num_sets)
# Model
# Model
model = cp_model.CpModel()
model = cp_model.CpModel()
# Scale quantities by 100.
max_set = [
int(
math.ceil(
min(max_quantities[q][1] * 1000 / chemical_set[s][q + 1]
for q in ALL_PRODUCTS if chemical_set[s][q + 1] != 0)))
for s in ALL_SETS
]
# Scale quantities by 100.
max_set = [
int(
math.ceil(
min(max_quantities[q][1] * 1000 / chemical_set[s][q + 1]
for q in all_products
if chemical_set[s][q + 1] != 0)))
for s in all_sets
]
set_vars = [model.NewIntVar(0, max_set[s], f"set_{s}") for s in ALL_SETS]
set_vars = [model.NewIntVar(0, max_set[s], f"set_{s}") for s in all_sets]
epsilon = model.NewIntVar(0, 10000000, "epsilon")
epsilon = model.NewIntVar(0, 10000000, "epsilon")
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))
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)
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))
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)
model.Minimize(epsilon)
model.Minimize(epsilon)
# Creates a solver and solves.
solver = cp_model.CpSolver()
status = solver.Solve(model)
print(f"Status = {solver.StatusName(status)}")
# The objective value of the solution.
print(f"Optimal objective value = {solver.ObjectiveValue() / 10000.0}")
# Creates a solver and solves.
solver = cp_model.CpSolver()
status = solver.Solve(model)
print(f"Status = {solver.StatusName(status)}")
# The objective value of the solution.
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()
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)
print(f"{name}: {quantity} out of {max_quantity}")
for s in all_sets:
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)
print(f"{name}: {quantity} out of {max_quantity}")
def main(argv: Sequence[str]) -> None:
if len(argv) > 1:
raise app.UsageError("Too many command-line arguments.")
chemical_balance()
if __name__ == "__main__":
app.run(main)

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env python3
# Copyright 2010-2022 Google LLC
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -12,7 +13,8 @@
# limitations under the License.
"""Cluster 40 cities in 4 equal groups to minimize sum of crossed distances."""
from typing import Sequence
from absl import app
from ortools.sat.python import cp_model
@@ -57,10 +59,10 @@ distance_matrix = [
[10084, 20956, 14618, 12135, 38935, 8306, 9793, 2615, 5850, 10467, 9918, 14568, 13907, 11803, 11750, 13657, 6901, 23862, 16125, 14748, 12981, 11624, 21033, 15358, 24144, 10304, 10742, 9094, 8042, 7408, 4580, 4072, 8446, 20543, 26181, 7668, 2747, 0, 3330, 5313],
[13026, 23963, 17563, 14771, 42160, 11069, 12925, 5730, 8778, 13375, 11235, 14366, 13621, 11188, 10424, 11907, 5609, 21861, 13624, 11781, 9718, 8304, 17737, 12200, 20816, 7330, 7532, 6117, 4735, 4488, 2599, 3355, 7773, 22186, 27895, 9742, 726, 3330, 0, 2042],
[15056, 25994, 19589, 16743, 44198, 13078, 14967, 7552, 10422, 14935, 11891, 14002, 13225, 10671, 9475, 10633, 5084, 20315, 11866, 9802, 7682, 6471, 15720, 10674, 18908, 6204, 6000, 5066, 3039, 3721, 3496, 4772, 8614, 23805, 29519, 11614, 2749, 5313, 2042, 0],
] # yapf: disable
] # yapf: disable
def main():
def clustering_sat():
"""Entry point of the program."""
num_nodes = len(distance_matrix)
print('Num nodes =', num_nodes)
@@ -85,16 +87,17 @@ def main():
# Number of neighborss:
for n in range(num_nodes):
model.Add(sum(neighbors[m, n] for m in range(n)) +
sum(neighbors[n, m] for m in range(n + 1, num_nodes)) ==
group_size - 1)
model.Add(
sum(neighbors[m, n] for m in range(n)) +
sum(neighbors[n, m]
for m in range(n + 1, num_nodes)) == group_size - 1)
# Enforce transivity on all triplets.
for n1 in range(num_nodes - 2):
for n2 in range(n1 + 1, num_nodes - 1):
for n3 in range(n2 + 1, num_nodes):
model.Add(
neighbors[n1, n3] + neighbors[n2, n3] + neighbors[n1, n2] != 2)
model.Add(neighbors[n1, n3] + neighbors[n2, n3] +
neighbors[n1, n2] != 2)
# Redundant constraints on total sum of neighborss.
model.Add(sum(obj_vars) == num_groups * group_size * (group_size - 1) // 2)
@@ -111,19 +114,26 @@ def main():
status = solver.Solve(model)
print(solver.ResponseStats())
visited = set()
for g in range(num_groups):
for n in range(num_nodes):
if not n in visited:
visited.add(n)
output = str(n)
for o in range(n + 1, num_nodes):
if solver.BooleanValue(neighbors[n, o]):
visited.add(o)
output += ' ' + str(o)
print('Group', g, ':', output)
break
if status == cp_model.FEASIBLE or status == cp_model.OPTIMAL:
visited = set()
for g in range(num_groups):
for n in range(num_nodes):
if n not in visited:
visited.add(n)
output = str(n)
for o in range(n + 1, num_nodes):
if solver.BooleanValue(neighbors[n, o]):
visited.add(o)
output += ' ' + str(o)
print('Group', g, ':', output)
break
def main(argv: Sequence[str]) -> None:
if len(argv) > 1:
raise app.UsageError('Too many command-line arguments.')
clustering_sat()
if __name__ == '__main__':
main()
app.run(main)

View File

@@ -17,7 +17,7 @@ load("@ortools_deps//:requirements.bzl", "requirement")
PYTHON_DEPS = [
"//ortools/sat/python:cp_model",
"//ortools/sat/python:visualization",
"//ortools/sat/colab:visualization",
requirement("absl-py"),
requirement("numpy"),
requirement("pandas"),

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env python3
# Copyright 2010-2022 Google LLC
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -12,7 +13,8 @@
# limitations under the License.
"""Fill a 60x50 rectangle by a minimum number of non-overlapping squares."""
from typing import Sequence
from absl import app
from ortools.sat.python import cp_model
@@ -74,11 +76,12 @@ def cover_rectangle(num_squares):
model.Add(x_starts[i] <= x_starts[i + 1]).OnlyEnforceIf(same)
# Symmetry breaking 2: first square in one quadrant.
model.Add(x_starts[0] < (size_x + 1)// 2)
model.Add(x_starts[0] < (size_x + 1) // 2)
model.Add(y_starts[0] < (size_y + 1) // 2)
# Creates a solver and solves.
solver = cp_model.CpSolver()
solver.parameters.num_workers = 8
status = solver.Solve(model)
print('%s found in %0.2fs' % (solver.StatusName(status), solver.WallTime()))
@@ -102,7 +105,14 @@ def cover_rectangle(num_squares):
return status == cp_model.OPTIMAL
for num_squares in range(1, 15):
print('Trying with size =', num_squares)
if cover_rectangle(num_squares):
break
def main(argv: Sequence[str]) -> None:
if len(argv) > 1:
raise app.UsageError('Too many command-line arguments.')
for num_squares in range(1, 15):
print('Trying with size =', num_squares)
if cover_rectangle(num_squares):
break
if __name__ == '__main__':
app.run(main)

View File

@@ -0,0 +1,82 @@
#!/usr/bin/env python3
# Copyright 2010-2022 Google LLC
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Use CP-SAT to solve a simple cryptarithmetic problem: SEND+MORE=MONEY.
"""
from absl import app
from ortools.sat.python import cp_model
def 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')
# 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')
# 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')
# Force all letters to take on different values.
model.AddAllDifferent(s, e, n, d, m, o, r, y)
# Column 0:
model.Add(c0 == m)
# Column 1:
model.Add(c1 + s + m == o + 10 * c0)
# Column 2:
model.Add(c2 + e + o == n + 10 * c1)
# Column 3:
model.Add(c3 + n + r == e + 10 * c2)
# Column 4:
model.Add(d + e == y + 10 * c3)
# 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))
def main(_):
send_more_money()
if __name__ == '__main__':
app.run(main)

View File

@@ -23,7 +23,6 @@ jobs. This is called the makespan.
"""
# overloaded sum() clashes with pytype.
# pytype: disable=wrong-arg-types
import collections

View File

@@ -24,7 +24,7 @@ The objective is to minimize the max end time of all jobs.
from absl import app
from ortools.sat.python import visualization
from ortools.sat.colab import visualization
from ortools.sat.python import cp_model

View File

@@ -19,7 +19,6 @@ It is known as the Golomb Ruler problem.
The idea is to put marks on a rule such that all differences
between all marks are all different. The objective is to minimize the length
of the rule.
see: https://en.wikipedia.org/wiki/Golomb_ruler
"""
from absl import app
@@ -27,74 +26,56 @@ from absl import flags
from ortools.constraint_solver import pywrapcp
FLAGS = flags.FLAGS
flags.DEFINE_integer('order', 8, 'Order of the ruler.')
# We disable the following warning because it is a false positive on constraints
# like: solver.Add(x == 0)
# pylint: disable=g-explicit-bool-comparison
def solve_golomb_ruler(order):
def main(_):
# Create the solver.
solver = pywrapcp.Solver('golomb ruler')
var_max = order * order
all_vars = list(range(0, order))
size = 8
var_max = size * size
all_vars = list(range(0, size))
marks = [solver.IntVar(0, var_max, f'marks_{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)
solver.Add(marks[0] == 0)
for i in range(order - 2):
solver.Add(marks[i + 1] > marks[i])
# We expand the creation of the diff array to avoid a pylint warning.
diffs = []
for i in range(order - 1):
for j in range(i + 1, order):
for i in range(size - 1):
for j in range(i + 1, size):
diffs.append(marks[j] - marks[i])
solver.Add(solver.AllDifferent(diffs))
# symmetry breaking
if order > 2:
solver.Add(marks[order - 1] - marks[order - 2] > marks[1] - marks[0])
solver.Add(marks[size - 1] - marks[size - 2] > marks[1] - marks[0])
for i in range(size - 2):
solver.Add(marks[i + 1] > marks[i])
# objective
objective = solver.Minimize(marks[order - 1], 1)
# Solve the model.
solution = solver.Assignment()
for mark in marks:
solution.Add(mark)
for diff in diffs:
solution.Add(diff)
solution.Add(marks[size - 1])
collector = solver.AllSolutionCollector(solution)
solver.Solve(
solver.Phase(
marks,
solver.CHOOSE_FIRST_UNBOUND,
solver.ASSIGN_MIN_VALUE),
[objective, collector])
# Print solution.
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[order - 1])
print(f'Solution #{i}: value = {obj_value}')
for idx, var in enumerate(marks):
print(f'mark[{idx}]: {collector.Value(i, var)}')
intervals = [collector.Value(i, diff) for diff in diffs]
intervals.sort()
print(f'intervals: {intervals}')
print('Statistics:')
print(f'- conflicts: {collector.Failures(i)}')
print(f'- branches : {collector.Branches(i)}')
print(f'- wall time: {collector.WallTime(i)}ms\n')
print('Global Statistics:')
print(f'- total conflicts: {solver.Failures()}')
print(f'- total branches : {solver.Branches()}')
print(f'- total wall time: {solver.WallTime()}ms\n')
def main(_=None):
solve_golomb_ruler(FLAGS.order)
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))
time = solver.WallTime()
branches = solver.Branches()
failures = solver.Failures()
print(('Total run : failures = %i, branches = %i, time = %i ms' %
(failures, branches, time)))
if __name__ == '__main__':

View File

@@ -22,19 +22,22 @@ of the rule.
see: https://en.wikipedia.org/wiki/Golomb_ruler
"""
from typing import Sequence
from absl import app
from absl import flags
from google.protobuf import text_format
from ortools.sat.python import cp_model
FLAGS = flags.FLAGS
flags.DEFINE_integer('order', 8, 'Order of the ruler.')
flags.DEFINE_string('params', 'max_time_in_seconds:10.0',
'Sat solver parameters.')
_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.')
def solve_golomb_ruler(order, params):
"""Solve the Golomb ruler problem."""
# Create the model.
model = cp_model.CpModel()
@@ -85,8 +88,10 @@ def solve_golomb_ruler(order, params):
print(f'- wall time: {solver.WallTime()}s\n')
def main(_=None):
solve_golomb_ruler(FLAGS.order, FLAGS.params)
def main(argv: Sequence[str]) -> None:
if len(argv) > 1:
raise app.UsageError('Too many command-line arguments.')
solve_golomb_ruler(_ORDER.value, _PARAMS.value)
if __name__ == '__main__':

View File

@@ -14,7 +14,7 @@
"""Solves the Hidato problem with the CP-SAT solver."""
from absl import app
from ortools.sat.python import visualization
from ortools.sat.colab import visualization
from ortools.sat.python import cp_model

View File

@@ -24,7 +24,7 @@ jobs. This is called the makespan.
import collections
from ortools.sat.python import visualization
from ortools.sat.colab import visualization
from ortools.sat.python import cp_model

View File

@@ -1,4 +1,5 @@
# Copyright 2010-2022 Google
#!/usr/bin/env python3
# Copyright 2010-2022 Google LLC
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
@@ -13,6 +14,8 @@
"""Jobshop with maintenance tasks using the CP-SAT solver."""
import collections
from typing import Sequence
from absl import app
from ortools.sat.python import cp_model
@@ -144,4 +147,11 @@ def jobshop_with_maintenance():
print(' - wall time : %f s' % solver.WallTime())
jobshop_with_maintenance()
def main(argv: Sequence[str]) -> None:
if len(argv) > 1:
raise app.UsageError('Too many command-line arguments.')
jobshop_with_maintenance()
if __name__ == '__main__':
app.run(main)

View File

@@ -42,7 +42,6 @@ _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')
# pytype: disable=wrong-arg-types
class SectionInfo(object):
@@ -337,8 +336,6 @@ def solve_scheduling_model(model, hint):
def main(argv: Sequence[str]) -> None:
if len(argv) > 1:
raise app.UsageError('Too many command-line arguments.')
if _INPUT.value == '':
raise app.UsageError('Missing input file.')
model = read_model(_INPUT.value)
print_stats(model)

View File

@@ -18,11 +18,12 @@
modified so the optimum solution is unique.
"""
from typing import Sequence
from absl import app
from ortools.graph.python import linear_sum_assignment
def RunAssignmentOn4x4Matrix():
def run_assignment_on_4x4_matrix():
"""Test linear sum assignment on a 4x4 matrix."""
num_sources = 4
num_targets = 4
@@ -49,8 +50,10 @@ def RunAssignmentOn4x4Matrix():
'Some input costs are too large and may cause an integer overflow.')
def main(_=None):
RunAssignmentOn4x4Matrix()
def main(argv: Sequence[str]) -> None:
if len(argv) > 1:
raise app.UsageError('Too many command-line arguments.')
run_assignment_on_4x4_matrix()
if __name__ == '__main__':

View File

@@ -0,0 +1,103 @@
#!/usr/bin/env python3
# Copyright 2010-2022 Google LLC
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""CP/SAT model for the N-queens problem."""
import time
from absl import app
from absl import flags
from ortools.sat.python import cp_model
_SIZE = flags.DEFINE_integer('size', 8, 'Number of queens.')
class NQueenSolutionPrinter(cp_model.CpSolverSolutionCallback):
"""Print intermediate solutions."""
def __init__(self, queens):
cp_model.CpSolverSolutionCallback.__init__(self)
self.__queens = queens
self.__solution_count = 0
self.__start_time = time.time()
def SolutionCount(self):
return self.__solution_count
def on_solution_callback(self):
current_time = time.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))
for i in all_queens:
for j in all_queens:
if self.Value(self.__queens[j]) == i:
# There is a queen in column j, row i.
print('Q', end=' ')
else:
print('_', end=' ')
print()
print()
def main(_):
board_size = _SIZE.value
### Creates the solver.
model = cp_model.CpModel()
### 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)
]
### Creates the constraints.
# All columns must be different because the indices of queens are all
# different, so we just add the all different constraint on the rows.
model.AddAllDifferent(queens)
# No two queens can be on the same diagonal.
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)
diag1.append(q1)
diag2.append(q2)
model.Add(q1 == queens[i] + i)
model.Add(q2 == queens[i] - i)
model.AddAllDifferent(diag1)
model.AddAllDifferent(diag2)
### Solve model.
solver = cp_model.CpSolver()
solution_printer = NQueenSolutionPrinter(queens)
# Enumerate all solutions.
solver.parameters.enumerate_all_solutions = True
# Solve.
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())
if __name__ == '__main__':
app.run(main)

View File

@@ -13,8 +13,11 @@
# limitations under the License.
"""Simple prize collecting TSP problem with a max distance."""
from typing import Sequence
from absl import app
from ortools.sat.python import cp_model
DISTANCE_MATRIX = [
[0, 10938, 4542, 2835, 29441, 2171, 1611, 9208, 9528, 11111, 16120, 22606, 22127, 20627, 21246, 23387, 16697, 33609, 26184, 24772, 22644, 20655, 30492, 23296, 32979, 18141, 19248, 17129, 17192, 15645, 12658, 11210, 12094, 13175, 18162, 4968, 12308, 10084, 13026, 15056],
[10938, 0, 6422, 9742, 18988, 12974, 11216, 19715, 19004, 18271, 25070, 31971, 31632, 30571, 31578, 33841, 27315, 43964, 36944, 35689, 33569, 31481, 41360, 33760, 43631, 28730, 29976, 27803, 28076, 26408, 23504, 22025, 22000, 13197, 14936, 15146, 23246, 20956, 23963, 25994],
@@ -56,13 +59,14 @@ DISTANCE_MATRIX = [
[10084, 20956, 14618, 12135, 38935, 8306, 9793, 2615, 5850, 10467, 9918, 14568, 13907, 11803, 11750, 13657, 6901, 23862, 16125, 14748, 12981, 11624, 21033, 15358, 24144, 10304, 10742, 9094, 8042, 7408, 4580, 4072, 8446, 20543, 26181, 7668, 2747, 0, 3330, 5313],
[13026, 23963, 17563, 14771, 42160, 11069, 12925, 5730, 8778, 13375, 11235, 14366, 13621, 11188, 10424, 11907, 5609, 21861, 13624, 11781, 9718, 8304, 17737, 12200, 20816, 7330, 7532, 6117, 4735, 4488, 2599, 3355, 7773, 22186, 27895, 9742, 726, 3330, 0, 2042],
[15056, 25994, 19589, 16743, 44198, 13078, 14967, 7552, 10422, 14935, 11891, 14002, 13225, 10671, 9475, 10633, 5084, 20315, 11866, 9802, 7682, 6471, 15720, 10674, 18908, 6204, 6000, 5066, 3039, 3721, 3496, 4772, 8614, 23805, 29519, 11614, 2749, 5313, 2042, 0],
] # yapf: disable
] # yapf: disable
MAX_DISTANCE = 80_000
VISIT_VALUES = [60_000, 50_000, 40_000, 30_000] * (len(DISTANCE_MATRIX) // 4)
VISIT_VALUES[0] = 0
# Create a console solution printer.
def print_solution(solver, visited_nodes, used_arcs, num_nodes):
"""Prints solution on console."""
@@ -98,7 +102,8 @@ def print_solution(solver, visited_nodes, used_arcs, num_nodes):
plan_output += f'Value collected: {value_collected}/{sum(VISIT_VALUES)}\n'
print(plan_output)
def main():
def prize_collecting_tsp():
"""Entry point of the program."""
num_nodes = len(DISTANCE_MATRIX)
all_nodes = range(num_nodes)
@@ -139,10 +144,10 @@ def main():
model.Add(visited_nodes[0] == 1)
# limit the route distance
model.Add(sum(used_arcs[i, j] * DISTANCE_MATRIX[i][j]
for i in all_nodes
for j in all_nodes) <= MAX_DISTANCE)
model.Add(
sum(used_arcs[i, j] * DISTANCE_MATRIX[i][j]
for i in all_nodes
for j in all_nodes) <= MAX_DISTANCE)
# Maximize visited node values minus the travelled distance.
model.Maximize(
@@ -153,13 +158,18 @@ def main():
# To benefit from the linearization of the circuit constraint.
solver.parameters.max_time_in_seconds = 15.0
solver.parameters.num_search_workers = 8
# solver.parameters.log_search_progress = True
solver.parameters.log_search_progress = True
solver.Solve(model)
#print(solver.ResponseStats())
print_solution(solver, visited_nodes, used_arcs, num_nodes)
status = solver.Solve(model)
if status == cp_model.FEASIBLE or status == cp_model.OPTIMAL:
print_solution(solver, visited_nodes, used_arcs, num_nodes)
def main(argv: Sequence[str]) -> None:
if len(argv) > 1:
raise app.UsageError('Too many command-line arguments.')
prize_collecting_tsp()
if __name__ == '__main__':
main()
app.run(main)

View File

@@ -11,10 +11,13 @@
# 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.
"""Simple prize collecting TSP problem with a max distance."""
"""Simple prize collecting VRP problem with a max distance."""
from typing import Sequence
from absl import app
from ortools.sat.python import cp_model
DISTANCE_MATRIX = [
[0, 10938, 4542, 2835, 29441, 2171, 1611, 9208, 9528, 11111, 16120, 22606, 22127, 20627, 21246, 23387, 16697, 33609, 26184, 24772, 22644, 20655, 30492, 23296, 32979, 18141, 19248, 17129, 17192, 15645, 12658, 11210, 12094, 13175, 18162, 4968, 12308, 10084, 13026, 15056],
[10938, 0, 6422, 9742, 18988, 12974, 11216, 19715, 19004, 18271, 25070, 31971, 31632, 30571, 31578, 33841, 27315, 43964, 36944, 35689, 33569, 31481, 41360, 33760, 43631, 28730, 29976, 27803, 28076, 26408, 23504, 22025, 22000, 13197, 14936, 15146, 23246, 20956, 23963, 25994],
@@ -56,13 +59,14 @@ DISTANCE_MATRIX = [
[10084, 20956, 14618, 12135, 38935, 8306, 9793, 2615, 5850, 10467, 9918, 14568, 13907, 11803, 11750, 13657, 6901, 23862, 16125, 14748, 12981, 11624, 21033, 15358, 24144, 10304, 10742, 9094, 8042, 7408, 4580, 4072, 8446, 20543, 26181, 7668, 2747, 0, 3330, 5313],
[13026, 23963, 17563, 14771, 42160, 11069, 12925, 5730, 8778, 13375, 11235, 14366, 13621, 11188, 10424, 11907, 5609, 21861, 13624, 11781, 9718, 8304, 17737, 12200, 20816, 7330, 7532, 6117, 4735, 4488, 2599, 3355, 7773, 22186, 27895, 9742, 726, 3330, 0, 2042],
[15056, 25994, 19589, 16743, 44198, 13078, 14967, 7552, 10422, 14935, 11891, 14002, 13225, 10671, 9475, 10633, 5084, 20315, 11866, 9802, 7682, 6471, 15720, 10674, 18908, 6204, 6000, 5066, 3039, 3721, 3496, 4772, 8614, 23805, 29519, 11614, 2749, 5313, 2042, 0],
] # yapf: disable
] # yapf: disable
MAX_DISTANCE = 80_000
VISIT_VALUES = [60_000, 50_000, 40_000, 30_000] * (len(DISTANCE_MATRIX) // 4)
VISIT_VALUES[0] = 0
# Create a console solution printer.
def print_solution(solver, visited_nodes, used_arcs, num_nodes, num_vehicles):
"""Prints solution on console."""
@@ -71,7 +75,10 @@ def print_solution(solver, visited_nodes, used_arcs, num_nodes, num_vehicles):
for node in range(num_nodes):
if node == 0:
continue
is_visited = sum([solver.BooleanValue(visited_nodes[v][node]) for v in range(num_vehicles)])
is_visited = sum([
solver.BooleanValue(visited_nodes[v][node])
for v in range(num_vehicles)
])
if not is_visited:
dropped_nodes += f' {node}({VISIT_VALUES[node]})'
print(dropped_nodes)
@@ -106,7 +113,8 @@ def print_solution(solver, visited_nodes, used_arcs, num_nodes, num_vehicles):
print(f'Total Distance: {total_distance}m')
print(f'Total Value collected: {total_value_collected}/{sum(VISIT_VALUES)}')
def main():
def prize_collecting_vrp():
"""Entry point of the program."""
num_nodes = len(DISTANCE_MATRIX)
num_vehicles = 4
@@ -151,13 +159,15 @@ def main():
model.Add(visited_nodes[v][0] == 1)
# limit the route distance
model.Add(sum(used_arcs[v][i, j] * DISTANCE_MATRIX[i][j]
for i in all_nodes
for j in all_nodes) <= MAX_DISTANCE)
model.Add(
sum(used_arcs[v][i, j] * DISTANCE_MATRIX[i][j]
for i in all_nodes
for j in all_nodes) <= MAX_DISTANCE)
# Each node is visited at most once
for node in range(1, num_nodes):
model.AddAtMostOne([visited_nodes[v][node] for v in range(num_vehicles)])
model.AddAtMostOne(
[visited_nodes[v][node] for v in range(num_vehicles)])
# Maximize visited node values minus the travelled distance.
model.Maximize(
@@ -167,16 +177,19 @@ def main():
solver = cp_model.CpSolver()
solver.parameters.num_search_workers = 8
solver.parameters.max_time_in_seconds = 15.0
# solver.parameters.log_search_progress = True
solver.parameters.log_search_progress = True
status = solver.Solve(model)
if status == cp_model.FEASIBLE or status == cp_model.OPTIMAL:
print(f'search returned with the status {solver.StatusName(status)}')
print_solution(solver, visited_nodes, used_arcs,
num_nodes, num_vehicles)
else:
print(solver.ResponseStats())
print_solution(solver, visited_nodes, used_arcs, num_nodes,
num_vehicles)
def main(argv: Sequence[str]) -> None:
if len(argv) > 1:
raise app.UsageError('Too many command-line arguments.')
prize_collecting_vrp()
if __name__ == '__main__':
main()
app.run(main)

View File

@@ -13,12 +13,13 @@
# limitations under the License.
"""MaxFlow and MinCostFlow examples."""
from typing import Sequence
from absl import app
from ortools.graph.python import max_flow
from ortools.graph.python import min_cost_flow
def MaxFlow():
def max_flow_api():
"""MaxFlow simple interface example."""
print('MaxFlow on a simple network.')
tails = [0, 0, 0, 0, 1, 2, 3, 3, 4]
@@ -39,7 +40,7 @@ def MaxFlow():
print('There was an issue with the max flow input.')
def MinCostFlow():
def min_cost_flow_api():
"""MinCostFlow simple interface example.
Note that this example is actually a linear sum assignment example and will
@@ -72,9 +73,11 @@ def MinCostFlow():
print('There was an issue with the min cost flow input.')
def main(_=None):
MaxFlow()
MinCostFlow()
def main(argv: Sequence[str]) -> None:
if len(argv) > 1:
raise app.UsageError('Too many command-line arguments.')
max_flow_api()
min_cost_flow_api()
if __name__ == '__main__':

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env python3
# Copyright 2010-2022 Google LLC
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -12,25 +13,22 @@
# limitations under the License.
"""Single machine jobshop with setup times, release dates and due dates."""
import argparse
from typing import Sequence
from absl import app
from absl import flags
from google.protobuf import text_format
from ortools.sat.python import cp_model
#----------------------------------------------------------------------------
# Command line arguments.
PARSER = argparse.ArgumentParser()
PARSER.add_argument(
'--output_proto_file',
default='',
help='Output file to write the cp_model'
'proto to.')
PARSER.add_argument('--params', default='', help='Sat solver parameters.')
PARSER.add_argument(
'--preprocess_times',
default=True,
type=bool,
help='Preprocess setup times and durations')
_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.')
_PREPROCESS = flags.DEFINE_bool('--preprocess_times', True,
'Preprocess setup times and durations')
#----------------------------------------------------------------------------
@@ -49,11 +47,11 @@ class SolutionPrinter(cp_model.CpSolverSolutionCallback):
self.__solution_count += 1
def main(args):
def single_machine_scheduling():
"""Solves a complex single machine jobshop scheduling problem."""
parameters = args.params
output_proto_file = args.output_proto_file
parameters = _PARAMS.value
output_proto_file = _OUTPUT_PROTO.value
#----------------------------------------------------------------------------
# Data.
@@ -147,7 +145,7 @@ def main(args):
#----------------------------------------------------------------------------
# Preprocess.
if args.preprocess_times:
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))
@@ -175,7 +173,8 @@ def main(args):
#----------------------------------------------------------------------------
# Compute a maximum makespan greedily.
horizon = sum(job_durations) + sum(
max(setup_times[i][j] for i in range(num_jobs + 1))
max(setup_times[i][j]
for i in range(num_jobs + 1))
for j in range(num_jobs))
print('Greedy horizon =', horizon)
@@ -232,8 +231,8 @@ def main(args):
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)
@@ -259,17 +258,21 @@ def main(args):
#----------------------------------------------------------------------------
# Solve.
solver = cp_model.CpSolver()
solver.parameters.max_time_in_seconds = 60 * 60 * 2
if parameters:
text_format.Merge(parameters, solver.parameters)
text_format.Parse(parameters, solver.parameters)
solution_printer = SolutionPrinter()
solver.Solve(model, solution_printer)
print(solver.ResponseStats())
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])))
print(
'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.')
single_machine_scheduling()
if __name__ == '__main__':
main(PARSER.parse_args())
app.run(main)

View File

@@ -14,7 +14,6 @@
"""Solves the Stell Mill Slab problem with 4 different techniques."""
# overloaded sum() clashes with pytype.
# pytype: disable=wrong-arg-types
import collections
import time
@@ -703,9 +702,8 @@ def steel_mill_slab_with_mip_column_generation(problem):
# create model and decision variables.
start_time = time.time()
solver = pywraplp.Solver.CreateSolver('SCIP')
if not solver:
return
solver = pywraplp.Solver('Steel',
pywraplp.Solver.SCIP_MIXED_INTEGER_PROGRAMMING)
selected = [
solver.IntVar(0.0, 1.0, 'selected_%i' % i) for i in all_valid_slabs
]

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env python3
# Copyright 2010-2022 Google LLC
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -11,218 +12,269 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""CP-SAT model for task allocation problem.
see
http://yetanothermathprogrammingconsultant.blogspot.com/2018/09/minizinc-cpsat-vs-mip.html
see http://yetanothermathprogrammingconsultant.blogspot.com/2018/09/minizinc-
cpsat-vs-mip.html
"""
from typing import Sequence
from absl import app
from ortools.sat.python import cp_model
def main():
def task_allocation_sat():
"""Solves the task allocation problem."""
# Availability matrix.
available = [[
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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
], [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0,
0, 0
], [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
], [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
], [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
], [
0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
], [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1,
1, 0
], [
0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
], [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
], [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1
], [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
], [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1
], [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
0, 0
], [
0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
], [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
], [
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
], [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
], [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
], [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
], [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
], [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
], [
0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
], [
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
], [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
], [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1,
1, 1
], [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
], [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
], [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
], [
0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
], [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
], [
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
], [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1,
1, 0
], [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
], [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
], [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
], [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0
], [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
], [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
], [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
], [
0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
], [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
], [
0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
], [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
], [
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
], [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
], [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0,
0, 0
], [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0
], [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0
]]
],
[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 1, 1, 1, 1, 1, 1, 1, 0
],
[
0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1
],
[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1
],
[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 1, 1, 1, 1, 1, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1
],
[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 1, 1, 1, 1, 1, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
1, 0, 0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 0, 0, 0, 0, 0, 0, 0, 0, 0
],
[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1,
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
]]
ntasks = len(available)
nslots = len(available[0])
@@ -245,15 +297,18 @@ def main():
for task in all_tasks:
model.Add(
sum(assign[(task, slot)] for slot in all_slots
sum(assign[(task, slot)]
for slot in all_slots
if available[task][slot] == 1) == 1)
for slot in all_slots:
model.Add(
sum(assign[(task, slot)] for task in all_tasks
sum(assign[(task, slot)]
for task in all_tasks
if available[task][slot] == 1) <= capacity)
model.AddBoolOr([
assign[(task, slot)] for task in all_tasks
assign[(task, slot)]
for task in all_tasks
if available[task][slot] == 1
]).OnlyEnforceIf(slot_used[slot])
for task in all_tasks:
@@ -273,7 +328,7 @@ def main():
solver = cp_model.CpSolver()
# Uses the portfolion of heuristics.
solver.parameters.log_search_progress = True
solver.parameters.num_search_workers = 6
solver.parameters.num_search_workers = 16
status = solver.Solve(model)
print('Statistics')
@@ -282,5 +337,11 @@ def main():
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.')
task_allocation_sat()
if __name__ == '__main__':
main()
app.run(main)

View File

@@ -1,18 +1,20 @@
#!/usr/bin/env python3
# Copyright 2010-2022 Google LLC
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Tasks and workers to group assignment to average sum(cost) / #workers"""
"""Tasks and workers to group assignment to average sum(cost) / #workers."""
from typing import Sequence
from absl import app
from ortools.sat.python import cp_model
@@ -117,3 +119,13 @@ def tasks_and_workers_assignment_sat():
tasks_and_workers_assignment_sat()
def main(argv: Sequence[str]) -> None:
if len(argv) > 1:
raise app.UsageError('Too many command-line arguments.')
tasks_and_workers_assignment_sat()
if __name__ == '__main__':
app.run(main)

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env python3
# Copyright 2010-2022 Google LLC
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -12,10 +13,8 @@
# limitations under the License.
"""Simple travelling salesman problem between cities."""
from ortools.sat.python import cp_model
DISTANCE_MATRIX = [
[0, 10938, 4542, 2835, 29441, 2171, 1611, 9208, 9528, 11111, 16120, 22606, 22127, 20627, 21246, 23387, 16697, 33609, 26184, 24772, 22644, 20655, 30492, 23296, 32979, 18141, 19248, 17129, 17192, 15645, 12658, 11210, 12094, 13175, 18162, 4968, 12308, 10084, 13026, 15056],
[10938, 0, 6422, 9742, 18988, 12974, 11216, 19715, 19004, 18271, 25070, 31971, 31632, 30571, 31578, 33841, 27315, 43964, 36944, 35689, 33569, 31481, 41360, 33760, 43631, 28730, 29976, 27803, 28076, 26408, 23504, 22025, 22000, 13197, 14936, 15146, 23246, 20956, 23963, 25994],
@@ -57,7 +56,7 @@ DISTANCE_MATRIX = [
[10084, 20956, 14618, 12135, 38935, 8306, 9793, 2615, 5850, 10467, 9918, 14568, 13907, 11803, 11750, 13657, 6901, 23862, 16125, 14748, 12981, 11624, 21033, 15358, 24144, 10304, 10742, 9094, 8042, 7408, 4580, 4072, 8446, 20543, 26181, 7668, 2747, 0, 3330, 5313],
[13026, 23963, 17563, 14771, 42160, 11069, 12925, 5730, 8778, 13375, 11235, 14366, 13621, 11188, 10424, 11907, 5609, 21861, 13624, 11781, 9718, 8304, 17737, 12200, 20816, 7330, 7532, 6117, 4735, 4488, 2599, 3355, 7773, 22186, 27895, 9742, 726, 3330, 0, 2042],
[15056, 25994, 19589, 16743, 44198, 13078, 14967, 7552, 10422, 14935, 11891, 14002, 13225, 10671, 9475, 10633, 5084, 20315, 11866, 9802, 7682, 6471, 15720, 10674, 18908, 6204, 6000, 5066, 3039, 3721, 3496, 4772, 8614, 23805, 29519, 11614, 2749, 5313, 2042, 0],
] # yapf: disable
] # yapf: disable
def main():

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env python3
# Copyright 2010-2022 Google LLC
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -12,7 +13,8 @@
# limitations under the License.
"""Solves a simple shift scheduling problem."""
from typing import Sequence
from absl import app
from ortools.sat.python import cp_model
@@ -36,8 +38,9 @@ class SolutionPrinter(cp_model.CpSolverSolutionCallback):
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])])
print(
' - vendor %i: ' % i, self.__possible_schedules[self.Value(
self.__selected_schedules[i])])
print()
for j in range(self.__num_hours):
@@ -51,7 +54,7 @@ class SolutionPrinter(cp_model.CpSolverSolutionCallback):
return self.__solution_count
def main():
def vendor_scheduling_sat():
"""Create the shift scheduling model and solve it."""
# Create the model.
model = cp_model.CpModel()
@@ -69,12 +72,12 @@ def main():
# 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 = []
@@ -134,9 +137,15 @@ def main():
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(' - 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.')
vendor_scheduling_sat()
if __name__ == '__main__':
main()
app.run(main)

View File

@@ -1,6 +1,5 @@
#
# Copyright 2018 Google.
#
#!/usr/bin/env python3
# Copyright 2010-2022 Google LLC
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
@@ -37,6 +36,8 @@ https://github.com/google/or-tools/blob/master/examples/csharp/wedding_optimal_c
"""
import time
from typing import Sequence
from absl import app
from ortools.sat.python import cp_model
@@ -70,11 +71,8 @@ class WeddingChartPrinter(cp_model.CpSolverSolutionCallback):
return self.__solution_count
def BuildData():
#
# Data
#
def build_data():
"""Build the data model."""
# Easy problem (from the paper)
# num_tables = 2
# table_capacity = 10
@@ -87,23 +85,23 @@ def BuildData():
# Connection matrix: who knows who, and how strong
# is the relation
C = [[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 = [
@@ -112,13 +110,15 @@ def BuildData():
"Lee (G)", "Annika (G)", "Carl (G)", "Colin (G)", "Shirley (G)",
"DeAnn (G)", "Lori (G)"
]
return num_tables, table_capacity, min_known_neighbors, C, names
return num_tables, table_capacity, min_known_neighbors, connections, names
def solve_with_discrete_model():
num_tables, table_capacity, min_known_neighbors, C, names = BuildData()
"""Discrete approach."""
num_tables, table_capacity, min_known_neighbors, connections, names = build_data(
)
num_guests = len(C)
num_guests = len(connections)
all_tables = range(num_tables)
all_guests = range(num_guests)
@@ -132,8 +132,8 @@ 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):
@@ -150,9 +150,10 @@ def solve_with_discrete_model():
# Objective
model.Maximize(
sum(C[g1][g2] * colocated[g1, g2]
for g1 in range(num_guests - 1) for g2 in range(g1 + 1, num_guests)
if C[g1][g2] > 0))
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))
#
# Constraints
@@ -180,8 +181,8 @@ def solve_with_discrete_model():
# 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:
@@ -189,12 +190,11 @@ def solve_with_discrete_model():
sum(same_table[(g, g2, t)]
for g2 in range(g + 1, num_guests)
for t in all_tables
if C[g][g2] > 0) +
if connections[g][g2] > 0) +
sum(same_table[(g1, g, t)]
for g1 in range(g)
for t in all_tables
if C[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)
@@ -211,4 +211,11 @@ def solve_with_discrete_model():
print(" - num solutions: %i" % solution_printer.num_solutions())
solve_with_discrete_model()
def main(argv: Sequence[str]) -> None:
if len(argv) > 1:
raise app.UsageError("Too many command-line arguments.")
solve_with_discrete_model()
if __name__ == "__main__":
app.run(main)

View File

@@ -0,0 +1,114 @@
#!/usr/bin/env python3
# Copyright 2010-2022 Google LLC
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Solve a random Weighted Latency problem with the CP-SAT solver."""
import random
from typing import Sequence
from absl import app
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.')
_PROTO_FILE = flags.DEFINE_string(
'proto_file', '', 'If not empty, output the proto to this file.')
def build_model():
"""Create the nodes and the profit."""
random.seed(_SEED.value)
x = []
y = []
x.append(random.randint(0, _GRID_SIZE.value))
y.append(random.randint(0, _GRID_SIZE.value))
for _ in range(_NUM_NODES.value):
x.append(random.randint(0, _GRID_SIZE.value))
y.append(random.randint(0, _GRID_SIZE.value))
profits = []
profits.append(0)
for _ in range(_NUM_NODES.value):
profits.append(random.randint(1, _PROFIT_RANGE.value))
sum_of_profits = sum(profits)
profits = [p / sum_of_profits for p in profits]
return x, y, profits
def solve_with_cp_sat(x, y, profits):
"""Solves the problem with the CP-SAT solver."""
model = cp_model.CpModel()
# 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)
]
# Node 0 is the start node.
model.Add(times[0] == 0)
# Create the circuit constraint.
arcs = []
for i in range(_NUM_NODES.value + 1):
for j in range(_NUM_NODES.value + 1):
if i == j:
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}')
arcs.append((i, j, lit))
# Add transitions between nodes.
if i == 0:
# Initial transition
model.Add(times[j] == distance).OnlyEnforceIf(lit)
elif j != 0:
# We do not care for the last transition.
model.Add(times[j] == times[i] + distance).OnlyEnforceIf(lit)
model.AddCircuit(arcs)
model.Minimize(cp_model.LinearExpr.WeightedSum(times, profits))
if _PROTO_FILE.value:
model.ExportToFile(_PROTO_FILE.value)
# Solve model.
solver = cp_model.CpSolver()
if _PARAMS.value:
text_format.Parse(_PARAMS.value, solver.parameters)
solver.parameters.log_search_progress = True
solver.Solve(model)
def main(argv: Sequence[str]) -> None:
if len(argv) > 1:
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__':
app.run(main)

View File

@@ -1,158 +0,0 @@
# Copyright 2010-2022 Google LLC
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from ortools.sat.python import cp_model
def schedule():
# Input data.
positions = [
1, 2, 8, 10, 5, 3, 4, 3, 6, 6, 4, 5, 4, 3, 4, 4, 3, 4, 2, 1, 0, 0, 0, 0,
1, 2, 9, 9, 4, 3, 4, 3, 5, 4, 5, 2, 5, 6, 6, 7, 4, 2, 1, 0, 0, 0, 0, 0,
0, 2, 7, 6, 5, 2, 4, 4, 6, 6, 4, 5, 5, 5, 7, 5, 4, 4, 2, 3, 1, 0, 0, 0,
1, 2, 9, 7, 2, 2, 4, 2, 4, 5, 3, 2, 6, 7, 5, 6, 4, 4, 2, 1, 0, 0, 0, 0,
2, 2, 8, 8, 6, 3, 3, 3, 10, 9, 6, 3, 3, 4, 5, 4, 5, 4, 2, 1, 0, 0, 0, 0,
1, 2, 9, 5, 5, 4, 5, 2, 5, 7, 5, 3, 4, 8, 4, 4, 2, 3, 1, 0, 0, 0, 0, 0,
1, 2, 10, 5, 5, 4, 5, 2, 4, 6, 7, 4, 4, 5, 4, 4, 3, 3, 2, 1, 0, 0, 0, 0
]
possible_shifts = [[
1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 40
], [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 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,
1, 40
], [
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, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
2, 40
], [
0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
3, 40
], [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
4, 0
], [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
5, 16
], [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
6, 16
], [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,
7, 16
], [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,
8, 40
]]
# Useful numbers.
num_slots = len(positions)
all_slots = range(num_slots)
num_shifts = len(possible_shifts)
all_shifts = range(num_shifts)
min_number_of_workers = [5 * x for x in positions]
num_workers = 300
# Model the problem.
model = cp_model.CpModel()
workers_per_shift = [
model.NewIntVar(0, num_workers, 'shift[%i]' % i) for i in all_shifts
]
# Satisfy min requirements.
for slot in all_slots:
model.Add(
sum(workers_per_shift[shift] * possible_shifts[shift][slot]
for shift in all_shifts) >= min_number_of_workers[slot])
# Create the objective variable.
objective = model.NewIntVar(0, num_workers, 'objective')
# Link the objective.
model.Add(sum(workers_per_shift) == objective)
# Minimize.
model.Minimize(objective)
solver = cp_model.CpSolver()
status = solver.Solve(model)
if status == cp_model.OPTIMAL:
print('Objective value = %i' % solver.ObjectiveValue())
print('Statistics')
print(' - conflicts : %i' % solver.NumConflicts())
print(' - branches : %i' % solver.NumBranches())
print(' - wall time : %f s' % solver.WallTime())
if __name__ == '__main__':
schedule()

View File

@@ -19,6 +19,7 @@
#include <string>
#include <vector>
#include "absl/strings/string_view.h"
#include "absl/synchronization/mutex.h"
#include "ortools/sat/boolean_problem.h"
@@ -28,7 +29,7 @@ namespace bop {
using ::operations_research::sat::LinearBooleanProblem;
using ::operations_research::sat::LinearObjective;
BopOptimizerBase::BopOptimizerBase(const std::string& name)
BopOptimizerBase::BopOptimizerBase(absl::string_view name)
: name_(name), stats_(name) {
SCOPED_TIME_STAT(&stats_);
}

View File

@@ -20,6 +20,7 @@
#include <string>
#include <vector>
#include "absl/strings/string_view.h"
#include "absl/synchronization/mutex.h"
#include "ortools/base/basictypes.h"
#include "ortools/base/strong_vector.h"
@@ -44,7 +45,7 @@ class ProblemState;
// are run sequentially or concurrently. See for instance BopRandomLNSOptimizer.
class BopOptimizerBase {
public:
explicit BopOptimizerBase(const std::string& name);
explicit BopOptimizerBase(absl::string_view name);
virtual ~BopOptimizerBase();
// Returns the name given at construction.

View File

@@ -19,6 +19,7 @@
#include <string>
#include <vector>
#include "absl/strings/string_view.h"
#include "ortools/base/strong_vector.h"
#include "ortools/bop/bop_base.h"
#include "ortools/bop/bop_lns.h"
@@ -174,7 +175,7 @@ class OptimizerSelector {
void UpdateOrder();
struct RunInfo {
RunInfo(OptimizerIndex i, const std::string& n)
RunInfo(OptimizerIndex i, absl::string_view n)
: optimizer_index(i),
name(n),
num_successes(0),

View File

@@ -16,6 +16,8 @@
#include <cstdint>
#include <string>
#include "absl/strings/string_view.h"
namespace operations_research {
namespace bop {
@@ -27,7 +29,7 @@ using ::operations_research::sat::LinearObjective;
// BopSolution
//------------------------------------------------------------------------------
BopSolution::BopSolution(const LinearBooleanProblem& problem,
const std::string& name)
absl::string_view name)
: problem_(&problem),
name_(name),
values_(problem.num_variables(), false),

View File

@@ -17,6 +17,7 @@
#include <cstdint>
#include <string>
#include "absl/strings/string_view.h"
#include "ortools/base/strong_vector.h"
#include "ortools/bop/bop_types.h"
#include "ortools/sat/boolean_problem.h"
@@ -34,8 +35,7 @@ namespace bop {
// the feasibility.
class BopSolution {
public:
BopSolution(const sat::LinearBooleanProblem& problem,
const std::string& name);
BopSolution(const sat::LinearBooleanProblem& problem, absl::string_view name);
void SetValue(VariableIndex var, bool value) {
recompute_cost_ = true;
@@ -46,7 +46,7 @@ class BopSolution {
size_t Size() const { return values_.size(); }
bool Value(VariableIndex var) const { return values_[var]; }
const std::string& name() const { return name_; }
void set_name(const std::string& name) { name_ = name; }
void set_name(absl::string_view name) { name_ = name; }
// Returns the objective cost of the solution.
// Note that this code is lazy but not incremental and might run in the

View File

@@ -1044,7 +1044,7 @@ void Model::AddConstraint(absl::string_view id, std::vector<Argument> arguments,
constraints_.push_back(constraint);
}
void Model::AddConstraint(const std::string& id,
void Model::AddConstraint(absl::string_view id,
std::vector<Argument> arguments) {
AddConstraint(id, std::move(arguments), false);
}

View File

@@ -357,7 +357,7 @@ class Model {
// Creates and add a constraint to the model.
void AddConstraint(absl::string_view id, std::vector<Argument> arguments,
bool is_domain);
void AddConstraint(const std::string& id, std::vector<Argument> arguments);
void AddConstraint(absl::string_view id, std::vector<Argument> arguments);
void AddOutput(SolutionOutputSpecs output);
// Set the search annotations and the objective: either simply satisfy the

View File

@@ -0,0 +1,22 @@
# Copyright 2010-2022 Google LLC
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Colab utilities for cp_model.
load("@rules_python//python:defs.bzl", "py_library")
py_library(
name = "visualization",
srcs = ["visualization.py"],
visibility = ["//visibility:public"],
)

373
ortools/sat/colab/README.md Normal file
View File

@@ -0,0 +1,373 @@
# CpModel Colab
Below you'll find three examples of Google's CP-SAT solver.
- The first example solves a [logic puzzle](#hidato)
- The second solves a simple [job shop problem](#jobshop)
- The third example solves a [two machine scheduling problem with shared
resources](#twomachine)
## Local instance
Build and run locally:
```
blaze run -c opt ortools/colab:or_notebook -- --alsologtostderr
```
In your browser go to Open an existing or create
a new notebook. Click Connect button and click CONNECT under **Local runtime**.
After you've connected to the local runtime, cut and paste each of the following
Python programs into a Colab cell. Then run the cell to see the results.
## Hidato Problem {#hidato}
We model the Hidato problem (https://en.wikipedia.org/wiki/Hidato).
```
from google3.util.operations_research.sat.python import cp_model
from google3.util.operations_research.sat.colab import visualization
def BuildPairs(rows, cols):
"""Build closeness pairs for consecutive numbers.
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.
Args:
rows: the number of rows in the grid
cols: the number of columns in the grid
"""
return [(x * cols + y, (x + dx) * cols + (y + dy))
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))]
def BuildPuzzle(problem):
#
# models, a 0 indicates an open cell which number is not yet known.
#
#
puzzle = None
if problem == 1:
# Simple 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]]
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]]
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]]
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]]
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]]
return puzzle
def SolveHidato(puzzle, index):
"""Solve the given hidato table."""
# Create the model.
model = cp_model.CpModel()
r = len(puzzle)
c = len(puzzle[0])
#
# declare variables
#
positions = [model.NewIntVar(0, r * c - 1, 'p[%i]' % i)
for i in range(r * c)]
#
# constraints
#
model.AddAllDifferent(positions)
#
# Fill in the clues
#
for i in range(r):
for j in range(c):
if puzzle[i][j] > 0:
model.Add(positions[puzzle[i][j] - 1] == i * c + j)
# Consecutive numbers must touch each other in the grid.
# We use an allowed assignment constraint to model it.
close_tuples = BuildPairs(r, c)
for k in range(0, r * c - 1):
model.AddAllowedAssignments([positions[k], positions[k + 1]], close_tuples)
#
# solution and search
#
solver = cp_model.CpSolver()
status = solver.Solve(model)
if status == cp_model.FEASIBLE:
output = visualization.SvgWrapper(10, r, 40.0)
for i in range(len(positions)):
val = solver.Value(positions[i])
x = val % c
y = val // c
color = 'white' if puzzle[y][x] == 0 else 'lightgreen'
value = solver.Value(positions[i])
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.Display()
for i in range(1, 7):
SolveHidato(BuildPuzzle(i), i)
```
## Jobshop example {#jobshop}
This example demonstrates jobshop scheduling
(https://en.wikipedia.org/wiki/Job_shop_scheduling)
```python
from google3.util.operations_research.sat.python import cp_model
from google3.util.operations_research.sat.colab import visualization
def JobshopFT06():
"""Solves the ft06 jobshop from the jssp library.
(http://people.brunel.ac.uk/~mastjjb/jeb/orlib/jobshopinfo.html)."""
# Creates the solver.
model = cp_model.CpModel()
machines_count = 6
jobs_count = 6
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]]
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])
# Creates jobs.
all_tasks = {}
for i in all_jobs:
for j in all_machines:
start = model.NewIntVar(0, horizon, 'start_%i_%i' % (i, j))
duration = durations[i][j]
end = model.NewIntVar(0, horizon, 'end_%i_%i' % (i, j))
interval = model.NewIntervalVar(start, duration, end,
'interval_%i_%i' % (i, j))
all_tasks[(i, j)] = (start, end, interval)
# Create disjunctive constraints.
for i in all_machines:
machines_jobs = []
for j in all_jobs:
for k in all_machines:
if machines[j][k] == i:
machines_jobs.append(all_tasks[(j, k)][2])
model.AddNoOverlap(machines_jobs)
# Makespan objective: minimize the total length of the schedule.
obj_var = model.NewIntVar(0, horizon, 'makespan')
model.AddMaxEquality(
obj_var, [all_tasks[(i, machines_count - 1)][1] for i in all_jobs])
model.Minimize(obj_var)
# Precedences inside a job.
for i in all_jobs:
for j in range(0, machines_count - 1):
model.Add(all_tasks[(i, j + 1)][0] >= all_tasks[(i, j)][1])
solver = cp_model.CpSolver()
response = solver.Solve(model)
starts = [[solver.Value(all_tasks[(i, j)][0]) for j in all_machines]
for i in all_jobs]
visualization.DisplayJobshop(starts, durations, machines, 'FT06')
JobshopFT06()
```
## Two Machine Scheduling {#twomachine}
We have a set of jobs to perform (duration, width). We have two parallel
machines that can perform this job. One machine can only perform one job at a
time. At any point in time, the sum of the width of the two active jobs cannot
exceed a max_width.
The objective is to minimize the max end time of all jobs.
```
from google3.util.operations_research.sat.python import cp_model
from google3.util.operations_research.sat.colab import visualization
def TwoMachineScheduling():
model = cp_model.CpModel()
jobs = [[3, 3], # (duration, width)
[2, 5],
[1, 3],
[3, 7],
[7, 3],
[2, 2],
[2, 2],
[5, 5],
[10, 2],
[4, 3],
[2, 6],
[1, 2],
[6, 8],
[4, 5],
[3, 7]]
max_width = 10
horizon = sum(t[0] for t in jobs)
num_jobs = len(jobs)
all_jobs = range(num_jobs)
intervals = []
intervals0 = []
intervals1 = []
performed = []
starts = []
ends = []
demands = []
for i in all_jobs:
# Create main interval (to be used in the cumulative constraint).
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)
starts.append(start)
intervals.append(interval)
ends.append(end)
demands.append(jobs[i][1])
performed_on_m0 = model.NewBoolVar('perform_%i_on_m0' % i)
performed.append(performed_on_m0)
# Create an optional copy of interval to be executed on machine 0.
start0 = model.NewIntVar(
0, horizon, 'start_%i_on_m0' % i)
end0 = model.NewIntVar(
0, horizon, 'end_%i_on_m0' % i)
interval0 = model.NewOptionalIntervalVar(
start0, duration, end0, performed_on_m0, 'interval_%i_on_m0' % i)
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)
intervals1.append(interval1)
# We only propagate the constraint if the task is performed on the machine.
model.Add(start0 == start).OnlyEnforceIf(performed_on_m0)
model.Add(start1 == start).OnlyEnforceIf(performed_on_m0.Not())
# Max width constraint (modeled as a cumulative).
model.AddCumulative(intervals, demands, max_width)
# Choose which machine to perform the jobs on.
model.AddNoOverlap(intervals0)
model.AddNoOverlap(intervals1)
# Objective variable.
makespan = model.NewIntVar(0, horizon, 'makespan')
model.AddMaxEquality(makespan, ends)
model.Minimize(makespan)
# Symmetry breaking.
model.Add(performed[0] == 0)
# Solve model.
solver = cp_model.CpSolver()
solver.Solve(model)
# Output solution.
output = visualization.SvgWrapper(solver.ObjectiveValue(), max_width, 40.0)
output.AddTitle('Makespan = %i' % solver.ObjectiveValue())
color_manager = visualization.ColorManager()
color_manager.SeedRandomColor(0)
for i in all_jobs:
performed_machine = 1 - solver.Value(performed[i])
start = solver.Value(starts[i])
dx = jobs[i][0]
dy = jobs[i][1]
sy = performed_machine * (max_width - dy)
output.AddRectangle(start, sy, dx, dy, color_manager.RandomColor(), 'black',
'j%i' % i)
output.AddXScale()
output.AddYScale()
output.Display()
TwoMachineScheduling()
```

View File

@@ -0,0 +1,450 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "4khkpduAslqk"
},
"source": [
"# CP-SAT Examples\n",
"\n",
"Below you'll find three examples of Google's CP-SAT solver.\n",
"\n",
"- The first example solves a [logic puzzle](#scrollTo=X3kcFue30b_c)\n",
"- The second solves a simple [job shop problem](#scrollTo=l_eCCYDE0q7f)\n",
"- The third example solves a [two machine scheduling problem with shared\n",
" resources](#scrollTo=v6A8SuSe0zTV\u0026line=2\u0026uniqifier=1)\n",
" \n",
"## Runtime and dependencies\n",
"\n",
"To run these examples you will need a customization of the default Colab runtime (go/colab) with features and libraries required for the CP-SAT solver (and some visualization tools).\n",
"\n",
"To build and run this runtime locally execute the following command at the root of a CitC:\n",
"\n",
"```\n",
"blaze run -c opt ortools/colab:or_notebook -- --alsologtostderr\n",
"```\n",
"\n",
"Then, click Connect button and click CONNECT under **Local runtime**.\n",
"\n",
"Finally, run the following cell to import all necessary dependencies. "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "_W7FYL570WBA"
},
"outputs": [],
"source": [
"from google3.util.operations_research.sat.python import cp_model\n",
"from google3.util.operations_research.sat.colab import visualization"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "X3kcFue30b_c"
},
"source": [
"## Hidato Problem\n",
"\n",
"We model the Hidato problem (https://en.wikipedia.org/wiki/Hidato)."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "lYYyoCqbwmj9"
},
"outputs": [],
"source": [
"def BuildPairs(rows, cols):\n",
" \"\"\"Build closeness pairs for consecutive numbers.\n",
"\n",
" Build set of allowed pairs such that two consecutive numbers touch\n",
" each other in the grid.\n",
"\n",
" Returns:\n",
" A list of pairs for allowed consecutive position of numbers.\n",
"\n",
" Args:\n",
" rows: the number of rows in the grid\n",
" cols: the number of columns in the grid\n",
" \"\"\"\n",
" return [(x * cols + y, (x + dx) * cols + (y + dy))\n",
" for x in range(rows) for y in range(cols)\n",
" for dx in (-1, 0, 1) for dy in (-1, 0, 1)\n",
" if (x + dx \u003e= 0 and x + dx \u003c rows and\n",
" y + dy \u003e= 0 and y + dy \u003c cols and (dx != 0 or dy != 0))]\n",
"\n",
"\n",
"def BuildPuzzle(problem):\n",
" #\n",
" # models, a 0 indicates an open cell which number is not yet known.\n",
" #\n",
" #\n",
" puzzle = None\n",
" if problem == 1:\n",
" # Simple problem\n",
" puzzle = [[6, 0, 9],\n",
" [0, 2, 8],\n",
" [1, 0, 0]]\n",
"\n",
" elif problem == 2:\n",
" puzzle = [[0, 44, 41, 0, 0, 0, 0],\n",
" [0, 43, 0, 28, 29, 0, 0],\n",
" [0, 1, 0, 0, 0, 33, 0],\n",
" [0, 2, 25, 4, 34, 0, 36],\n",
" [49, 16, 0, 23, 0, 0, 0],\n",
" [0, 19, 0, 0, 12, 7, 0],\n",
" [0, 0, 0, 14, 0, 0, 0]]\n",
"\n",
" elif problem == 3:\n",
" # Problems from the book:\n",
" # Gyora Bededek: \"Hidato: 2000 Pure Logic Puzzles\"\n",
" # Problem 1 (Practice)\n",
" puzzle = [[0, 0, 20, 0, 0],\n",
" [0, 0, 0, 16, 18],\n",
" [22, 0, 15, 0, 0],\n",
" [23, 0, 1, 14, 11],\n",
" [0, 25, 0, 0, 12]]\n",
"\n",
" elif problem == 4:\n",
" # problem 2 (Practice)\n",
" puzzle = [[0, 0, 0, 0, 14],\n",
" [0, 18, 12, 0, 0],\n",
" [0, 0, 17, 4, 5],\n",
" [0, 0, 7, 0, 0],\n",
" [9, 8, 25, 1, 0]]\n",
"\n",
" elif problem == 5:\n",
" # problem 3 (Beginner)\n",
" puzzle = [[0, 26, 0, 0, 0, 18],\n",
" [0, 0, 27, 0, 0, 19],\n",
" [31, 23, 0, 0, 14, 0],\n",
" [0, 33, 8, 0, 15, 1],\n",
" [0, 0, 0, 5, 0, 0],\n",
" [35, 36, 0, 10, 0, 0]]\n",
" elif problem == 6:\n",
" # Problem 15 (Intermediate)\n",
" puzzle = [[64, 0, 0, 0, 0, 0, 0, 0],\n",
" [1, 63, 0, 59, 15, 57, 53, 0],\n",
" [0, 4, 0, 14, 0, 0, 0, 0],\n",
" [3, 0, 11, 0, 20, 19, 0, 50],\n",
" [0, 0, 0, 0, 22, 0, 48, 40],\n",
" [9, 0, 0, 32, 23, 0, 0, 41],\n",
" [27, 0, 0, 0, 36, 0, 46, 0],\n",
" [28, 30, 0, 35, 0, 0, 0, 0]]\n",
" return puzzle\n",
"\n",
"\n",
"def SolveHidato(puzzle, index):\n",
" \"\"\"Solve the given hidato table.\"\"\"\n",
" # Create the model.\n",
" model = cp_model.CpModel()\n",
"\n",
" r = len(puzzle)\n",
" c = len(puzzle[0])\n",
"\n",
" #\n",
" # declare variables\n",
" #\n",
" positions = [model.NewIntVar(0, r * c - 1, 'p[%i]' % i)\n",
" for i in range(r * c)]\n",
"\n",
" #\n",
" # constraints\n",
" #\n",
" model.AddAllDifferent(positions)\n",
"\n",
" #\n",
" # Fill in the clues\n",
" #\n",
" for i in range(r):\n",
" for j in range(c):\n",
" if puzzle[i][j] \u003e 0:\n",
" model.Add(positions[puzzle[i][j] - 1] == i * c + j)\n",
"\n",
" # Consecutive numbers must touch each other in the grid.\n",
" # We use an allowed assignment constraint to model it.\n",
" close_tuples = BuildPairs(r, c)\n",
" for k in range(0, r * c - 1):\n",
" model.AddAllowedAssignments([positions[k], positions[k + 1]], close_tuples)\n",
"\n",
" #\n",
" # solution and search\n",
" #\n",
"\n",
" solver = cp_model.CpSolver()\n",
" status = solver.Solve(model)\n",
"\n",
" if status == cp_model.FEASIBLE or status == cp_model.OPTIMAL:\n",
" output = visualization.SvgWrapper(10, r, 40.0)\n",
" for i in range(len(positions)):\n",
" val = solver.Value(positions[i])\n",
" x = val % c\n",
" y = val // c\n",
" color = 'white' if puzzle[y][x] == 0 else 'lightgreen'\n",
" value = solver.Value(positions[i])\n",
" output.AddRectangle(x, r - y - 1, 1, 1, color, 'black', str(i + 1))\n",
"\n",
" output.AddTitle('Puzzle %i solved in %f s' % (index, solver.WallTime()))\n",
" output.Display()\n",
"\n",
"\n",
"for i in range(1, 7):\n",
" SolveHidato(BuildPuzzle(i), i)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "l_eCCYDE0q7f"
},
"source": [
"## Jobshop example\n",
"\n",
"This example demonstrates jobshop scheduling\n",
"(https://en.wikipedia.org/wiki/Job_shop_scheduling)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "aBGOY3xWgV-g"
},
"outputs": [],
"source": [
"def JobshopFT06():\n",
" \"\"\"Solves the ft06 jobshop from the jssp library.\n",
" (http://people.brunel.ac.uk/~mastjjb/jeb/orlib/jobshopinfo.html).\"\"\"\n",
"\n",
" # Creates the solver.\n",
" model = cp_model.CpModel()\n",
"\n",
" machines_count = 6\n",
" jobs_count = 6\n",
" all_machines = range(0, machines_count)\n",
" all_jobs = range(0, jobs_count)\n",
"\n",
" durations = [[1, 3, 6, 7, 3, 6],\n",
" [8, 5, 10, 10, 10, 4],\n",
" [5, 4, 8, 9, 1, 7],\n",
" [5, 5, 5, 3, 8, 9],\n",
" [9, 3, 5, 4, 3, 1],\n",
" [3, 3, 9, 10, 4, 1]]\n",
"\n",
" machines = [[2, 0, 1, 3, 5, 4],\n",
" [1, 2, 4, 5, 0, 3],\n",
" [2, 3, 5, 0, 1, 4],\n",
" [1, 0, 2, 3, 4, 5],\n",
" [2, 1, 4, 5, 0, 3],\n",
" [1, 3, 5, 0, 4, 2]]\n",
"\n",
" # Computes horizon dynamically.\n",
" horizon = sum([sum(durations[i]) for i in all_jobs])\n",
"\n",
" # Creates jobs.\n",
" all_tasks = {}\n",
" for i in all_jobs:\n",
" for j in all_machines:\n",
" start = model.NewIntVar(0, horizon, 'start_%i_%i' % (i, j))\n",
" duration = durations[i][j]\n",
" end = model.NewIntVar(0, horizon, 'end_%i_%i' % (i, j))\n",
" interval = model.NewIntervalVar(start, duration, end,\n",
" 'interval_%i_%i' % (i, j))\n",
" all_tasks[(i, j)] = (start, end, interval)\n",
"\n",
" # Create disjunctive constraints.\n",
" for i in all_machines:\n",
" machines_jobs = []\n",
" for j in all_jobs:\n",
" for k in all_machines:\n",
" if machines[j][k] == i:\n",
" machines_jobs.append(all_tasks[(j, k)][2])\n",
" model.AddNoOverlap(machines_jobs)\n",
"\n",
" # Makespan objective: minimize the total length of the schedule.\n",
" obj_var = model.NewIntVar(0, horizon, 'makespan')\n",
" model.AddMaxEquality(\n",
" obj_var, [all_tasks[(i, machines_count - 1)][1] for i in all_jobs])\n",
" model.Minimize(obj_var)\n",
"\n",
" # Precedences inside a job.\n",
" for i in all_jobs:\n",
" for j in range(0, machines_count - 1):\n",
" model.Add(all_tasks[(i, j + 1)][0] \u003e= all_tasks[(i, j)][1])\n",
"\n",
" solver = cp_model.CpSolver()\n",
" response = solver.Solve(model)\n",
" starts = [[solver.Value(all_tasks[(i, j)][0]) for j in all_machines]\n",
" for i in all_jobs]\n",
" visualization.DisplayJobshop(starts, durations, machines, 'FT06')\n",
"\n",
"\n",
"JobshopFT06()"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "v6A8SuSe0zTV"
},
"source": [
"## Two Machine Scheduling\n",
"\n",
"We have a set of jobs to perform (duration, width). We have two parallel\n",
"machines that can perform this job. One machine can only perform one job at a\n",
"time. At any point in time, the sum of the width of the two active jobs cannot\n",
"exceed a max_width.\n",
"\n",
"The objective is to minimize the max end time of all jobs."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "aJ4tCfc4z8DU"
},
"outputs": [],
"source": [
"def TwoMachineScheduling():\n",
" model = cp_model.CpModel()\n",
"\n",
" jobs = [[3, 3], # (duration, width)\n",
" [2, 5],\n",
" [1, 3],\n",
" [3, 7],\n",
" [7, 3],\n",
" [2, 2],\n",
" [2, 2],\n",
" [5, 5],\n",
" [10, 2],\n",
" [4, 3],\n",
" [2, 6],\n",
" [1, 2],\n",
" [6, 8],\n",
" [4, 5],\n",
" [3, 7]]\n",
"\n",
" max_width = 10\n",
"\n",
" horizon = sum(t[0] for t in jobs)\n",
" num_jobs = len(jobs)\n",
" all_jobs = range(num_jobs)\n",
"\n",
" intervals = []\n",
" intervals0 = []\n",
" intervals1 = []\n",
" performed = []\n",
" starts = []\n",
" ends = []\n",
" demands = []\n",
"\n",
" for i in all_jobs:\n",
" # Create main interval (to be used in the cumulative constraint).\n",
" start = model.NewIntVar(0, horizon, 'start_%i' % i)\n",
" duration = jobs[i][0]\n",
" end = model.NewIntVar(0, horizon, 'end_%i' % i)\n",
" interval = model.NewIntervalVar(start, duration, end, 'interval_%i' % i)\n",
" starts.append(start)\n",
" intervals.append(interval)\n",
" ends.append(end)\n",
" demands.append(jobs[i][1])\n",
"\n",
" performed_on_m0 = model.NewBoolVar('perform_%i_on_m0' % i)\n",
" performed.append(performed_on_m0)\n",
"\n",
" # Create an optional copy of interval to be executed on machine 0.\n",
" start0 = model.NewIntVar(\n",
" 0, horizon, 'start_%i_on_m0' % i)\n",
" end0 = model.NewIntVar(\n",
" 0, horizon, 'end_%i_on_m0' % i)\n",
" interval0 = model.NewOptionalIntervalVar(\n",
" start0, duration, end0, performed_on_m0, 'interval_%i_on_m0' % i)\n",
" intervals0.append(interval0)\n",
"\n",
" # Create an optional copy of interval to be executed on machine 1.\n",
" start1 = model.NewIntVar(\n",
" 0, horizon, 'start_%i_on_m1' % i)\n",
" end1 = model.NewIntVar(\n",
" 0, horizon, 'end_%i_on_m1' % i)\n",
" interval1 = model.NewOptionalIntervalVar(\n",
" start1, duration, end1, performed_on_m0.Not(), 'interval_%i_on_m1' % i)\n",
" intervals1.append(interval1)\n",
"\n",
" # We only propagate the constraint if the task is performed on the machine.\n",
" model.Add(start0 == start).OnlyEnforceIf(performed_on_m0)\n",
" model.Add(start1 == start).OnlyEnforceIf(performed_on_m0.Not())\n",
"\n",
" # Max width constraint (modeled as a cumulative).\n",
" model.AddCumulative(intervals, demands, max_width)\n",
"\n",
" # Choose which machine to perform the jobs on.\n",
" model.AddNoOverlap(intervals0)\n",
" model.AddNoOverlap(intervals1)\n",
"\n",
" # Objective variable.\n",
" makespan = model.NewIntVar(0, horizon, 'makespan')\n",
" model.AddMaxEquality(makespan, ends)\n",
" model.Minimize(makespan)\n",
"\n",
" # Symmetry breaking.\n",
" model.Add(performed[0] == 0)\n",
"\n",
" # Solve model.\n",
" solver = cp_model.CpSolver()\n",
" solver.Solve(model)\n",
"\n",
" # Output solution.\n",
" output = visualization.SvgWrapper(solver.ObjectiveValue(), max_width, 40.0)\n",
" output.AddTitle('Makespan = %i' % solver.ObjectiveValue())\n",
" color_manager = visualization.ColorManager()\n",
" color_manager.SeedRandomColor(0)\n",
" for i in all_jobs:\n",
" performed_machine = 1 - solver.Value(performed[i])\n",
" start = solver.Value(starts[i])\n",
" dx = jobs[i][0]\n",
" dy = jobs[i][1]\n",
" sy = performed_machine * (max_width - dy)\n",
" output.AddRectangle(start, sy, dx, dy, color_manager.RandomColor(), 'black',\n",
" 'j%i' % i)\n",
"\n",
" output.AddXScale()\n",
" output.AddYScale()\n",
" output.Display()\n",
"\n",
"\n",
"TwoMachineScheduling()"
]
}
],
"metadata": {
"colab": {
"collapsed_sections": [],
"last_runtime": {
"build_target": "",
"kind": "local"
},
"name": "cp-sat1.ipynb",
"provenance": [
{
"file_id": "1yJJLir0ITXD8I-SyJ06ZdN1x12fEsTlv",
"timestamp": 1571253284638
}
],
"toc_visible": true
},
"kernelspec": {
"display_name": "Python 3",
"name": "python3"
}
},
"nbformat": 4,
"nbformat_minor": 0
}

View File

@@ -88,9 +88,3 @@ py_test(
requirement("absl-py"),
],
)
py_library(
name = "visualization",
srcs = ["visualization.py"],
visibility = ["//visibility:public"],
)

View File

@@ -47,6 +47,8 @@ code_sample_cc_py(name = "literal_sample_sat")
code_sample_cc_py(name = "multiple_knapsack_sat")
code_sample_cc_py(name = "non_linear_sat")
code_sample_cc_py(name = "nqueens_sat")
code_sample_cc_py(name = "nurses_sat")

View File

@@ -18,7 +18,6 @@ import com.google.ortools.sat.CpModel;
import com.google.ortools.sat.CpSolver;
import com.google.ortools.sat.CpSolverStatus;
import com.google.ortools.sat.IntVar;
import com.google.ortools.sat.IntervalVar;
import com.google.ortools.sat.LinearExpr;
public class NonLinearSat {
@@ -30,9 +29,7 @@ public class NonLinearSat {
IntVar x = model.newIntVar(0, perimeter, "x");
IntVar y = model.newIntVar(0, perimeter, "y");
model.addEquality(
LinearExpr.weightedSum(new IntVar[] {x, y}, new long[] {2, 2}),
perimeter);
model.addEquality(LinearExpr.weightedSum(new IntVar[] {x, y}, new long[] {2, 2}), perimeter);
IntVar area = model.newIntVar(0, perimeter * perimeter, "s");
model.addMultiplicationEquality(area, x, y);
@@ -46,8 +43,7 @@ public class NonLinearSat {
System.out.println("x = " + solver.value(x));
System.out.println("y = " + solver.value(y));
System.out.println("s = " + solver.value(area));
}
else
} else
System.out.println("No solution found");
}
}

View File

@@ -15,6 +15,8 @@
// using AddMultiplicationEquality
#include <stdlib.h>
#include "ortools/base/logging.h"
#include "ortools/sat/cp_model.h"
#include "ortools/sat/cp_model_solver.h"
@@ -22,34 +24,34 @@ namespace operations_research {
namespace sat {
void NonLinearSatProgram() {
CpModelBuilder cp_model;
CpModelBuilder cp_model;
const int perimeter = 20;
const Domain sides_domain(0, perimeter);
const IntVar x = cp_model.NewIntVar(sides_domain);
const IntVar y = cp_model.NewIntVar(sides_domain);
const int perimeter = 20;
const Domain sides_domain(0, perimeter);
cp_model.AddEquality(2 * (x + y), perimeter);
const IntVar x = cp_model.NewIntVar(sides_domain);
const IntVar y = cp_model.NewIntVar(sides_domain);
const Domain area_domain(0, perimeter * perimeter);
const IntVar area = cp_model.NewIntVar(area_domain);
cp_model.AddEquality(2 * (x + y), perimeter);
cp_model.AddMultiplicationEquality(area, x, y);
const Domain area_domain(0, perimeter * perimeter);
const IntVar area = cp_model.NewIntVar(area_domain);
cp_model.Maximize(area);
cp_model.AddMultiplicationEquality(area, x, y);
const CpSolverResponse response = Solve(cp_model.Build());
if (response.status() == CpSolverStatus::OPTIMAL ||
response.status() == CpSolverStatus::FEASIBLE) {
// Get the value of x in the solution.
LOG(INFO) << "x = " << SolutionIntegerValue(response, x);
LOG(INFO) << "y = " << SolutionIntegerValue(response, y);
LOG(INFO) << "s = " << SolutionIntegerValue(response, area);
} else {
LOG(INFO) << "No solution found.";
}
cp_model.Maximize(area);
const CpSolverResponse response = Solve(cp_model.Build());
if (response.status() == CpSolverStatus::OPTIMAL ||
response.status() == CpSolverStatus::FEASIBLE) {
// Get the value of x in the solution.
LOG(INFO) << "x = " << SolutionIntegerValue(response, x);
LOG(INFO) << "y = " << SolutionIntegerValue(response, y);
LOG(INFO) << "s = " << SolutionIntegerValue(response, area);
} else {
LOG(INFO) << "No solution found.";
}
}
} // namespace sat

View File

@@ -18,16 +18,16 @@
from ortools.sat.python import cp_model
def NonLinearSat():
def non_linear_sat():
perimeter = 20
model = cp_model.CpModel()
x = model.NewIntVar(0, perimeter, "x")
y = model.NewIntVar(0, perimeter, "y")
x = model.NewIntVar(0, perimeter, 'x')
y = model.NewIntVar(0, perimeter, 'y')
model.Add(2 * (x + y) == perimeter)
area = model.NewIntVar(0, perimeter * perimeter, "s")
area = model.NewIntVar(0, perimeter * perimeter, 's')
model.AddMultiplicationEquality(area, x, y)
model.Maximize(area)
@@ -44,4 +44,4 @@ def NonLinearSat():
print('No solution found.')
NonLinearSat()
non_linear_sat()