* remove python script * remove RTE actions * fix test_xpress_interface.cc * remove callback_xpress.py * revert writing colnames and rownames * accept suggestion from Mizux * clean * change cmake/README.md * try fix build bazel * try fix build bazel add MPSWriteError.h * xpress tests gracefully exit if Xpress not found * add integer and linear programming test for dotnet python and java * remove MPSWriteError * try fix Window build * remove useless line from CMakeLists.txt * try fix test under windows * reformat * use XPRESS_LP instead of XPRESS for linear programming examples * tools: add --platform arg when possible make script more resilient/cross-platform * [CP-SAT] convert to PEP8 convention * use XPRSmipoptimize and XPRSlpoptimize instead of XPRSminim and XPRSmaxim (#114) * use XPRSmipoptimize and XPRSlpoptimize instead of XPRSminim and XPRSmaxim * clean xpress/environment files * accept changes: empty char* parameter for XPRS*optimize * Add test on number iterations with LP basis * fix gtests flags * refactor * suggestions by @flomnes * remove unwanted files --------- Co-authored-by: Andrea Sgattoni <andrea.sgattoni@rte-france.com> Co-authored-by: Laurent Perron <lperron@google.com> Co-authored-by: Corentin Le Molgat <corentinl@google.com> Co-authored-by: Andrea Sgattoni <andrea.sgattoni@gmail.com>
161 lines
5.4 KiB
Python
161 lines
5.4 KiB
Python
#!/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.
|
|
|
|
"""Excape the maze while collecting treasures in order.
|
|
|
|
The path must begin at the 'start' position, finish at the 'end' position,
|
|
visit all boxes in order, and walk on each block in a 4x4x4 map exactly once.
|
|
|
|
Admissible moves are one step in one of the 6 directions:
|
|
x+, x-, y+, y-, z+(up), z-(down)
|
|
"""
|
|
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
|
|
|
|
_OUTPUT_PROTO = flags.DEFINE_string(
|
|
"output_proto", "", "Output file to write the cp_model proto to."
|
|
)
|
|
_PARAMS = flags.DEFINE_string(
|
|
"params", "num_search_workers:8,log_search_progress:true", "Sat solver parameters."
|
|
)
|
|
|
|
|
|
def add_neighbor(size, x, y, z, dx, dy, dz, model, index_map, position_to_rank, arcs):
|
|
"""Checks if the neighbor is valid, and adds it to the model."""
|
|
if (
|
|
x + dx < 0
|
|
or x + dx >= size
|
|
or y + dy < 0
|
|
or y + dy >= size
|
|
or z + dz < 0
|
|
or z + dz >= size
|
|
):
|
|
return
|
|
before_index = index_map[(x, y, z)]
|
|
before_rank = position_to_rank[(x, y, z)]
|
|
after_index = index_map[(x + dx, y + dy, z + dz)]
|
|
after_rank = position_to_rank[(x + dx, y + dy, z + dz)]
|
|
move_literal = model.new_bool_var("")
|
|
model.add(after_rank == before_rank + 1).only_enforce_if(move_literal)
|
|
arcs.append((before_index, after_index, move_literal))
|
|
|
|
|
|
def escape_the_maze(params, output_proto):
|
|
"""Escapes the maze."""
|
|
size = 4
|
|
boxes = [(0, 1, 0), (2, 0, 1), (1, 3, 1), (3, 1, 3)]
|
|
start = (3, 3, 0)
|
|
end = (1, 0, 0)
|
|
|
|
# Builds a map between each position in the grid and a unique integer between
|
|
# 0 and size^3 - 1.
|
|
index_map = {}
|
|
reverse_map = []
|
|
counter = 0
|
|
for x in range(size):
|
|
for y in range(size):
|
|
for z in range(size):
|
|
index_map[(x, y, z)] = counter
|
|
reverse_map.append((x, y, z))
|
|
counter += 1
|
|
|
|
# Starts building the model.
|
|
model = cp_model.CpModel()
|
|
position_to_rank = {}
|
|
|
|
for coord in reverse_map:
|
|
position_to_rank[coord] = model.new_int_var(0, counter - 1, f"rank_{coord}")
|
|
|
|
# Path constraints.
|
|
model.add(position_to_rank[start] == 0)
|
|
model.add(position_to_rank[end] == counter - 1)
|
|
for i in range(len(boxes) - 1):
|
|
model.add(position_to_rank[boxes[i]] < position_to_rank[boxes[i + 1]])
|
|
|
|
# Circuit constraint: visit all blocks exactly once, and maintains the rank
|
|
# of each block.
|
|
arcs = []
|
|
for x in range(size):
|
|
for y in range(size):
|
|
for z in range(size):
|
|
add_neighbor(
|
|
size, x, y, z, -1, 0, 0, model, index_map, position_to_rank, arcs
|
|
)
|
|
add_neighbor(
|
|
size, x, y, z, 1, 0, 0, model, index_map, position_to_rank, arcs
|
|
)
|
|
add_neighbor(
|
|
size, x, y, z, 0, -1, 0, model, index_map, position_to_rank, arcs
|
|
)
|
|
add_neighbor(
|
|
size, x, y, z, 0, 1, 0, model, index_map, position_to_rank, arcs
|
|
)
|
|
add_neighbor(
|
|
size, x, y, z, 0, 0, -1, model, index_map, position_to_rank, arcs
|
|
)
|
|
add_neighbor(
|
|
size, x, y, z, 0, 0, 1, model, index_map, position_to_rank, arcs
|
|
)
|
|
|
|
# Closes the loop as the constraint expects a circuit, not a path.
|
|
arcs.append((index_map[end], index_map[start], True))
|
|
|
|
# Adds the circuit (hamiltonian path) constraint.
|
|
model.add_circuit(arcs)
|
|
|
|
# Exports the model if required.
|
|
if output_proto:
|
|
model.export_to_file(output_proto)
|
|
|
|
# Solve model.
|
|
solver = cp_model.CpSolver()
|
|
if params:
|
|
text_format.Parse(params, solver.parameters)
|
|
solver.parameters.log_search_progress = True
|
|
result = solver.solve(model)
|
|
|
|
# Prints solution.
|
|
if result == cp_model.OPTIMAL:
|
|
path = [""] * counter
|
|
for x in range(size):
|
|
for y in range(size):
|
|
for z in range(size):
|
|
position = (x, y, z)
|
|
rank = solver.value(position_to_rank[position])
|
|
msg = f"({x}, {y}, {z})"
|
|
if position == start:
|
|
msg += " [start]"
|
|
elif position == end:
|
|
msg += " [end]"
|
|
else:
|
|
for b in range(len(boxes)):
|
|
if position == boxes[b]:
|
|
msg += f" [boxes {b}]"
|
|
path[rank] = msg
|
|
print(path)
|
|
|
|
|
|
def main(argv: Sequence[str]) -> None:
|
|
if len(argv) > 1:
|
|
raise app.UsageError("Too many command-line arguments.")
|
|
escape_the_maze(_PARAMS.value, _OUTPUT_PROTO.value)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
app.run(main)
|