Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ Changed
``Instance.solve``, ``Instance.solve_async``, and ``Instance.solutions``. The
``timeout`` parameter is still accepted, but will add a
``DeprecationWarning`` and will be removed in future versions.
- The ``intermediate_solutions`` parameter can now be explicitly set to
``False`` to avoid the ``-i`` flag to be passed to MiniZinc, which is
generally added to ensure that a final solution is available.

Fixed
^^^^^
Expand Down
69 changes: 42 additions & 27 deletions src/minizinc/instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ def solve(
processes: Optional[int] = None,
random_seed: Optional[int] = None,
all_solutions: bool = False,
intermediate_solutions: bool = False,
intermediate_solutions: Optional[bool] = None,
free_search: bool = False,
optimisation_level: Optional[int] = None,
timeout: Optional[timedelta] = None,
Expand Down Expand Up @@ -141,10 +141,13 @@ def solve(
all_solutions (bool): Request to solver to find all solutions. (Only
available on satisfaction problems and when the ``-a`` flag is
supported by the solver)
intermediate_solutions (bool): Request the solver to output any
intermediate solutions that are found during the solving
process. (Only available on optimisation problems and when the
``-a`` flag is supported by the solver)
intermediate_solutions (Optional[bool]): Request the solver to
output any intermediate solutions that are found during the
solving process. If left to ``None``, then intermediate
solutions might still be requested to ensure that the solving
process gives its final solution. (Only available on
optimisation problems and when the ``-i`` or ``-a`` flag is
supported by the solver)
optimisation_level (Optional[int]): Set the MiniZinc compiler
optimisation level.

Expand Down Expand Up @@ -189,6 +192,8 @@ def solve(
)
return asyncio.run(coroutine)
except RuntimeError as r:
coroutine.close()
del coroutine
if "called from a running event loop" in r.args[0]:
raise RuntimeError(
"the synchronous MiniZinc Python `solve()` method was called from"
Expand All @@ -207,8 +212,8 @@ async def solve_async(
nr_solutions: Optional[int] = None,
processes: Optional[int] = None,
random_seed: Optional[int] = None,
all_solutions=False,
intermediate_solutions=False,
all_solutions: bool = False,
intermediate_solutions: Optional[bool] = None,
free_search: bool = False,
optimisation_level: Optional[int] = None,
timeout: Optional[timedelta] = None,
Expand All @@ -231,14 +236,14 @@ async def solve_async(

"""
status = Status.UNKNOWN
solution = None
statistics: Dict[str, Any] = {}

multiple_solutions = (
all_solutions or intermediate_solutions or nr_solutions is not None
)
if multiple_solutions:
solution = []
solution: Union[Optional[Any], List[Any]] = (
[] if multiple_solutions else None
)

async for result in self.solutions(
time_limit=time_limit,
Expand All @@ -256,6 +261,7 @@ async def solve_async(
statistics.update(result.statistics)
if result.solution is not None:
if multiple_solutions:
assert isinstance(solution, list)
solution.append(result.solution)
else:
solution = result.solution
Expand Down Expand Up @@ -296,12 +302,12 @@ async def diverse_solutions(
"mzn-analyse executable could not be located"
)

try:
# Create a temporary file in which the diversity model (generated by mzn-analyse) is placed
div_file = tempfile.NamedTemporaryFile(
prefix="mzn_div", suffix=".mzn", delete=False
)
# Create a temporary file in which the diversity model (generated by mzn-analyse) is placed
div_file = tempfile.NamedTemporaryFile(
prefix="mzn_div", suffix=".mzn", delete=False
)

try:
# Extract the diversity annotations.
with self.files() as files:
div_anns = mzn_analyse.run(
Expand Down Expand Up @@ -454,8 +460,8 @@ async def solutions(
nr_solutions: Optional[int] = None,
processes: Optional[int] = None,
random_seed: Optional[int] = None,
all_solutions=False,
intermediate_solutions=False,
all_solutions: bool = False,
intermediate_solutions: Optional[bool] = None,
free_search: bool = False,
optimisation_level: Optional[int] = None,
verbose: bool = False,
Expand Down Expand Up @@ -537,12 +543,20 @@ async def solutions(
"Solver does not support the -n-o flag"
)
cmd.extend(["--num-optimal", str(nr_solutions)])
elif (
"-i" not in self._solver.stdFlags
or "-a" not in self._solver.stdFlags
elif intermediate_solutions:
if (
"-i" not in self._solver.stdFlags
and "-a" not in self._solver.stdFlags
):
raise NotImplementedError(
"Solver does not support the -i and -a flags"
)
cmd.append("--intermediate-solutions")
elif (intermediate_solutions is None and time_limit is not None) and (
"-i" in self._solver.stdFlags or "-a" in self._solver.stdFlags
):
# Enable intermediate solutions when possible
# (ensure that solvers always output their best solution)
# Enable intermediate solutions just in case to ensure that there is
# a best solution available at the time limit.
cmd.append("--intermediate-solutions")
# Set number of processes to be used
if processes is not None:
Expand Down Expand Up @@ -596,12 +610,11 @@ async def solutions(

# Run the MiniZinc process
proc = await self._driver._create_process(cmd, solver=solver)
try:
assert isinstance(proc.stderr, asyncio.StreamReader)
assert isinstance(proc.stdout, asyncio.StreamReader)

read_stderr = asyncio.create_task(_read_all(proc.stderr))
assert isinstance(proc.stderr, asyncio.StreamReader)
assert isinstance(proc.stdout, asyncio.StreamReader)
read_stderr = asyncio.create_task(_read_all(proc.stderr))

try:
async for obj in decode_async_json_stream(
proc.stdout, cls=MZNJSONDecoder, enum_map=self._enum_map
):
Expand Down Expand Up @@ -836,6 +849,7 @@ def analyse(self):
if obj["type"] == "interface":
interface = obj
break
assert interface is not None
old_method = self._method_cache
self._method_cache = Method.from_string(interface["method"])
self._input_cache = {}
Expand Down Expand Up @@ -1019,6 +1033,7 @@ def _parse_stream_obj(self, obj, statistics):
if "_checker" in statistics:
tmp["_checker"] = statistics.pop("_checker")

assert self.output_type is not None
solution = self.output_type(**tmp)
statistics["time"] = timedelta(milliseconds=obj["time"])
elif obj["type"] == "time":
Expand Down