Skip to content

Commit 7b41dc5

Browse files
committed
Add explicit ability for Driver objects to report available solvers
Solves #32
1 parent 6adc0f0 commit 7b41dc5

File tree

4 files changed

+64
-32
lines changed

4 files changed

+64
-32
lines changed

CHANGELOG.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,23 @@ Unreleased_
1111

1212
Added
1313
^^^^^
14+
1415
- Add (generated) ``__version__`` field to the package to abide by the PEP
1516
recommendations
1617
- Add support for using NumPy types to instantiate Models. (``np.array`` and
1718
any type that falls under ``np.generic``)
19+
- Add ``available_solvers`` method to the ``Driver`` objects to explicitly
20+
report the available solvers according to the current environment
1821

1922
Changed
2023
^^^^^^^
24+
2125
- **BREAKING:** Update minimal supported MiniZinc version to 2.5.0 to ensure
2226
full functionality.
2327
- Remove the (additional) hard time-out in from the CLI driver. MiniZinc should
2428
correctly enforce set time limit.
29+
- ``Solver.lookup`` now has an extra ``refresh`` argument to signal whether
30+
the driver should refresh the found solver configurations.
2531

2632
Fixed
2733
^^^^^

src/minizinc/CLI/driver.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22
# License, v. 2.0. If a copy of the MPL was not distributed with this
33
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
44

5+
import json
56
import re
67
import subprocess
78
import sys
89
import warnings
910
from asyncio import create_subprocess_exec
1011
from asyncio.subprocess import PIPE, Process
12+
from dataclasses import fields
1113
from pathlib import Path
1214
from typing import Any, Dict, List, Optional, Set, Type, Union
1315

@@ -81,6 +83,7 @@ class CLIDriver(Driver):
8183
"""
8284

8385
_executable: Path
86+
_solver_cache: Optional[Dict[str, Solver]] = None
8487

8588
def __init__(self, executable: Path):
8689
self._executable = executable
@@ -222,3 +225,30 @@ def check_version(self):
222225
f"version {found}. The minimal required version is "
223226
f"{CLI_REQUIRED_VERSION}."
224227
)
228+
229+
def available_solvers(self, refresh=False):
230+
if not refresh and self._solver_cache is not None:
231+
return self._solver_cache
232+
233+
# Find all available solvers
234+
output = self.run(["--solvers-json"])
235+
solvers = json.loads(output.stdout)
236+
237+
# Construct Solver objects
238+
self._solver_cache = {}
239+
allowed_fields = set([f.name for f in fields(Solver)])
240+
for s in solvers:
241+
obj = Solver(
242+
**{key: value for (key, value) in s.items() if key in allowed_fields}
243+
)
244+
if obj.version == "<unknown version>":
245+
obj._identifier = obj.id
246+
else:
247+
obj._identifier = obj.id + "@" + obj.version
248+
249+
names = s.get("tags", [])
250+
names.extend([s["id"], s["id"].split(".")[-1]])
251+
for name in names:
252+
self._solver_cache.setdefault(name, []).append(obj)
253+
254+
return self._solver_cache

src/minizinc/driver.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,25 @@ def check_version(self) -> None:
6262
"""
6363
pass
6464

65+
@abstractmethod
66+
def available_solvers(self, refresh=False):
67+
"""Returns a list of available solvers
68+
69+
This method returns the list of solvers available to the Driver object
70+
according to the current environment. Note that the list of solvers might
71+
be cached for future usage. The refresh argument can be used to ignore
72+
the current cache.
73+
74+
Args:
75+
refresh (bool): When set to true, the Driver will rediscover the
76+
available solvers from the current environment.
77+
78+
Returns:
79+
Dict[str, List[Solver]]: A dictionary that maps solver tags to MiniZinc
80+
solver configurations that can be used with the Driver object.
81+
"""
82+
pass
83+
6584

6685
def find_driver(
6786
path: Optional[List[str]] = None, name: str = "minizinc"

src/minizinc/solver.py

Lines changed: 9 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
import json
77
import os
88
import tempfile
9-
from dataclasses import dataclass, field, fields
9+
from dataclasses import dataclass, field
1010
from pathlib import Path
11-
from typing import Any, Dict, Iterator, List, Optional, Set, Tuple
11+
from typing import Iterator, List, Optional, Tuple
1212

1313
import minizinc
1414

@@ -109,7 +109,7 @@ class Solver:
109109
_identifier: Optional[str] = None
110110

111111
@classmethod
112-
def lookup(cls, tag: str, driver=None):
112+
def lookup(cls, tag: str, driver=None, refresh=False):
113113
"""Lookup a solver configuration in the driver registry.
114114
115115
Access the MiniZinc driver's known solver configuration and find the
@@ -121,6 +121,7 @@ def lookup(cls, tag: str, driver=None):
121121
tag (str): tag (or id) of a solver configuration to look up.
122122
driver (Driver): driver which registry will be searched for the
123123
solver. If set to None, then ``default_driver`` will be used.
124+
refresh (bool): Forces the driver to refresh the cache of available solvers.
124125
125126
Returns:
126127
Solver: MiniZinc solver configuration compatible with the driver.
@@ -131,41 +132,17 @@ def lookup(cls, tag: str, driver=None):
131132
"""
132133
if driver is None:
133134
driver = minizinc.default_driver
134-
from .CLI.driver import CLIDriver
135+
assert driver is not None
135136

136-
assert isinstance(driver, CLIDriver)
137-
if driver is not None:
138-
output = driver.run(["--solvers-json"])
139-
else:
140-
raise LookupError("Solver is not linked to a MiniZinc driver")
141-
# Find all available solvers
142-
solvers = json.loads(output.stdout)
137+
tag_map = driver.available_solvers(refresh)
143138

144-
# Find the specified solver
145-
lookup: Optional[Dict[str, Any]] = None
146-
names: Set[str] = set()
147-
for s in solvers:
148-
s_names = [s["id"], s["id"].split(".")[-1]]
149-
s_names.extend(s.get("tags", []))
150-
names = names.union(set(s_names))
151-
if tag in s_names:
152-
lookup = s
153-
break
154-
if lookup is None:
139+
if tag not in tag_map or len(tag_map[tag]) < 1:
155140
raise LookupError(
156141
f"No solver id or tag '{tag}' found, available options: "
157-
f"{sorted([x for x in names])}"
142+
f"{sorted(tag_map.keys())}"
158143
)
159144

160-
allowed_fields = set([f.name for f in fields(cls)])
161-
ret = cls(
162-
**{key: value for (key, value) in lookup.items() if key in allowed_fields}
163-
)
164-
if ret.version == "<unknown version>":
165-
ret._identifier = ret.id
166-
else:
167-
ret._identifier = ret.id + "@" + ret.version
168-
return ret
145+
return tag_map[tag][0]
169146

170147
@classmethod
171148
def load(cls, path: Path):

0 commit comments

Comments
 (0)