Skip to content

Commit

Permalink
Merge branch 'feature/xpress_only' into feature/xpress_only_RTE
Browse files Browse the repository at this point in the history
# Conflicts:
#	.github/workflows/centos.yml
#	.github/workflows/oracle.yml
#	.github/workflows/ubuntu.yml
#	examples/java/LinearProgramming.java
#	examples/python/linear_programming.py
#	ortools/linear_solver/xpress_interface.cc
#	ortools/linear_solver/xpress_interface_test.cc
#	ortools/xpress/parse_header_xpress.py
  • Loading branch information
sgatto committed Nov 17, 2023
2 parents 17ed6aa + 2505eab commit e7d4e26
Show file tree
Hide file tree
Showing 111 changed files with 4,629 additions and 3,485 deletions.
2 changes: 1 addition & 1 deletion examples/java/LinearProgramming.java
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,6 @@ public static void main(String[] args) throws Exception {
System.out.println("---- Linear programming example with CLP ----");
runLinearProgrammingExample("CLP", false);
System.out.println("---- Linear programming example with XPRESS ----");
runLinearProgrammingExample("XPRESS", false);
runLinearProgrammingExample("XPRESS_LP", false);
}
}
47 changes: 29 additions & 18 deletions examples/python/appointments.py
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,19 @@ def __init__(self, variables):
self.__variables = variables
self.__collect = []

def on_solution_callback(self):
def on_solution_callback(self) -> None:
"""Collect a new combination."""
combination = [self.Value(v) for v in self.__variables]
combination = [self.value(v) for v in self.__variables]
self.__collect.append(combination)

def combinations(self):
def combinations(self) -> list[list[int]]:
"""Returns all collected combinations."""
return self.__collect


def EnumerateAllKnapsacksWithRepetition(item_sizes, total_size_min, total_size_max):
def EnumerateAllKnapsacksWithRepetition(
item_sizes: list[int], total_size_min: int, total_size_max: int
) -> list[list[int]]:
"""Enumerate all possible knapsacks with total size in the given range.
Args:
Expand All @@ -68,22 +70,26 @@ def EnumerateAllKnapsacksWithRepetition(item_sizes, total_size_min, total_size_m
nonnegative integer: the number of times we put item #K in the knapsack.
"""
model = cp_model.CpModel()
variables = [model.NewIntVar(0, total_size_max // size, "") for size in item_sizes]
variables = [
model.new_int_var(0, total_size_max // size, "") for size in item_sizes
]
load = sum(variables[i] * size for i, size in enumerate(item_sizes))
model.AddLinearConstraint(load, total_size_min, total_size_max)
model.add_linear_constraint(load, total_size_min, total_size_max)

solver = cp_model.CpSolver()
solution_collector = AllSolutionCollector(variables)
# Enumerate all solutions.
solver.parameters.enumerate_all_solutions = True
# Solve
solver.Solve(model, solution_collector)
# solve
solver.solve(model, solution_collector)
return solution_collector.combinations()


def AggregateItemCollectionsOptimally(
item_collections, max_num_collections, ideal_item_ratios
):
item_collections: list[list[int]],
max_num_collections: int,
ideal_item_ratios: list[float],
) -> list[int]:
"""Selects a set (with repetition) of combination of items optimally.
Given a set of collections of N possible items (in each collection, an item
Expand Down Expand Up @@ -173,7 +179,9 @@ def AggregateItemCollectionsOptimally(
return []


def GetOptimalSchedule(demand):
def GetOptimalSchedule(
demand: list[tuple[float, str, int]]
) -> list[tuple[int, list[tuple[int, str]]]]:
"""Computes the optimal schedule for the installation input.
Args:
Expand All @@ -186,7 +194,9 @@ def GetOptimalSchedule(demand):
The same output type as EnumerateAllKnapsacksWithRepetition.
"""
combinations = EnumerateAllKnapsacksWithRepetition(
[a[2] + _COMMUTE_TIME.value for a in demand], _LOAD_MIN.value, _LOAD_MAX.value
[a[2] + _COMMUTE_TIME.value for a in demand],
_LOAD_MIN.value,
_LOAD_MAX.value,
)
print(
(
Expand All @@ -199,14 +209,14 @@ def GetOptimalSchedule(demand):
combinations, _NUM_WORKERS.value, [a[0] / 100.0 for a in demand]
)
output = []
for i in range(len(selection)):
if selection[i] != 0:
for i, s in enumerate(selection):
if s != 0:
output.append(
(
selection[i],
s,
[
(combinations[i][t], demand[t][1])
for t in range(len(demand))
(combinations[i][t], d[1])
for t, d in enumerate(demand)
if combinations[i][t] != 0
],
)
Expand Down Expand Up @@ -252,7 +262,8 @@ def main(_):
per_type = installed_per_type[name]
if installed != 0:
print(
f" {per_type} ({per_type * 100.0 / installed}%) installations of type {name} planned"
f" {per_type} ({per_type * 100.0 / installed}%) installations of"
f" type {name} planned"
)
else:
print(f" {per_type} installations of type {name} planned")
Expand Down
35 changes: 18 additions & 17 deletions examples/python/assignment_with_constraints_sat.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@
# See the License for the specific language governing permissions and
# limitations under the License.

"""Solve an assignment problem with combination constraints on workers."""
"""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."""
"""solve the assignment problem."""
# Data.
cost = [
[90, 76, 75, 70, 50, 74],
Expand Down Expand Up @@ -73,54 +73,55 @@ def solve_assignment():
model = cp_model.CpModel()
# Variables
selected = [
[model.NewBoolVar("x[%i,%i]" % (i, j)) for j in all_tasks] for i in all_workers
[model.new_bool_var("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]
works = [model.new_bool_var("works[%i]" % i) for i in all_workers]

# Constraints

# Link selected and workers.
for i in range(num_workers):
model.AddMaxEquality(works[i], selected[i])
model.add_max_equality(works[i], selected[i])

# Each task is assigned to at least one worker.
for j in all_tasks:
model.Add(sum(selected[i][j] for i in all_workers) >= 1)
model.add(sum(selected[i][j] for i in all_workers) >= 1)

# Total task size for each worker is at most total_size_max
for i in all_workers:
model.Add(sum(sizes[j] * selected[i][j] for j in all_tasks) <= total_size_max)
model.add(sum(sizes[j] * selected[i][j] for j in all_tasks) <= total_size_max)

# Group constraints.
model.AddAllowedAssignments([works[0], works[1], works[2], works[3]], group1)
model.AddAllowedAssignments([works[4], works[5], works[6], works[7]], group2)
model.AddAllowedAssignments([works[8], works[9], works[10], works[11]], group3)
model.add_allowed_assignments([works[0], works[1], works[2], works[3]], group1)
model.add_allowed_assignments([works[4], works[5], works[6], works[7]], group2)
model.add_allowed_assignments([works[8], works[9], works[10], works[11]], group3)

# Objective
model.Minimize(
model.minimize(
sum(selected[i][j] * cost[i][j] for j in all_tasks for i in all_workers)
)

# Solve and output solution.
solver = cp_model.CpSolver()
status = solver.Solve(model)
status = solver.solve(model)

if status == cp_model.OPTIMAL:
print("Total cost = %i" % solver.ObjectiveValue())
print("Total cost = %i" % solver.objective_value)
print()
for i in all_workers:
for j in all_tasks:
if solver.BooleanValue(selected[i][j]):
if solver.boolean_value(selected[i][j]):
print(
"Worker ", i, " assigned to task ", j, " Cost = ", cost[i][j]
)

print()

print("Statistics")
print(" - conflicts : %i" % solver.NumConflicts())
print(" - branches : %i" % solver.NumBranches())
print(" - wall time : %f s" % solver.WallTime())
print(" - conflicts : %i" % solver.num_conflicts)
print(" - branches : %i" % solver.num_branches)
print(" - wall time : %f s" % solver.wall_time)


def main(argv: Sequence[str]) -> None:
Expand Down
42 changes: 21 additions & 21 deletions examples/python/balance_group_sat.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,14 @@ def on_solution_callback(self):
print("Solution %i" % self.__solution_count)
self.__solution_count += 1

print(" objective value = %i" % self.ObjectiveValue())
print(" objective value = %i" % self.objective_value)
groups = {}
sums = {}
for g in self.__all_groups:
groups[g] = []
sums[g] = 0
for item in self.__all_items:
if self.BooleanValue(self.__item_in_group[(item, g)]):
if self.boolean_value(self.__item_in_group[(item, g)]):
groups[g].append(item)
sums[g] += self.__values[item]

Expand Down Expand Up @@ -77,7 +77,7 @@ def main(argv: Sequence[str]) -> None:
all_items = range(num_items)
all_colors = range(num_colors)

# Values for each items.
# values for each items.
values = [1 + i + (i * i // 200) for i in all_items]
# Color for each item (simple modulo).
colors = [i % num_colors for i in all_items]
Expand Down Expand Up @@ -108,26 +108,26 @@ def main(argv: Sequence[str]) -> None:
item_in_group = {}
for i in all_items:
for g in all_groups:
item_in_group[(i, g)] = model.NewBoolVar("item %d in group %d" % (i, g))
item_in_group[(i, g)] = model.new_bool_var("item %d in group %d" % (i, g))

# Each group must have the same size.
for g in all_groups:
model.Add(sum(item_in_group[(i, g)] for i in all_items) == num_items_per_group)
model.add(sum(item_in_group[(i, g)] for i in all_items) == num_items_per_group)

# One item must belong to exactly one group.
for i in all_items:
model.Add(sum(item_in_group[(i, g)] for g in all_groups) == 1)
model.add(sum(item_in_group[(i, g)] for g in all_groups) == 1)

# The deviation of the sum of each items in a group against the average.
e = model.NewIntVar(0, 550, "epsilon")
e = model.new_int_var(0, 550, "epsilon")

# Constrain the sum of values in one group around the average sum per group.
for g in all_groups:
model.Add(
model.add(
sum(item_in_group[(i, g)] * values[i] for i in all_items)
<= average_sum_per_group + e
)
model.Add(
model.add(
sum(item_in_group[(i, g)] * values[i] for i in all_items)
>= average_sum_per_group - e
)
Expand All @@ -136,50 +136,50 @@ def main(argv: Sequence[str]) -> None:
color_in_group = {}
for g in all_groups:
for c in all_colors:
color_in_group[(c, g)] = model.NewBoolVar(
color_in_group[(c, g)] = model.new_bool_var(
"color %d is in group %d" % (c, g)
)

# Item is in a group implies its color is in that group.
for i in all_items:
for g in all_groups:
model.AddImplication(item_in_group[(i, g)], color_in_group[(colors[i], g)])
model.add_implication(item_in_group[(i, g)], color_in_group[(colors[i], g)])

# If a color is in a group, it must contains at least
# min_items_of_same_color_per_group items from that color.
for c in all_colors:
for g in all_groups:
literal = color_in_group[(c, g)]
model.Add(
model.add(
sum(item_in_group[(i, g)] for i in items_per_color[c])
>= min_items_of_same_color_per_group
).OnlyEnforceIf(literal)
).only_enforce_if(literal)

# Compute the maximum number of colors in a group.
max_color = num_items_per_group // min_items_of_same_color_per_group

# Redundant constraint, it helps with solving time.
if max_color < num_colors:
for g in all_groups:
model.Add(sum(color_in_group[(c, g)] for c in all_colors) <= max_color)
model.add(sum(color_in_group[(c, g)] for c in all_colors) <= max_color)

# Minimize epsilon
model.Minimize(e)
# minimize epsilon
model.minimize(e)

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)
status = solver.solve(model, solution_printer)

if status == cp_model.OPTIMAL:
print("Optimal epsilon: %i" % solver.ObjectiveValue())
print("Optimal epsilon: %i" % solver.objective_value)
print("Statistics")
print(" - conflicts : %i" % solver.NumConflicts())
print(" - branches : %i" % solver.NumBranches())
print(" - wall time : %f s" % solver.WallTime())
print(" - conflicts : %i" % solver.num_conflicts)
print(" - branches : %i" % solver.num_branches)
print(" - wall time : %f s" % solver.wall_time)
else:
print("No solution found")

Expand Down
Loading

0 comments on commit e7d4e26

Please sign in to comment.