Skip to content

Commit f24d33e

Browse files
committed
new examples
1 parent c71b9a0 commit f24d33e

File tree

2 files changed

+239
-0
lines changed

2 files changed

+239
-0
lines changed

examples/python/pell_equation_sat.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
#!/usr/bin/env python3
2+
# Copyright 2010-2024 Google LLC
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Solves Pell's equation x^2 - coeff * y^2 = 1."""
16+
17+
from collections.abc import Sequence
18+
19+
from absl import app
20+
from absl import flags
21+
from ortools.sat.python import cp_model
22+
23+
24+
_COEFF = flags.DEFINE_integer("coeff", 1, "The Pell equation coefficient.")
25+
_MAX_VALUE = flags.DEFINE_integer("max_value", 500000000, "The maximum value.")
26+
27+
28+
def solve_pell(coeff: int, max_value: int):
29+
"""Solves Pell's equation x^2 - coeff * y^2 = 1."""
30+
model = cp_model.CpModel()
31+
32+
x = model.new_int_var(1, max_value, "x")
33+
y = model.new_int_var(1, max_value, "y")
34+
35+
# Pell's equation:
36+
x_square = model.new_int_var(1, max_value * max_value, "x_square")
37+
y_square = model.new_int_var(1, max_value * max_value, "y_square")
38+
model.add_multiplication_equality(x_square, x, x)
39+
model.add_multiplication_equality(y_square, y, y)
40+
model.add(x_square - coeff * y_square == 1)
41+
42+
model.add_decision_strategy(
43+
[x, y], cp_model.CHOOSE_MIN_DOMAIN_SIZE, cp_model.SELECT_MIN_VALUE
44+
)
45+
46+
solver = cp_model.CpSolver()
47+
solver.parameters.num_workers = 12
48+
solver.parameters.log_search_progress = True
49+
solver.parameters.cp_model_presolve = True
50+
solver.parameters.cp_model_probing_level = 0
51+
52+
result = solver.solve(model)
53+
if result == cp_model.OPTIMAL:
54+
print(f"x={solver.value(x)} y={solver.value(y)} coeff={coeff}")
55+
if solver.value(x) ** 2 - coeff * (solver.value(y) ** 2) != 1:
56+
raise ValueError("Pell equation not satisfied.")
57+
58+
59+
def main(argv: Sequence[str]) -> None:
60+
if len(argv) > 1:
61+
raise app.UsageError("Too many command-line arguments.")
62+
solve_pell(_COEFF.value, _MAX_VALUE.value)
63+
64+
65+
if __name__ == "__main__":
66+
app.run(main)
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
#!/usr/bin/env python3
2+
# Copyright 2010-2024 Google LLC
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Solves a test scheduling problem.
16+
17+
Tests must be run by an operator. Tests have a duration and a power consumption.
18+
19+
Operators draw power from power supplies. The mapping between operators and
20+
power supplies is given.
21+
22+
Power supplies have a maximum power they can deliver.
23+
24+
Can we schedule the tests so that the power consumption of each power supply is
25+
always below its maximum power, and the total makespan is minimized?
26+
"""
27+
28+
from collections.abc import Sequence
29+
import io
30+
from typing import Dict, Tuple
31+
32+
from absl import app
33+
from absl import flags
34+
import pandas as pd
35+
36+
from google.protobuf import text_format
37+
from ortools.sat.python import cp_model
38+
39+
40+
_PARAMS = flags.DEFINE_string(
41+
"params",
42+
"num_search_workers:16,log_search_progress:true,max_time_in_seconds:45",
43+
"Sat solver parameters.",
44+
)
45+
46+
47+
def build_data() -> tuple[pd.DataFrame, pd.Series, pd.Series]:
48+
"""Build the data frame."""
49+
tests_str = """
50+
Name Operator TestTime AveragePower
51+
T1 O1 300 200
52+
T2 O1 150 40
53+
T3 O2 100 65
54+
T4 O2 250 150
55+
T5 O3 210 140
56+
"""
57+
58+
operators_str = """
59+
Operator Supply
60+
O1 S1
61+
O2 S2
62+
O3 S2
63+
"""
64+
65+
supplies_str = """
66+
Supply MaxAllowedPower
67+
S1 230
68+
S2 210
69+
"""
70+
71+
tests_data = pd.read_table(io.StringIO(tests_str), sep=r"\s+")
72+
operators_data = pd.read_table(io.StringIO(operators_str), sep=r"\s+")
73+
supplies_data = pd.read_table(io.StringIO(supplies_str), sep=r"\s+")
74+
75+
return (tests_data, operators_data, supplies_data)
76+
77+
78+
def solve(
79+
tests_data: pd.DataFrame, operator_data: pd.Series, supplies_data: pd.Series
80+
) -> None:
81+
"""Solve the scheduling of tests problem."""
82+
83+
# Parses data.
84+
operator_to_supply: Dict[str, str] = {}
85+
for _, row in operator_data.iterrows():
86+
operator_to_supply[row["Operator"]] = row["Supply"]
87+
88+
supply_to_max_power: Dict[str, int] = {}
89+
for _, row in supplies_data.iterrows():
90+
supply_to_max_power[row["Supply"]] = row["MaxAllowedPower"]
91+
92+
horizon = tests_data["TestTime"].sum()
93+
94+
# OR-Tools model.
95+
model = cp_model.CpModel()
96+
97+
# Create containers.
98+
tests_per_supply: Dict[str, Tuple[list[cp_model.IntervalVar], list[int]]] = {}
99+
test_supply: Dict[str, str] = {}
100+
test_starts: Dict[str, cp_model.IntVar] = {}
101+
test_durations: Dict[str, int] = {}
102+
test_powers: Dict[str, int] = {}
103+
all_ends = []
104+
105+
# Creates intervals.
106+
for _, row in tests_data.iterrows():
107+
name: str = row["Name"]
108+
operator: str = row["Operator"]
109+
test_time: int = row["TestTime"]
110+
average_power: int = row["AveragePower"]
111+
supply: str = operator_to_supply[operator]
112+
113+
start = model.new_int_var(0, horizon - test_time, f"start_{name}")
114+
interval = model.new_fixed_size_interval_var(
115+
start, test_time, f"interval_{name}"
116+
)
117+
118+
# Bookkeeping.
119+
test_starts[name] = start
120+
test_durations[name] = test_time
121+
test_powers[name] = average_power
122+
test_supply[name] = supply
123+
if supply not in tests_per_supply.keys():
124+
tests_per_supply[supply] = ([], [])
125+
tests_per_supply[supply][0].append(interval)
126+
tests_per_supply[supply][1].append(average_power)
127+
all_ends.append(start + test_time)
128+
129+
# Create supply cumulative constraints.
130+
for supply, (intervals, demands) in tests_per_supply.items():
131+
model.add_cumulative(intervals, demands, supply_to_max_power[supply])
132+
133+
# Objective.
134+
makespan = model.new_int_var(0, horizon, "makespan")
135+
for end in all_ends:
136+
model.add(makespan >= end)
137+
model.minimize(makespan)
138+
139+
# Solve model.
140+
solver = cp_model.CpSolver()
141+
if _PARAMS.value:
142+
text_format.Parse(_PARAMS.value, solver.parameters)
143+
status = solver.solve(model)
144+
145+
# Report solution.
146+
if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:
147+
print(f"Makespan = {solver.value(makespan)}")
148+
for name, start in test_starts.items():
149+
print(
150+
f"{name}: start:{solver.value(start)} duration:{test_durations[name]}"
151+
f" power:{test_powers[name]} on supply {test_supply[name]}"
152+
)
153+
154+
155+
def main(argv: Sequence[str]) -> None:
156+
if len(argv) > 1:
157+
raise app.UsageError("Too many command-line arguments.")
158+
159+
tests_data, operators_data, supplies_data = build_data()
160+
print("Tests data")
161+
print(tests_data)
162+
print()
163+
print("Operators data")
164+
print(operators_data)
165+
print()
166+
print("Supplies data")
167+
print(supplies_data)
168+
169+
solve(tests_data, operators_data, supplies_data)
170+
171+
172+
if __name__ == "__main__":
173+
app.run(main)

0 commit comments

Comments
 (0)