solver-support 0.1.1__tar.gz → 0.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {solver_support-0.1.1/solver_support.egg-info → solver_support-0.2.1}/PKG-INFO +1 -1
- {solver_support-0.1.1 → solver_support-0.2.1}/docs/timing-model.md +39 -0
- {solver_support-0.1.1 → solver_support-0.2.1}/solver_support/__init__.py +1 -1
- {solver_support-0.1.1 → solver_support-0.2.1}/solver_support/command_builders.py +149 -35
- {solver_support-0.1.1 → solver_support-0.2.1}/solver_support/solver_horizon.py +17 -3
- {solver_support-0.1.1 → solver_support-0.2.1/solver_support.egg-info}/PKG-INFO +1 -1
- {solver_support-0.1.1 → solver_support-0.2.1}/solver_support.egg-info/SOURCES.txt +1 -0
- solver_support-0.2.1/tests/fixtures/models.yaml +205 -0
- solver_support-0.2.1/tests/test_command_builders.py +203 -0
- solver_support-0.1.1/tests/fixtures/models.yaml +0 -57
- {solver_support-0.1.1 → solver_support-0.2.1}/LICENSE +0 -0
- {solver_support-0.1.1 → solver_support-0.2.1}/MANIFEST.in +0 -0
- {solver_support-0.1.1 → solver_support-0.2.1}/README.md +0 -0
- {solver_support-0.1.1 → solver_support-0.2.1}/docs/injection-api.md +0 -0
- {solver_support-0.1.1 → solver_support-0.2.1}/docs/solver-runner.md +0 -0
- {solver_support-0.1.1 → solver_support-0.2.1}/pyproject.toml +0 -0
- {solver_support-0.1.1 → solver_support-0.2.1}/setup.cfg +0 -0
- {solver_support-0.1.1 → solver_support-0.2.1}/solver_support/file_management.py +0 -0
- {solver_support-0.1.1 → solver_support-0.2.1}/solver_support/global_state.py +0 -0
- {solver_support-0.1.1 → solver_support-0.2.1}/solver_support/horizon_utils.py +0 -0
- {solver_support-0.1.1 → solver_support-0.2.1}/solver_support/model_registry.py +0 -0
- {solver_support-0.1.1 → solver_support-0.2.1}/solver_support/objective.py +0 -0
- {solver_support-0.1.1 → solver_support-0.2.1}/solver_support/run_summary.py +0 -0
- {solver_support-0.1.1 → solver_support-0.2.1}/solver_support/solver_base.py +0 -0
- {solver_support-0.1.1 → solver_support-0.2.1}/solver_support/solver_enhsp.py +0 -0
- {solver_support-0.1.1 → solver_support-0.2.1}/solver_support/solver_fd.py +0 -0
- {solver_support-0.1.1 → solver_support-0.2.1}/solver_support/solver_runner.py +0 -0
- {solver_support-0.1.1 → solver_support-0.2.1}/solver_support/solver_stats.py +0 -0
- {solver_support-0.1.1 → solver_support-0.2.1}/solver_support/solver_upfplan.py +0 -0
- {solver_support-0.1.1 → solver_support-0.2.1}/solver_support/utilities.py +0 -0
- {solver_support-0.1.1 → solver_support-0.2.1}/solver_support.egg-info/dependency_links.txt +0 -0
- {solver_support-0.1.1 → solver_support-0.2.1}/solver_support.egg-info/not-zip-safe +0 -0
- {solver_support-0.1.1 → solver_support-0.2.1}/solver_support.egg-info/requires.txt +0 -0
- {solver_support-0.1.1 → solver_support-0.2.1}/solver_support.egg-info/top_level.txt +0 -0
- {solver_support-0.1.1 → solver_support-0.2.1}/tests/conftest.py +0 -0
- {solver_support-0.1.1 → solver_support-0.2.1}/tests/test_helpers.py +0 -0
- {solver_support-0.1.1 → solver_support-0.2.1}/tests/test_horizon_utils.py +0 -0
- {solver_support-0.1.1 → solver_support-0.2.1}/tests/test_import.py +0 -0
- {solver_support-0.1.1 → solver_support-0.2.1}/tests/test_run_summary.py +0 -0
- {solver_support-0.1.1 → solver_support-0.2.1}/tests/test_run_summary_success.py +0 -0
- {solver_support-0.1.1 → solver_support-0.2.1}/tests/test_solution_naming.py +0 -0
- {solver_support-0.1.1 → solver_support-0.2.1}/tests/test_solver_runner.py +0 -0
- {solver_support-0.1.1 → solver_support-0.2.1}/tests/test_solver_stats.py +0 -0
- {solver_support-0.1.1 → solver_support-0.2.1}/tests/test_solvers.py +0 -0
|
@@ -85,6 +85,45 @@ fields. All times are seconds unless noted.
|
|
|
85
85
|
- **Best-effort**: a hard timeout can kill Savile Row before it writes `.info`,
|
|
86
86
|
leaving the split absent (`None`).
|
|
87
87
|
|
|
88
|
+
#### Backends
|
|
89
|
+
|
|
90
|
+
Savile Row drives five backends, chosen by a solver config's `backend:` key and
|
|
91
|
+
described by one row each in `CommandBuilderMixin.SAVILEROW_BACKENDS`. A config
|
|
92
|
+
with no `backend:` key is the SAT arm, which is what every pre-existing config
|
|
93
|
+
relies on. The row supplies three things that vary and nothing that does not; the
|
|
94
|
+
arg-set key is *derived* as `<backend>_args`, so it needs no row.
|
|
95
|
+
|
|
96
|
+
| `backend:` | arg-set key | solver's own time limit | binary flag |
|
|
97
|
+
|---|---|---|---|
|
|
98
|
+
| *(absent)* / `sat` | `sat_args` | `--time=N` (s) | `-satsolver-bin` |
|
|
99
|
+
| `minion` | `minion_args` | *(none)* | `-minion-bin` |
|
|
100
|
+
| `ortools` | `ortools_args` | `--time_limit=N` (s) | `-fzn-bin` |
|
|
101
|
+
| `gecode` | `gecode_args` | `-time N` (**ms**) | `-gecode-bin` |
|
|
102
|
+
| `chuffed` | `chuffed_args` | `--time-out N` (**ms**) | `-chuffed-bin` |
|
|
103
|
+
|
|
104
|
+
Savile Row's own `-timelimit` is always added, always in seconds, and covers
|
|
105
|
+
translation plus solving. The solver's limit is passed through `-solver-options`
|
|
106
|
+
in addition, so the solver stops itself and reports rather than being killed with
|
|
107
|
+
nothing to show. Repeated `-solver-options` accumulate, so a tune preset may
|
|
108
|
+
carry its own without displacing the timeout.
|
|
109
|
+
|
|
110
|
+
**The units are a trap.** Gecode and Chuffed take milliseconds and neither says
|
|
111
|
+
so in its flag name. An unscaled value does not fail loudly — a 30s budget
|
|
112
|
+
becomes 30ms and the solver gives up instantly, which reads as a hard instance.
|
|
113
|
+
`timeout_scale` in the table is what converts; a config overriding
|
|
114
|
+
`solver_timeout_option` changes the flag's *name* only, since the unit belongs to
|
|
115
|
+
the solver rather than to the spelling.
|
|
116
|
+
|
|
117
|
+
**Only the SAT arm records intermediate objectives.** It is the one configuration
|
|
118
|
+
where Savile Row runs the optimisation loop itself; every other backend optimises
|
|
119
|
+
internally, leaving nothing for `-record-intermediate-objective-values` to see.
|
|
120
|
+
So for a non-SAT backend `_generate_objective_file_path` returns `None` and no
|
|
121
|
+
`.obj` file is named or cleared, and `_check_continue_scan` falls back to the raw
|
|
122
|
+
objective read out of the *solution* by the injected extractor. A consumer that
|
|
123
|
+
wires only the objective-file route will find continue-scan stops at the first
|
|
124
|
+
solving horizon on those backends instead of improving — wire
|
|
125
|
+
`set_raw_objective_from_solution` to use them for optimisation models.
|
|
126
|
+
|
|
88
127
|
### Fast Downward / SymK (PDDL models)
|
|
89
128
|
|
|
90
129
|
- **Shape**: not a horizon scan — one `solving` phase whose A\* (blind) / symbolic
|
|
@@ -13,7 +13,7 @@ unconfigured pipeline. A consumer registers its scoring (see :mod:`objective`)
|
|
|
13
13
|
and its packaging paths (see :mod:`model_registry`) once at its own import time.
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
|
-
__version__ = "0.
|
|
16
|
+
__version__ = "0.2.1"
|
|
17
17
|
|
|
18
18
|
# --- model registry (loader + command builders + packaging injection) ---
|
|
19
19
|
from .model_registry import (
|
|
@@ -24,6 +24,84 @@ from .model_registry import ModelRegistryError
|
|
|
24
24
|
class CommandBuilderMixin:
|
|
25
25
|
"""The ``build_*_command`` methods mixed into :class:`ModelRegistry`."""
|
|
26
26
|
|
|
27
|
+
#: The Savile Row backends, one row each. Everything the argv assembler needs
|
|
28
|
+
#: to know about a backend lives here, so adding one is a single entry plus
|
|
29
|
+
#: its ``<backend>_args`` presets in the config - not an edit spread across an
|
|
30
|
+
#: arg-set lookup, a binary flag and a timeout dialect. The arg-set key is
|
|
31
|
+
#: *derived* (``<backend>_args``) rather than tabulated, so it needs no row.
|
|
32
|
+
#:
|
|
33
|
+
#: ``sat`` is the implicit default: a config with no ``backend:`` key is the
|
|
34
|
+
#: kissat SAT backend every pre-existing config relies on.
|
|
35
|
+
#:
|
|
36
|
+
#: ``timeout_option`` is the *solver's own* time limit, handed to it through
|
|
37
|
+
#: Savile Row's ``-solver-options``; Savile Row's own ``-timelimit`` (covering
|
|
38
|
+
#: translation plus solving) is added for every backend regardless. Giving the
|
|
39
|
+
#: solver its own budget means it stops itself and reports, rather than being
|
|
40
|
+
#: killed with nothing to show.
|
|
41
|
+
#:
|
|
42
|
+
#: ``timeout_scale`` converts the seconds this API speaks into the unit that
|
|
43
|
+
#: solver expects. Gecode and Chuffed both want milliseconds and neither says
|
|
44
|
+
#: so in its flag name, which makes the mistake silent rather than loud: a 30s
|
|
45
|
+
#: budget passed unscaled becomes 30ms, and the solver gives up instantly
|
|
46
|
+
#: looking for all the world like a hard instance.
|
|
47
|
+
SAVILEROW_BACKENDS = {
|
|
48
|
+
'sat': {
|
|
49
|
+
'bin_option': '-satsolver-bin',
|
|
50
|
+
'timeout_option': '--time={value}',
|
|
51
|
+
'timeout_scale': 1,
|
|
52
|
+
# Savile Row drives the optimisation loop itself when the backend is a
|
|
53
|
+
# SAT solver, so it can record each improving objective as it goes.
|
|
54
|
+
# Every other backend optimises internally, leaving SR nothing to see.
|
|
55
|
+
'records_objectives': True,
|
|
56
|
+
},
|
|
57
|
+
'minion': {
|
|
58
|
+
'bin_option': '-minion-bin',
|
|
59
|
+
# Minion takes no time limit of its own here: -timelimit is enough.
|
|
60
|
+
'timeout_option': None,
|
|
61
|
+
'timeout_scale': 1,
|
|
62
|
+
'records_objectives': False,
|
|
63
|
+
},
|
|
64
|
+
'ortools': {
|
|
65
|
+
# SR calls OR-Tools through its "standard FlatZinc" route, so the
|
|
66
|
+
# generic -fzn-bin names the binary; Gecode and Chuffed have their own.
|
|
67
|
+
'bin_option': '-fzn-bin',
|
|
68
|
+
'timeout_option': '--time_limit={value}', # an Abseil flag, seconds
|
|
69
|
+
'timeout_scale': 1,
|
|
70
|
+
'records_objectives': False,
|
|
71
|
+
},
|
|
72
|
+
'gecode': {
|
|
73
|
+
'bin_option': '-gecode-bin',
|
|
74
|
+
'timeout_option': '-time {value}', # "time (in ms) cutoff"
|
|
75
|
+
'timeout_scale': 1000,
|
|
76
|
+
'records_objectives': False,
|
|
77
|
+
},
|
|
78
|
+
'chuffed': {
|
|
79
|
+
'bin_option': '-chuffed-bin',
|
|
80
|
+
# Named --time-out but documented "Time out in milliseconds".
|
|
81
|
+
'timeout_option': '--time-out {value}',
|
|
82
|
+
'timeout_scale': 1000,
|
|
83
|
+
'records_objectives': False,
|
|
84
|
+
},
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
def records_intermediate_objectives(self, solver_name: str) -> bool:
|
|
88
|
+
"""Whether this solver's backend writes the intermediate-objective file.
|
|
89
|
+
|
|
90
|
+
True only for Savile Row's SAT arm, which is the one configuration where
|
|
91
|
+
Savile Row runs the optimisation loop and can report improving objectives
|
|
92
|
+
as it finds them. Callers use this to avoid naming (and clearing) a file
|
|
93
|
+
that will never be written; with no such file, continue-scan falls back to
|
|
94
|
+
the injected solution extractor. Resolves through
|
|
95
|
+
:meth:`get_tune_backend`, so the Conjure path answers for the kissat
|
|
96
|
+
solve it delegates to, and non-savilerow solvers answer False.
|
|
97
|
+
"""
|
|
98
|
+
backend_solver = self.get_tune_backend(solver_name)
|
|
99
|
+
if self.get_solver_type(backend_solver) != 'savilerow':
|
|
100
|
+
return False
|
|
101
|
+
backend = self.get_solver_config(backend_solver).get('backend', 'sat')
|
|
102
|
+
spec = self.SAVILEROW_BACKENDS.get(backend)
|
|
103
|
+
return bool(spec and spec['records_objectives'])
|
|
104
|
+
|
|
27
105
|
def build_savilerow_command(self, model_path: str, param_path: str,
|
|
28
106
|
use_srjava: bool = False,
|
|
29
107
|
timeout: Optional[int] = None,
|
|
@@ -46,31 +124,50 @@ class CommandBuilderMixin:
|
|
|
46
124
|
objective_file_path: path for intermediate objective values file (default: obj.txt)
|
|
47
125
|
extra_options: additional command line options to pass to Savile Row
|
|
48
126
|
solver_name: name of the solver configuration to use (default: 'kissat').
|
|
49
|
-
The backend
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
127
|
+
The backend is a property of this config, named by its
|
|
128
|
+
``backend:`` key and described by ``SAVILEROW_BACKENDS``: it
|
|
129
|
+
selects the arg-set key, the solver's own timeout dialect, and
|
|
130
|
+
the flag that names its binary. No ``backend:`` key means the
|
|
131
|
+
kissat SAT backend, which is what every pre-existing config
|
|
132
|
+
relies on.
|
|
53
133
|
tune: name of a ``tune:`` preset to apply instead of the config's
|
|
54
134
|
``default_tune`` (kissat: 'safe'/'bare'/'srdefault'/'gac';
|
|
55
|
-
minion: 'bare'). The preset supplies this backend's arg-set
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
``default_tune``. This is the
|
|
59
|
-
dial fd uses for its search
|
|
135
|
+
minion: 'bare'). The preset supplies this backend's arg-set,
|
|
136
|
+
under the key ``<backend>_args`` - sat_args, minion_args,
|
|
137
|
+
ortools_args, and so on. A name that is unknown or lacks this
|
|
138
|
+
backend's arg key falls back to ``default_tune``. This is the
|
|
139
|
+
savilerow arm of the same ``--tune`` dial fd uses for its search
|
|
140
|
+
presets.
|
|
60
141
|
|
|
61
142
|
Returns:
|
|
62
143
|
Complete command as list of strings.
|
|
63
144
|
|
|
64
145
|
Raises:
|
|
65
|
-
ModelRegistryError: If savilerow solver not found in config,
|
|
66
|
-
|
|
146
|
+
ModelRegistryError: If savilerow solver not found in config, if the
|
|
147
|
+
config names a backend that is not in ``SAVILEROW_BACKENDS``, or
|
|
148
|
+
if the resolved tune has no arg-set for this backend.
|
|
67
149
|
"""
|
|
68
150
|
solver_config = self.get_solver_config(solver_name)
|
|
69
151
|
|
|
70
|
-
# The backend solver
|
|
71
|
-
#
|
|
72
|
-
#
|
|
73
|
-
|
|
152
|
+
# The backend solver is declared by the config, not passed in: `--solver
|
|
153
|
+
# minion` selects the minion backend config. This replaced the old
|
|
154
|
+
# cross-cutting `--minion` boolean. Absent means the kissat SAT backend,
|
|
155
|
+
# which is what every pre-existing config relies on.
|
|
156
|
+
#
|
|
157
|
+
# An unrecognised name is an error rather than a fallback. The lenient
|
|
158
|
+
# `_effective_tune` lookup below can afford to shrug at a typo because a
|
|
159
|
+
# wrong tune still runs the right solver; a typo'd backend would silently
|
|
160
|
+
# build the SAT arm's argv - a run that succeeds while comparing the wrong
|
|
161
|
+
# solver, which is worse than no run at all. Same reasoning as
|
|
162
|
+
# `is_valid_tune`, applied one level up.
|
|
163
|
+
backend = solver_config.get('backend', 'sat')
|
|
164
|
+
spec = self.SAVILEROW_BACKENDS.get(backend)
|
|
165
|
+
if spec is None:
|
|
166
|
+
known = ', '.join(sorted(self.SAVILEROW_BACKENDS))
|
|
167
|
+
raise ModelRegistryError(
|
|
168
|
+
f"Solver '{solver_name}' declares unknown backend '{backend}' "
|
|
169
|
+
f"(known backends: {known})"
|
|
170
|
+
)
|
|
74
171
|
|
|
75
172
|
# Choose command variant
|
|
76
173
|
if use_srjava:
|
|
@@ -90,12 +187,14 @@ class CommandBuilderMixin:
|
|
|
90
187
|
self._savilerow_native_warning_shown = True
|
|
91
188
|
command = solver_config['command']
|
|
92
189
|
|
|
93
|
-
# Choose the argument set from the active tune preset.
|
|
94
|
-
#
|
|
95
|
-
#
|
|
96
|
-
#
|
|
97
|
-
#
|
|
98
|
-
|
|
190
|
+
# Choose the argument set from the active tune preset. Each backend reads
|
|
191
|
+
# its own key, named after it: sat_args, minion_args, ortools_args, and so
|
|
192
|
+
# on. Deriving the name rather than tabulating it is what lets a new
|
|
193
|
+
# backend arrive without touching this line. A tune that is unknown or
|
|
194
|
+
# lacks this backend's key falls back to default_tune (the lenient
|
|
195
|
+
# behaviour the fd builder uses too). The model-default tune is resolved
|
|
196
|
+
# by the caller, so `tune` here is already the effective name.
|
|
197
|
+
arg_key = f'{backend}_args'
|
|
99
198
|
tunes = solver_config.get('tune', {})
|
|
100
199
|
tune_name = self._effective_tune(solver_config, tune, arg_key)
|
|
101
200
|
preset = tunes.get(tune_name, {})
|
|
@@ -104,26 +203,41 @@ class CommandBuilderMixin:
|
|
|
104
203
|
f"Solver '{solver_name}' tune '{tune_name}' has no {arg_key}"
|
|
105
204
|
)
|
|
106
205
|
args = preset[arg_key].copy()
|
|
107
|
-
if not
|
|
108
|
-
#
|
|
109
|
-
|
|
110
|
-
|
|
206
|
+
if spec['records_objectives'] and objective_file_path is not None:
|
|
207
|
+
# Only the SAT arm has intermediate objectives to record, because it
|
|
208
|
+
# is the only one where Savile Row runs the optimisation loop rather
|
|
209
|
+
# than the solver. Asking any other backend for them names a file that
|
|
210
|
+
# is never written.
|
|
211
|
+
args.extend(["-record-intermediate-objective-values", objective_file_path])
|
|
212
|
+
|
|
213
|
+
# Name the backend's binary only when the config pins one. Each backend
|
|
214
|
+
# has its own flag for this (-fzn-bin, -gecode-bin, -minion-bin, ...), and
|
|
215
|
+
# left unset Savile Row's default applies - so the registry never has to
|
|
216
|
+
# bake one machine's build into a shared config.
|
|
217
|
+
solver_bin = solver_config.get('solver_bin')
|
|
218
|
+
if solver_bin:
|
|
219
|
+
args.extend([spec['bin_option'], solver_bin])
|
|
111
220
|
|
|
112
221
|
# Add timeout arguments
|
|
113
222
|
if timeout is None:
|
|
114
223
|
timeout = solver_config['default_timeout']
|
|
115
224
|
|
|
116
|
-
# Different timeout handling for minion vs SAT solver
|
|
117
225
|
if timeout is not None:
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
226
|
+
# The solver's own limit, in whatever flag and unit that solver
|
|
227
|
+
# speaks. A config may override the dialect (or set it to null to
|
|
228
|
+
# suppress it) without the core having to learn a new backend.
|
|
229
|
+
option = solver_config.get('solver_timeout_option', spec['timeout_option'])
|
|
230
|
+
if option:
|
|
231
|
+
scale = spec['timeout_scale']
|
|
232
|
+
# Left untouched when the solver already speaks seconds, so those
|
|
233
|
+
# argvs keep their exact previous form (--time=45, never 45.0).
|
|
234
|
+
value = timeout if scale == 1 else int(timeout * scale)
|
|
235
|
+
args.extend(["-solver-options", option.format(value=value)])
|
|
236
|
+
# Savile Row's own limit covers translation plus solving and is always
|
|
237
|
+
# in seconds. Always added, so a backend with no limit of its own is
|
|
238
|
+
# still bounded - and one that has its own stops itself and reports,
|
|
239
|
+
# rather than being killed with nothing to show.
|
|
240
|
+
args.extend(["-timelimit", str(timeout)])
|
|
127
241
|
|
|
128
242
|
# Add extra options if provided
|
|
129
243
|
if extra_options:
|
|
@@ -32,8 +32,20 @@ class HorizonBasedSolver(Solver):
|
|
|
32
32
|
self._stop_at_first = False # regime: first stops the scan at the first solving horizon
|
|
33
33
|
self.solver_name = solver_name # the backend config (kissat/minion/...)
|
|
34
34
|
|
|
35
|
-
def _generate_objective_file_path(self, n: int) -> Path:
|
|
36
|
-
"""Generate the objective file path for a given horizon.
|
|
35
|
+
def _generate_objective_file_path(self, n: int) -> Optional[Path]:
|
|
36
|
+
"""Generate the objective file path for a given horizon, or None.
|
|
37
|
+
|
|
38
|
+
None when the active backend does not write one. Only Savile Row's SAT
|
|
39
|
+
arm runs the optimisation loop that emits intermediate objectives, so for
|
|
40
|
+
a Minion or FlatZinc backend the file would be named, cleared, and then
|
|
41
|
+
never appear. Returning None here rather than gating each call site keeps
|
|
42
|
+
the three of them identical, and leaves continue-scan to read the
|
|
43
|
+
objective out of the solution via the injected extractor - which is the
|
|
44
|
+
only route those backends have.
|
|
45
|
+
"""
|
|
46
|
+
if not global_state.model_registry.records_intermediate_objectives(
|
|
47
|
+
self.solver_name):
|
|
48
|
+
return None
|
|
37
49
|
return ObjectiveFileManager.generate_objective_path(
|
|
38
50
|
self.runner.summary.instance,
|
|
39
51
|
self.runner.summary.solver,
|
|
@@ -43,8 +55,10 @@ class HorizonBasedSolver(Solver):
|
|
|
43
55
|
tune=self.runner.summary.tune_slot
|
|
44
56
|
)
|
|
45
57
|
|
|
46
|
-
def _prepare_objective_file(self, obj_file: Path) -> None:
|
|
58
|
+
def _prepare_objective_file(self, obj_file: Optional[Path]) -> None:
|
|
47
59
|
"""Prepare the objective file by removing any existing file."""
|
|
60
|
+
if obj_file is None:
|
|
61
|
+
return
|
|
48
62
|
ObjectiveFileManager.prepare_objective_file(obj_file, self.logger)
|
|
49
63
|
|
|
50
64
|
def _parse_objective_file(self, obj_file: Path) -> Optional[int]:
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# Synthetic model registry for the core's own tests.
|
|
2
|
+
#
|
|
3
|
+
# The core ships without a registry -- a consumer injects the path to its own
|
|
4
|
+
# models.yaml (see docs/injection-api.md). Tests that reach the registry wire
|
|
5
|
+
# THIS file instead, so the core is exercised against a config it owns rather
|
|
6
|
+
# than against whichever consumer happens to be installed beside it. Keep it
|
|
7
|
+
# minimal: add only what a test actually reads, and keep the shape faithful to a
|
|
8
|
+
# real consumer config (that shape is the contract the loader and the accessors
|
|
9
|
+
# implement).
|
|
10
|
+
|
|
11
|
+
models:
|
|
12
|
+
# A constructed UPF model: built in Python, solved through the UPF driver.
|
|
13
|
+
upf:
|
|
14
|
+
path: upf.py
|
|
15
|
+
solver: upffd
|
|
16
|
+
detection_type: upf
|
|
17
|
+
format-name: upf
|
|
18
|
+
proves-unsat: true
|
|
19
|
+
description: constructed UPF model, solved by Fast Downward via UPF
|
|
20
|
+
|
|
21
|
+
# An optimisation model whose objective is recorded in the solution file, so
|
|
22
|
+
# the horizon scan compares objectives between horizons.
|
|
23
|
+
scored:
|
|
24
|
+
path: models/Scored.eprime
|
|
25
|
+
solver: kissat
|
|
26
|
+
detection_type: scored
|
|
27
|
+
format-name: compact
|
|
28
|
+
objective-in-solution: true
|
|
29
|
+
description: optimisation model carrying its objective in the solution
|
|
30
|
+
|
|
31
|
+
solvers:
|
|
32
|
+
upffd:
|
|
33
|
+
# Fast Downward through the Unified Planning Framework. The engine and the
|
|
34
|
+
# FD search config are BOTH resolved from the effective --tune, which is
|
|
35
|
+
# what UpfPlanSolver turns into `-s <engine>` and `--fd-search <search>`.
|
|
36
|
+
solver_type: upfplan
|
|
37
|
+
command: upfplan
|
|
38
|
+
extensions:
|
|
39
|
+
- .pddl
|
|
40
|
+
default_timeout: 600
|
|
41
|
+
engine_name: fast-downward
|
|
42
|
+
default_tune: optimal
|
|
43
|
+
tune:
|
|
44
|
+
optimal:
|
|
45
|
+
fd_search: "astar(blind())"
|
|
46
|
+
description: cost-optimal blind A*
|
|
47
|
+
satisficing:
|
|
48
|
+
fd_search: "lazy_greedy([ff()])"
|
|
49
|
+
description: satisficing greedy best-first with FF
|
|
50
|
+
|
|
51
|
+
kissat:
|
|
52
|
+
solver_type: savilerow
|
|
53
|
+
command: savilerow
|
|
54
|
+
extensions:
|
|
55
|
+
- .eprime
|
|
56
|
+
default_timeout: 600
|
|
57
|
+
description: SAT backend driven through Savile Row
|
|
58
|
+
|
|
59
|
+
# The Savile Row backends, with tune presets, for the command builder tests.
|
|
60
|
+
# Separate from `kissat` above so that adding them cannot perturb the tests
|
|
61
|
+
# which rely on that entry having no presets.
|
|
62
|
+
#
|
|
63
|
+
# One entry per backend, plus two that pin a solver binary. Two, because the
|
|
64
|
+
# flag that names the binary differs per backend (-fzn-bin vs -gecode-bin), so
|
|
65
|
+
# a single pinned entry could not tell a per-backend flag apart from a
|
|
66
|
+
# hardcoded one.
|
|
67
|
+
srsat:
|
|
68
|
+
# No `backend:` key at all -- the shape every pre-existing SAT config has.
|
|
69
|
+
solver_type: savilerow
|
|
70
|
+
command: savilerow
|
|
71
|
+
extensions:
|
|
72
|
+
- .eprime
|
|
73
|
+
default_timeout: null
|
|
74
|
+
default_tune: default
|
|
75
|
+
tune:
|
|
76
|
+
default:
|
|
77
|
+
sat_args:
|
|
78
|
+
- "-run-solver"
|
|
79
|
+
- "-sat"
|
|
80
|
+
description: SAT backend
|
|
81
|
+
|
|
82
|
+
srminion:
|
|
83
|
+
solver_type: savilerow
|
|
84
|
+
command: savilerow
|
|
85
|
+
extensions:
|
|
86
|
+
- .eprime
|
|
87
|
+
default_timeout: null
|
|
88
|
+
backend: minion
|
|
89
|
+
default_tune: default
|
|
90
|
+
tune:
|
|
91
|
+
default:
|
|
92
|
+
minion_args:
|
|
93
|
+
- "-run-solver"
|
|
94
|
+
description: Minion backend
|
|
95
|
+
|
|
96
|
+
srortools:
|
|
97
|
+
solver_type: savilerow
|
|
98
|
+
command: savilerow
|
|
99
|
+
extensions:
|
|
100
|
+
- .eprime
|
|
101
|
+
default_timeout: null
|
|
102
|
+
backend: ortools
|
|
103
|
+
default_tune: default
|
|
104
|
+
tune:
|
|
105
|
+
default:
|
|
106
|
+
ortools_args:
|
|
107
|
+
- "-run-solver"
|
|
108
|
+
- "-or-tools"
|
|
109
|
+
description: OR-Tools CP-SAT backend
|
|
110
|
+
|
|
111
|
+
srgecode:
|
|
112
|
+
solver_type: savilerow
|
|
113
|
+
command: savilerow
|
|
114
|
+
extensions:
|
|
115
|
+
- .eprime
|
|
116
|
+
default_timeout: null
|
|
117
|
+
backend: gecode
|
|
118
|
+
default_tune: default
|
|
119
|
+
tune:
|
|
120
|
+
default:
|
|
121
|
+
gecode_args:
|
|
122
|
+
- "-run-solver"
|
|
123
|
+
- "-gecode"
|
|
124
|
+
description: Gecode FlatZinc backend
|
|
125
|
+
|
|
126
|
+
srchuffed:
|
|
127
|
+
solver_type: savilerow
|
|
128
|
+
command: savilerow
|
|
129
|
+
extensions:
|
|
130
|
+
- .eprime
|
|
131
|
+
default_timeout: null
|
|
132
|
+
backend: chuffed
|
|
133
|
+
default_tune: default
|
|
134
|
+
tune:
|
|
135
|
+
default:
|
|
136
|
+
chuffed_args:
|
|
137
|
+
- "-run-solver"
|
|
138
|
+
- "-chuffed"
|
|
139
|
+
description: Chuffed FlatZinc backend
|
|
140
|
+
|
|
141
|
+
srortoolsbin:
|
|
142
|
+
# Same as srortools, but pinning the solver binary by name.
|
|
143
|
+
solver_type: savilerow
|
|
144
|
+
command: savilerow
|
|
145
|
+
extensions:
|
|
146
|
+
- .eprime
|
|
147
|
+
default_timeout: null
|
|
148
|
+
backend: ortools
|
|
149
|
+
solver_bin: fzn-ortools
|
|
150
|
+
default_tune: default
|
|
151
|
+
tune:
|
|
152
|
+
default:
|
|
153
|
+
ortools_args:
|
|
154
|
+
- "-run-solver"
|
|
155
|
+
- "-or-tools"
|
|
156
|
+
description: OR-Tools CP-SAT backend, binary pinned
|
|
157
|
+
|
|
158
|
+
srgecodebin:
|
|
159
|
+
# The same pin on a different backend, which must reach a different flag.
|
|
160
|
+
# Also carries a timeout-dialect override, so one entry covers both config
|
|
161
|
+
# escape hatches: the flag name is the config's to change, while the unit
|
|
162
|
+
# stays the solver's (still milliseconds here).
|
|
163
|
+
solver_type: savilerow
|
|
164
|
+
command: savilerow
|
|
165
|
+
extensions:
|
|
166
|
+
- .eprime
|
|
167
|
+
default_timeout: null
|
|
168
|
+
backend: gecode
|
|
169
|
+
solver_bin: fzn-gecode-6
|
|
170
|
+
solver_timeout_option: "-overall-time {value}"
|
|
171
|
+
default_tune: default
|
|
172
|
+
tune:
|
|
173
|
+
default:
|
|
174
|
+
gecode_args:
|
|
175
|
+
- "-run-solver"
|
|
176
|
+
- "-gecode"
|
|
177
|
+
description: Gecode backend, binary pinned
|
|
178
|
+
|
|
179
|
+
srbadbackend:
|
|
180
|
+
# A wrong backend name: must be rejected, not silently run as SAT.
|
|
181
|
+
#
|
|
182
|
+
# `or-tools` rather than an obviously-mangled string, because this is the
|
|
183
|
+
# mistake someone would actually make: Savile Row's flag is `-or-tools`, so a
|
|
184
|
+
# config author copying the name out of the documentation writes the
|
|
185
|
+
# hyphenated form. A test that only rejects gibberish would not have caught
|
|
186
|
+
# the near-miss that matters.
|
|
187
|
+
solver_type: savilerow
|
|
188
|
+
command: savilerow
|
|
189
|
+
extensions:
|
|
190
|
+
- .eprime
|
|
191
|
+
default_timeout: null
|
|
192
|
+
backend: or-tools
|
|
193
|
+
default_tune: default
|
|
194
|
+
tune:
|
|
195
|
+
default:
|
|
196
|
+
# Carries sat_args too, which is what makes the silent-fallback failure
|
|
197
|
+
# possible at all: without them the mistake would surface as a missing
|
|
198
|
+
# arg-set rather than as a wrong-but-working command line.
|
|
199
|
+
sat_args:
|
|
200
|
+
- "-run-solver"
|
|
201
|
+
- "-sat"
|
|
202
|
+
ortools_args:
|
|
203
|
+
- "-run-solver"
|
|
204
|
+
- "-or-tools"
|
|
205
|
+
description: deliberately misspelled backend
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"""Backend dispatch in ``build_savilerow_command``.
|
|
2
|
+
|
|
3
|
+
Savile Row drives five backends: a SAT solver, Minion, and three FlatZinc solvers
|
|
4
|
+
(OR-Tools CP-SAT, Gecode, Chuffed). Which one a config selects, which tune key
|
|
5
|
+
holds its arguments, how its time limit is spelled and in what unit, and which
|
|
6
|
+
flag names its binary all hang off one ``backend:`` key, so the dispatch is worth
|
|
7
|
+
pinning in one place -- including the two backwards-compatibility properties a
|
|
8
|
+
new backend could quietly break: a config with NO ``backend:`` key must still
|
|
9
|
+
behave as the SAT backend, and Minion's argv must not move.
|
|
10
|
+
|
|
11
|
+
The unit conversions are the reason several of these tests exist. Gecode and
|
|
12
|
+
Chuffed both take their limit in milliseconds and neither says so in the flag
|
|
13
|
+
name, so an unscaled value is not a crash but a solver that gives up in 30ms and
|
|
14
|
+
reports the instance as hard.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import pytest
|
|
18
|
+
|
|
19
|
+
from solver_support.model_registry import ModelRegistry, ModelRegistryError
|
|
20
|
+
|
|
21
|
+
MODEL = "model.eprime"
|
|
22
|
+
PARAM = "instance.param"
|
|
23
|
+
|
|
24
|
+
#: Every backend the builder knows, with the arg it is expected to emit.
|
|
25
|
+
ALL_BACKENDS = [
|
|
26
|
+
("srsat", "-sat"),
|
|
27
|
+
("srminion", "-run-solver"),
|
|
28
|
+
("srortools", "-or-tools"),
|
|
29
|
+
("srgecode", "-gecode"),
|
|
30
|
+
("srchuffed", "-chuffed"),
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def build(registry, solver, **kwargs):
|
|
35
|
+
return registry.build_savilerow_command(MODEL, PARAM, solver_name=solver, **kwargs)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def solver_options(cmd):
|
|
39
|
+
"""The values of every ``-solver-options`` flag in a command."""
|
|
40
|
+
return [cmd[i + 1] for i, a in enumerate(cmd) if a == "-solver-options"]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@pytest.fixture
|
|
44
|
+
def registry(fixture_registry):
|
|
45
|
+
return ModelRegistry()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class TestBackendArgSets:
|
|
49
|
+
"""Each backend takes its arguments from its own ``<backend>_args`` key."""
|
|
50
|
+
|
|
51
|
+
@pytest.mark.parametrize("solver,expected", ALL_BACKENDS)
|
|
52
|
+
def test_backend_uses_its_own_arg_set(self, registry, solver, expected):
|
|
53
|
+
assert expected in build(registry, solver)
|
|
54
|
+
|
|
55
|
+
def test_sat_config_without_backend_key_still_uses_sat_args(self, registry):
|
|
56
|
+
# The regression that matters: every pre-existing SAT config omits
|
|
57
|
+
# `backend:` entirely, so the default must remain the SAT arm.
|
|
58
|
+
cmd = build(registry, "srsat")
|
|
59
|
+
assert "-sat" in cmd
|
|
60
|
+
assert "-or-tools" not in cmd
|
|
61
|
+
|
|
62
|
+
def test_minion_argv_unchanged(self, registry):
|
|
63
|
+
cmd = build(registry, "srminion")
|
|
64
|
+
assert cmd == ["savilerow", "-run-solver", MODEL, PARAM]
|
|
65
|
+
|
|
66
|
+
def test_missing_arg_set_for_backend_raises(self, registry):
|
|
67
|
+
# `kissat` in the fixture config has no tune presets at all.
|
|
68
|
+
with pytest.raises(ModelRegistryError, match="sat_args"):
|
|
69
|
+
build(registry, "kissat")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class TestUnknownBackend:
|
|
73
|
+
"""A misspelled backend is an error, not a quiet fallback to SAT."""
|
|
74
|
+
|
|
75
|
+
def test_unknown_backend_raises(self, registry):
|
|
76
|
+
# `srbadbackend` says `backend: or-tools` -- the hyphenated spelling of
|
|
77
|
+
# Savile Row's own flag, and so the near-miss a config author is most
|
|
78
|
+
# likely to write -- and carries sat_args, so the lenient path would have
|
|
79
|
+
# built a working SAT command line: a run that succeeds while measuring
|
|
80
|
+
# the wrong solver.
|
|
81
|
+
with pytest.raises(ModelRegistryError, match="unknown backend 'or-tools'"):
|
|
82
|
+
build(registry, "srbadbackend")
|
|
83
|
+
|
|
84
|
+
def test_error_lists_the_known_backends(self, registry):
|
|
85
|
+
with pytest.raises(ModelRegistryError, match="chuffed, gecode, minion"):
|
|
86
|
+
build(registry, "srbadbackend")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class TestTimeoutDialects:
|
|
90
|
+
"""Each backend spells its own time limit, in its own unit."""
|
|
91
|
+
|
|
92
|
+
def test_sat_takes_seconds(self, registry):
|
|
93
|
+
assert solver_options(build(registry, "srsat", timeout=45)) == ["--time=45"]
|
|
94
|
+
|
|
95
|
+
def test_ortools_takes_seconds(self, registry):
|
|
96
|
+
assert solver_options(build(registry, "srortools", timeout=45)) == [
|
|
97
|
+
"--time_limit=45"]
|
|
98
|
+
|
|
99
|
+
def test_gecode_takes_milliseconds(self, registry):
|
|
100
|
+
assert solver_options(build(registry, "srgecode", timeout=45)) == [
|
|
101
|
+
"-time 45000"]
|
|
102
|
+
|
|
103
|
+
def test_chuffed_takes_milliseconds(self, registry):
|
|
104
|
+
assert solver_options(build(registry, "srchuffed", timeout=45)) == [
|
|
105
|
+
"--time-out 45000"]
|
|
106
|
+
|
|
107
|
+
def test_minion_takes_no_solver_option(self, registry):
|
|
108
|
+
cmd = build(registry, "srminion", timeout=45)
|
|
109
|
+
assert solver_options(cmd) == []
|
|
110
|
+
assert cmd[cmd.index("-timelimit") + 1] == "45"
|
|
111
|
+
|
|
112
|
+
@pytest.mark.parametrize("solver,_", ALL_BACKENDS)
|
|
113
|
+
def test_savilerow_timelimit_is_always_seconds(self, registry, solver, _):
|
|
114
|
+
# SR's own -timelimit covers translation plus solving and never scales,
|
|
115
|
+
# whatever unit the backend solver happens to speak.
|
|
116
|
+
cmd = build(registry, solver, timeout=45)
|
|
117
|
+
assert cmd[cmd.index("-timelimit") + 1] == "45"
|
|
118
|
+
|
|
119
|
+
@pytest.mark.parametrize("solver,_", ALL_BACKENDS)
|
|
120
|
+
def test_no_timeout_emits_no_limit(self, registry, solver, _):
|
|
121
|
+
cmd = build(registry, solver)
|
|
122
|
+
assert "-timelimit" not in cmd
|
|
123
|
+
assert solver_options(cmd) == []
|
|
124
|
+
|
|
125
|
+
def test_config_may_override_the_dialect(self, registry):
|
|
126
|
+
# The flag name is the config's to change; the unit stays the solver's,
|
|
127
|
+
# so the override is still scaled to milliseconds.
|
|
128
|
+
assert solver_options(build(registry, "srgecodebin", timeout=45)) == [
|
|
129
|
+
"-overall-time 45000"]
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class TestObjectiveRecording:
|
|
133
|
+
"""Intermediate-objective recording is a SAT facility."""
|
|
134
|
+
|
|
135
|
+
def test_sat_records_intermediate_objectives(self, registry):
|
|
136
|
+
cmd = build(registry, "srsat", objective_file_path="obj.txt")
|
|
137
|
+
assert "-record-intermediate-objective-values" in cmd
|
|
138
|
+
assert cmd[cmd.index("-record-intermediate-objective-values") + 1] == "obj.txt"
|
|
139
|
+
|
|
140
|
+
@pytest.mark.parametrize("solver", ["srminion", "srortools", "srgecode",
|
|
141
|
+
"srchuffed"])
|
|
142
|
+
def test_other_backends_do_not_record(self, registry, solver):
|
|
143
|
+
# They optimise internally, so Savile Row is not running the loop and has
|
|
144
|
+
# no intermediate values to see.
|
|
145
|
+
cmd = build(registry, solver, objective_file_path="obj.txt")
|
|
146
|
+
assert "-record-intermediate-objective-values" not in cmd
|
|
147
|
+
|
|
148
|
+
@pytest.mark.parametrize("solver,expected", [
|
|
149
|
+
("srsat", True),
|
|
150
|
+
("kissat", True), # no `backend:` key -- still the SAT arm
|
|
151
|
+
("srminion", False),
|
|
152
|
+
("srortools", False),
|
|
153
|
+
("srgecode", False),
|
|
154
|
+
("srchuffed", False),
|
|
155
|
+
("upffd", False), # not a savilerow solver at all
|
|
156
|
+
])
|
|
157
|
+
def test_records_intermediate_objectives_query(self, registry, solver, expected):
|
|
158
|
+
# The horizon loop asks this before naming an objective file, so that it
|
|
159
|
+
# does not name and clear a file the backend will never write.
|
|
160
|
+
assert registry.records_intermediate_objectives(solver) is expected
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class TestSolverBinary:
|
|
164
|
+
"""The solver binary is named only when pinned, with that backend's flag."""
|
|
165
|
+
|
|
166
|
+
@pytest.mark.parametrize("solver,_", ALL_BACKENDS)
|
|
167
|
+
def test_omitted_when_unset(self, registry, solver, _):
|
|
168
|
+
# Leaving it to Savile Row's default keeps one machine's build out of a
|
|
169
|
+
# shared registry.
|
|
170
|
+
cmd = build(registry, solver)
|
|
171
|
+
assert not any(a.endswith("-bin") for a in cmd)
|
|
172
|
+
|
|
173
|
+
def test_ortools_uses_the_standard_flatzinc_flag(self, registry):
|
|
174
|
+
cmd = build(registry, "srortoolsbin")
|
|
175
|
+
assert cmd[cmd.index("-fzn-bin") + 1] == "fzn-ortools"
|
|
176
|
+
|
|
177
|
+
def test_gecode_uses_its_own_flag(self, registry):
|
|
178
|
+
# The point of the pair: a per-backend flag, not one hardcoded name.
|
|
179
|
+
cmd = build(registry, "srgecodebin")
|
|
180
|
+
assert "-fzn-bin" not in cmd
|
|
181
|
+
assert cmd[cmd.index("-gecode-bin") + 1] == "fzn-gecode-6"
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class TestBackendTable:
|
|
185
|
+
"""The table is the single place a backend is described."""
|
|
186
|
+
|
|
187
|
+
def test_every_backend_is_fully_specified(self):
|
|
188
|
+
required = {"bin_option", "timeout_option", "timeout_scale",
|
|
189
|
+
"records_objectives"}
|
|
190
|
+
for name, spec in ModelRegistry.SAVILEROW_BACKENDS.items():
|
|
191
|
+
assert set(spec) == required, name
|
|
192
|
+
|
|
193
|
+
def test_only_the_sat_arm_records_objectives(self):
|
|
194
|
+
recording = {n for n, s in ModelRegistry.SAVILEROW_BACKENDS.items()
|
|
195
|
+
if s["records_objectives"]}
|
|
196
|
+
assert recording == {"sat"}
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
@pytest.mark.parametrize("solver,_", ALL_BACKENDS)
|
|
200
|
+
def test_model_and_param_come_last(registry, solver, _):
|
|
201
|
+
"""Savile Row takes the model and param as trailing positionals."""
|
|
202
|
+
cmd = build(registry, solver, timeout=10, objective_file_path="obj.txt")
|
|
203
|
+
assert cmd[-2:] == [MODEL, PARAM]
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
# Synthetic model registry for the core's own tests.
|
|
2
|
-
#
|
|
3
|
-
# The core ships without a registry -- a consumer injects the path to its own
|
|
4
|
-
# models.yaml (see docs/injection-api.md). Tests that reach the registry wire
|
|
5
|
-
# THIS file instead, so the core is exercised against a config it owns rather
|
|
6
|
-
# than against whichever consumer happens to be installed beside it. Keep it
|
|
7
|
-
# minimal: add only what a test actually reads, and keep the shape faithful to a
|
|
8
|
-
# real consumer config (that shape is the contract the loader and the accessors
|
|
9
|
-
# implement).
|
|
10
|
-
|
|
11
|
-
models:
|
|
12
|
-
# A constructed UPF model: built in Python, solved through the UPF driver.
|
|
13
|
-
upf:
|
|
14
|
-
path: upf.py
|
|
15
|
-
solver: upffd
|
|
16
|
-
detection_type: upf
|
|
17
|
-
format-name: upf
|
|
18
|
-
proves-unsat: true
|
|
19
|
-
description: constructed UPF model, solved by Fast Downward via UPF
|
|
20
|
-
|
|
21
|
-
# An optimisation model whose objective is recorded in the solution file, so
|
|
22
|
-
# the horizon scan compares objectives between horizons.
|
|
23
|
-
scored:
|
|
24
|
-
path: models/Scored.eprime
|
|
25
|
-
solver: kissat
|
|
26
|
-
detection_type: scored
|
|
27
|
-
format-name: compact
|
|
28
|
-
objective-in-solution: true
|
|
29
|
-
description: optimisation model carrying its objective in the solution
|
|
30
|
-
|
|
31
|
-
solvers:
|
|
32
|
-
upffd:
|
|
33
|
-
# Fast Downward through the Unified Planning Framework. The engine and the
|
|
34
|
-
# FD search config are BOTH resolved from the effective --tune, which is
|
|
35
|
-
# what UpfPlanSolver turns into `-s <engine>` and `--fd-search <search>`.
|
|
36
|
-
solver_type: upfplan
|
|
37
|
-
command: upfplan
|
|
38
|
-
extensions:
|
|
39
|
-
- .pddl
|
|
40
|
-
default_timeout: 600
|
|
41
|
-
engine_name: fast-downward
|
|
42
|
-
default_tune: optimal
|
|
43
|
-
tune:
|
|
44
|
-
optimal:
|
|
45
|
-
fd_search: "astar(blind())"
|
|
46
|
-
description: cost-optimal blind A*
|
|
47
|
-
satisficing:
|
|
48
|
-
fd_search: "lazy_greedy([ff()])"
|
|
49
|
-
description: satisficing greedy best-first with FF
|
|
50
|
-
|
|
51
|
-
kissat:
|
|
52
|
-
solver_type: savilerow
|
|
53
|
-
command: savilerow
|
|
54
|
-
extensions:
|
|
55
|
-
- .eprime
|
|
56
|
-
default_timeout: 600
|
|
57
|
-
description: SAT backend driven through Savile Row
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|