solver-support 0.1.1__py3-none-any.whl
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/__init__.py +132 -0
- solver_support/command_builders.py +334 -0
- solver_support/file_management.py +243 -0
- solver_support/global_state.py +62 -0
- solver_support/horizon_utils.py +350 -0
- solver_support/model_registry.py +792 -0
- solver_support/objective.py +92 -0
- solver_support/run_summary.py +338 -0
- solver_support/solver_base.py +140 -0
- solver_support/solver_enhsp.py +101 -0
- solver_support/solver_fd.py +261 -0
- solver_support/solver_horizon.py +634 -0
- solver_support/solver_runner.py +702 -0
- solver_support/solver_stats.py +724 -0
- solver_support/solver_upfplan.py +168 -0
- solver_support/utilities.py +146 -0
- solver_support-0.1.1.dist-info/METADATA +120 -0
- solver_support-0.1.1.dist-info/RECORD +21 -0
- solver_support-0.1.1.dist-info/WHEEL +5 -0
- solver_support-0.1.1.dist-info/licenses/LICENSE +29 -0
- solver_support-0.1.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""solver_support -- a generic solver pipeline.
|
|
2
|
+
|
|
3
|
+
Domain-agnostic infrastructure for driving a Conjure / Savile Row / Fast Downward
|
|
4
|
+
/ SymK / ENHSP / UPF pipeline: spawn solvers, manage phases and timeouts, name
|
|
5
|
+
files, parse solver output into timing / cost-layer stats, and run a horizon
|
|
6
|
+
loop. A consumer supplies its own model registry and plugs its domain scoring
|
|
7
|
+
in via injection; this package knows nothing about any particular problem or
|
|
8
|
+
objective.
|
|
9
|
+
|
|
10
|
+
The core ships *unwired*: no objective extractor is registered and the registry
|
|
11
|
+
has no default config path, so a bare ``import solver_support`` yields an
|
|
12
|
+
unconfigured pipeline. A consumer registers its scoring (see :mod:`objective`)
|
|
13
|
+
and its packaging paths (see :mod:`model_registry`) once at its own import time.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
__version__ = "0.1.1"
|
|
17
|
+
|
|
18
|
+
# --- model registry (loader + command builders + packaging injection) ---
|
|
19
|
+
from .model_registry import (
|
|
20
|
+
ModelRegistry,
|
|
21
|
+
ModelRegistryError,
|
|
22
|
+
get_default_registry,
|
|
23
|
+
check_model_registry_consistency,
|
|
24
|
+
set_default_config_path,
|
|
25
|
+
set_resource_root,
|
|
26
|
+
set_user_config_finder,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# --- run configuration singleton ---
|
|
30
|
+
from .global_state import global_state, GlobalState
|
|
31
|
+
|
|
32
|
+
# --- objective-extraction injection point (a consumer registers extractors) ---
|
|
33
|
+
from . import objective
|
|
34
|
+
from .objective import (
|
|
35
|
+
set_reported_score_from_solution,
|
|
36
|
+
set_reported_score_from_stdout,
|
|
37
|
+
set_reported_score_from_timeout_solution,
|
|
38
|
+
set_raw_objective_from_solution,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# --- solvers ---
|
|
42
|
+
from .solver_base import Solver
|
|
43
|
+
from .solver_fd import FastDownwardSolver
|
|
44
|
+
from .solver_enhsp import EnhspSolver
|
|
45
|
+
from .solver_upfplan import UpfPlanSolver
|
|
46
|
+
from .solver_horizon import HorizonBasedSolver, SavileRowSolver, ConjureSolver
|
|
47
|
+
|
|
48
|
+
# --- execution, stats, run model ---
|
|
49
|
+
from .solver_runner import SolverRunner, SolverResult, SolverExecutableError
|
|
50
|
+
from .solver_stats import SolverStatsExtractor, detect_solver_from_command
|
|
51
|
+
from .run_summary import RunSummary, PhaseResult, create_run_summary
|
|
52
|
+
|
|
53
|
+
# --- file naming and horizon loop ---
|
|
54
|
+
from .file_management import (
|
|
55
|
+
SolutionFileNamer,
|
|
56
|
+
ObjectiveFileManager,
|
|
57
|
+
VisualizationFileManager,
|
|
58
|
+
compute_tune_slot,
|
|
59
|
+
compute_options_label,
|
|
60
|
+
)
|
|
61
|
+
from .horizon_utils import (
|
|
62
|
+
horizon_search,
|
|
63
|
+
calculate_horizon_timeout,
|
|
64
|
+
reconcile_horizon_bounds,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# --- generic helpers ---
|
|
68
|
+
from .utilities import (
|
|
69
|
+
setup_logger,
|
|
70
|
+
relativize_path,
|
|
71
|
+
warn,
|
|
72
|
+
set_warn_logger_name,
|
|
73
|
+
format_timestamp,
|
|
74
|
+
arrayify,
|
|
75
|
+
indices,
|
|
76
|
+
BrokenPipeHandler,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
__all__ = [
|
|
80
|
+
# registry
|
|
81
|
+
"ModelRegistry",
|
|
82
|
+
"ModelRegistryError",
|
|
83
|
+
"get_default_registry",
|
|
84
|
+
"check_model_registry_consistency",
|
|
85
|
+
"set_default_config_path",
|
|
86
|
+
"set_resource_root",
|
|
87
|
+
"set_user_config_finder",
|
|
88
|
+
# run config
|
|
89
|
+
"global_state",
|
|
90
|
+
"GlobalState",
|
|
91
|
+
# objective injection
|
|
92
|
+
"objective",
|
|
93
|
+
"set_reported_score_from_solution",
|
|
94
|
+
"set_reported_score_from_stdout",
|
|
95
|
+
"set_reported_score_from_timeout_solution",
|
|
96
|
+
"set_raw_objective_from_solution",
|
|
97
|
+
# solvers
|
|
98
|
+
"Solver",
|
|
99
|
+
"FastDownwardSolver",
|
|
100
|
+
"EnhspSolver",
|
|
101
|
+
"UpfPlanSolver",
|
|
102
|
+
"HorizonBasedSolver",
|
|
103
|
+
"SavileRowSolver",
|
|
104
|
+
"ConjureSolver",
|
|
105
|
+
# execution / stats / run model
|
|
106
|
+
"SolverRunner",
|
|
107
|
+
"SolverResult",
|
|
108
|
+
"SolverExecutableError",
|
|
109
|
+
"SolverStatsExtractor",
|
|
110
|
+
"detect_solver_from_command",
|
|
111
|
+
"RunSummary",
|
|
112
|
+
"PhaseResult",
|
|
113
|
+
"create_run_summary",
|
|
114
|
+
# file naming / horizon loop
|
|
115
|
+
"SolutionFileNamer",
|
|
116
|
+
"ObjectiveFileManager",
|
|
117
|
+
"VisualizationFileManager",
|
|
118
|
+
"compute_tune_slot",
|
|
119
|
+
"compute_options_label",
|
|
120
|
+
"horizon_search",
|
|
121
|
+
"calculate_horizon_timeout",
|
|
122
|
+
"reconcile_horizon_bounds",
|
|
123
|
+
# helpers
|
|
124
|
+
"setup_logger",
|
|
125
|
+
"relativize_path",
|
|
126
|
+
"warn",
|
|
127
|
+
"set_warn_logger_name",
|
|
128
|
+
"format_timestamp",
|
|
129
|
+
"arrayify",
|
|
130
|
+
"indices",
|
|
131
|
+
"BrokenPipeHandler",
|
|
132
|
+
]
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Solver command builders for the model registry.
|
|
3
|
+
|
|
4
|
+
The ``build_*_command`` methods assemble the argv for each solver backend (Savile
|
|
5
|
+
Row, Fast Downward / SymK, ENHSP, Conjure) from a solver's registry config and the
|
|
6
|
+
effective ``--tune`` preset. This is a distinct concern from the registry's config
|
|
7
|
+
loading and its model/solver queries, so the builders live here as a mixin that
|
|
8
|
+
:class:`ModelRegistry` inherits.
|
|
9
|
+
|
|
10
|
+
The split is behaviour-preserving: the methods reference the registry only through
|
|
11
|
+
``self`` (``get_solver_config``, ``_effective_tune``, ``is_command_available``, the
|
|
12
|
+
``_savilerow_native_warning_shown`` flag), so on the combined instance they resolve
|
|
13
|
+
exactly as before. ``ModelRegistryError`` is imported from ``model_registry``; that
|
|
14
|
+
back-import is safe because the package ``__init__`` always imports
|
|
15
|
+
``model_registry`` first, and ``model_registry`` imports this mixin only after the
|
|
16
|
+
exception is defined.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from typing import List, Optional
|
|
20
|
+
|
|
21
|
+
from .model_registry import ModelRegistryError
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class CommandBuilderMixin:
|
|
25
|
+
"""The ``build_*_command`` methods mixed into :class:`ModelRegistry`."""
|
|
26
|
+
|
|
27
|
+
def build_savilerow_command(self, model_path: str, param_path: str,
|
|
28
|
+
use_srjava: bool = False,
|
|
29
|
+
timeout: Optional[int] = None,
|
|
30
|
+
objective_file_path: Optional[str] = None,
|
|
31
|
+
extra_options: Optional[List[str]] = None,
|
|
32
|
+
solver_name: str = 'kissat',
|
|
33
|
+
tune: Optional[str] = None) -> List[str]:
|
|
34
|
+
"""
|
|
35
|
+
Build a Savile Row command with proper flags for horizon scanning.
|
|
36
|
+
|
|
37
|
+
original run script's SRARGS logic:
|
|
38
|
+
- Minion mode: just "-run-solver"
|
|
39
|
+
- SAT mode: "-run-solver -O0 -deletevars -identical-cse -sat -sat-polarity -sat-family kissat"
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
model_path: path to the model file
|
|
43
|
+
param_path: path to the parameter file
|
|
44
|
+
use_srjava: use Java savilerow instead of savilerow-native
|
|
45
|
+
timeout: override timeout in seconds (uses default_timeout if None)
|
|
46
|
+
objective_file_path: path for intermediate objective values file (default: obj.txt)
|
|
47
|
+
extra_options: additional command line options to pass to Savile Row
|
|
48
|
+
solver_name: name of the solver configuration to use (default: 'kissat').
|
|
49
|
+
The backend (kissat vs minion) is a property of this config:
|
|
50
|
+
a config with ``backend: minion`` uses its tune presets'
|
|
51
|
+
minion_args and Minion's timeout dialect, otherwise the kissat
|
|
52
|
+
SAT arg-set is used.
|
|
53
|
+
tune: name of a ``tune:`` preset to apply instead of the config's
|
|
54
|
+
``default_tune`` (kissat: 'safe'/'bare'/'srdefault'/'gac';
|
|
55
|
+
minion: 'bare'). The preset supplies this backend's arg-set
|
|
56
|
+
(sat_args for kissat, minion_args for minion). A name that is
|
|
57
|
+
unknown or lacks this backend's arg key falls back to
|
|
58
|
+
``default_tune``. This is the savilerow arm of the same ``--tune``
|
|
59
|
+
dial fd uses for its search presets.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
Complete command as list of strings.
|
|
63
|
+
|
|
64
|
+
Raises:
|
|
65
|
+
ModelRegistryError: If savilerow solver not found in config, or the
|
|
66
|
+
resolved tune has no arg-set for this backend.
|
|
67
|
+
"""
|
|
68
|
+
solver_config = self.get_solver_config(solver_name)
|
|
69
|
+
|
|
70
|
+
# The backend solver (kissat SAT vs Minion CP) is declared by the config,
|
|
71
|
+
# not passed in: `--solver minion` selects the minion backend config. This
|
|
72
|
+
# replaced the old cross-cutting `--minion` boolean.
|
|
73
|
+
use_minion = solver_config.get('backend') == 'minion'
|
|
74
|
+
|
|
75
|
+
# Choose command variant
|
|
76
|
+
if use_srjava:
|
|
77
|
+
# User explicitly requested Java version
|
|
78
|
+
command = solver_config['command']
|
|
79
|
+
else:
|
|
80
|
+
# Try to use native version if available
|
|
81
|
+
native_command = solver_config.get('command_native', 'savilerow-native')
|
|
82
|
+
if self.is_command_available(native_command):
|
|
83
|
+
command = native_command
|
|
84
|
+
else:
|
|
85
|
+
# Fall back to Java version with warning (only warn once)
|
|
86
|
+
if not self._savilerow_native_warning_shown:
|
|
87
|
+
import logging
|
|
88
|
+
logger = logging.getLogger(__name__)
|
|
89
|
+
logger.warning(f"savilerow-native not found in PATH, falling back to Java version (savilerow)")
|
|
90
|
+
self._savilerow_native_warning_shown = True
|
|
91
|
+
command = solver_config['command']
|
|
92
|
+
|
|
93
|
+
# Choose the argument set from the active tune preset. kissat presets
|
|
94
|
+
# carry sat_args, minion presets carry minion_args; a tune that is
|
|
95
|
+
# unknown or lacks this backend's key falls back to default_tune (the
|
|
96
|
+
# lenient behaviour the fd builder uses too). The model-default tune is
|
|
97
|
+
# resolved by the caller, so `tune` here is already the effective name.
|
|
98
|
+
arg_key = 'minion_args' if use_minion else 'sat_args'
|
|
99
|
+
tunes = solver_config.get('tune', {})
|
|
100
|
+
tune_name = self._effective_tune(solver_config, tune, arg_key)
|
|
101
|
+
preset = tunes.get(tune_name, {})
|
|
102
|
+
if arg_key not in preset:
|
|
103
|
+
raise ModelRegistryError(
|
|
104
|
+
f"Solver '{solver_name}' tune '{tune_name}' has no {arg_key}"
|
|
105
|
+
)
|
|
106
|
+
args = preset[arg_key].copy()
|
|
107
|
+
if not use_minion:
|
|
108
|
+
# Add objective recording arguments only when needed
|
|
109
|
+
if objective_file_path is not None:
|
|
110
|
+
args.extend(["-record-intermediate-objective-values", objective_file_path])
|
|
111
|
+
|
|
112
|
+
# Add timeout arguments
|
|
113
|
+
if timeout is None:
|
|
114
|
+
timeout = solver_config['default_timeout']
|
|
115
|
+
|
|
116
|
+
# Different timeout handling for minion vs SAT solver
|
|
117
|
+
if timeout is not None:
|
|
118
|
+
if use_minion:
|
|
119
|
+
# For minion, just use -timelimit (no solver-options needed)
|
|
120
|
+
args.extend(["-timelimit", str(timeout)])
|
|
121
|
+
else:
|
|
122
|
+
# For SAT solver (kissat), use both -solver-options and -timelimit
|
|
123
|
+
args.extend([
|
|
124
|
+
"-solver-options", f"--time={timeout}",
|
|
125
|
+
"-timelimit", str(timeout)
|
|
126
|
+
])
|
|
127
|
+
|
|
128
|
+
# Add extra options if provided
|
|
129
|
+
if extra_options:
|
|
130
|
+
args.extend(extra_options)
|
|
131
|
+
|
|
132
|
+
# Build complete command
|
|
133
|
+
cmd = [command] + args + [str(model_path), str(param_path)]
|
|
134
|
+
|
|
135
|
+
return cmd
|
|
136
|
+
|
|
137
|
+
def build_fd_command(self, model_path: str, param_path: str,
|
|
138
|
+
anytime: bool = False, timeout: Optional[int] = None,
|
|
139
|
+
tune: Optional[str] = None,
|
|
140
|
+
extra_options: Optional[List[str]] = None,
|
|
141
|
+
solver_name: str = 'fd') -> List[str]:
|
|
142
|
+
"""
|
|
143
|
+
Build a Fast Downward command with proper flags.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
model_path: Path to the PDDL domain file.
|
|
147
|
+
param_path: Path to the PDDL problem file.
|
|
148
|
+
anytime: Whether to run the anytime portfolio (the ``soup`` tune).
|
|
149
|
+
When true the pre-file ``--alias`` form is emitted and ``tune``
|
|
150
|
+
is ignored; the caller decides this via ``is_anytime_tune`` (the
|
|
151
|
+
tune's ``regime: anytime``), not the model name.
|
|
152
|
+
timeout: Override timeout in seconds (uses default_timeout if None).
|
|
153
|
+
tune: Name of a ``tune:`` preset to apply instead of the config's
|
|
154
|
+
``default_tune`` (fd: 'hmax'/'ff'/'lmcut'/'verbose';
|
|
155
|
+
symk: 'fw'/'bw'/'bd'). A search-bearing preset becomes the
|
|
156
|
+
post-file ``--search`` argument; the ``soup`` preset (an
|
|
157
|
+
``alias``) is the pre-file portfolio. An unknown or searchless
|
|
158
|
+
name falls back to ``default_tune``. This is the fd-family arm
|
|
159
|
+
of the same ``--tune`` dial savilerow uses for its arg-sets.
|
|
160
|
+
extra_options: raw ``--options`` tokens passed straight through,
|
|
161
|
+
appended after all generated arguments (the runner does not
|
|
162
|
+
interpret them - the caller is responsible for their placement
|
|
163
|
+
being valid for Fast Downward's command line).
|
|
164
|
+
solver_name: Which fd-type solver config to use ('fd' or 'symk').
|
|
165
|
+
SymK is a Fast Downward fork, so it shares this builder; the
|
|
166
|
+
config supplies the command (fd vs symk) and its tune presets.
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
Complete command as list of strings.
|
|
170
|
+
|
|
171
|
+
Raises:
|
|
172
|
+
ModelRegistryError: If the solver is not found in config, or has no
|
|
173
|
+
usable tune preset for the requested mode.
|
|
174
|
+
"""
|
|
175
|
+
solver_config = self.get_solver_config(solver_name)
|
|
176
|
+
|
|
177
|
+
# Add timeout argument
|
|
178
|
+
if timeout is None:
|
|
179
|
+
timeout = solver_config['default_timeout']
|
|
180
|
+
|
|
181
|
+
tunes = solver_config.get('tune', {})
|
|
182
|
+
|
|
183
|
+
# Build command with correct argument order for FD
|
|
184
|
+
if anytime:
|
|
185
|
+
# Anytime portfolio: the pre-file --alias from the soup tune preset,
|
|
186
|
+
# e.g. fd --alias seq-sat-fdss-2018 --search-time-limit <timeout>
|
|
187
|
+
# domain.pddl problem.pddl. `soup` is currently the only anytime fd
|
|
188
|
+
# tune, so it is the one resolved here.
|
|
189
|
+
alias = tunes.get('soup', {}).get('alias')
|
|
190
|
+
if not alias:
|
|
191
|
+
raise ModelRegistryError(
|
|
192
|
+
f"Solver '{solver_name}' has no 'soup' tune preset for anytime mode"
|
|
193
|
+
)
|
|
194
|
+
pre_file_args = ["--alias", alias]
|
|
195
|
+
if timeout is not None:
|
|
196
|
+
pre_file_args.extend(["--search-time-limit", str(timeout)])
|
|
197
|
+
cmd = [solver_config['command']] + pre_file_args + [str(model_path), str(param_path)]
|
|
198
|
+
else:
|
|
199
|
+
# Standard mode: the post-file --search string from a tune preset,
|
|
200
|
+
# e.g. fd --search-time-limit <timeout> domain.pddl problem.pddl
|
|
201
|
+
# --search "astar(blind())". A requested tune that doesn't exist or
|
|
202
|
+
# carries no search string (e.g. soup) falls back to default_tune.
|
|
203
|
+
name = self._effective_tune(solver_config, tune, 'search')
|
|
204
|
+
search = tunes.get(name, {}).get('search')
|
|
205
|
+
if not search:
|
|
206
|
+
raise ModelRegistryError(
|
|
207
|
+
f"Solver '{solver_name}' has no search string for tune '{name}'"
|
|
208
|
+
)
|
|
209
|
+
pre_file_args = []
|
|
210
|
+
if timeout is not None:
|
|
211
|
+
pre_file_args = ["--search-time-limit", str(timeout)]
|
|
212
|
+
post_file_args = ["--search", search]
|
|
213
|
+
cmd = [solver_config['command']] + pre_file_args + [str(model_path), str(param_path)] + post_file_args
|
|
214
|
+
|
|
215
|
+
# Raw --options passthrough, appended last (uninterpreted).
|
|
216
|
+
if extra_options:
|
|
217
|
+
cmd.extend(extra_options)
|
|
218
|
+
|
|
219
|
+
return cmd
|
|
220
|
+
|
|
221
|
+
def build_enhsp_command(self, model_path: str, param_path: str,
|
|
222
|
+
plan_path: str, tune: Optional[str] = None,
|
|
223
|
+
extra_options: Optional[List[str]] = None,
|
|
224
|
+
solver_name: str = 'enhsp') -> List[str]:
|
|
225
|
+
"""
|
|
226
|
+
Build an ENHSP command.
|
|
227
|
+
|
|
228
|
+
ENHSP takes the domain and problem as ``-o``/``-f`` and writes its plan
|
|
229
|
+
(the same ``(action args)`` form as Fast Downward's ``sas_plan``) to the
|
|
230
|
+
``-sp`` file. The active tune preset selects the ``-planner``
|
|
231
|
+
preconfiguration (sat-* satisficing / opt-* metric-optimal); an unknown
|
|
232
|
+
or planner-less ``tune`` falls back to the config's ``default_tune``,
|
|
233
|
+
the same leniency as the fd/savilerow builders. ENHSP has no internal
|
|
234
|
+
time limit outside anytime mode, so no timeout flag is emitted - the
|
|
235
|
+
SolverRunner's external timeout terminates it.
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
model_path: Path to the PDDL domain file.
|
|
239
|
+
param_path: Path to the PDDL problem file.
|
|
240
|
+
plan_path: Where ENHSP should write the plan (the ``-sp`` argument).
|
|
241
|
+
tune: Name of a ``tune:`` preset (a planner config) to apply instead
|
|
242
|
+
of the config's ``default_tune``.
|
|
243
|
+
extra_options: raw ``--options`` tokens passed straight through,
|
|
244
|
+
appended after the generated arguments (uninterpreted).
|
|
245
|
+
solver_name: Which enhsp-type solver config to use.
|
|
246
|
+
|
|
247
|
+
Returns:
|
|
248
|
+
Complete command as list of strings.
|
|
249
|
+
|
|
250
|
+
Raises:
|
|
251
|
+
ModelRegistryError: If the solver has no usable planner tune.
|
|
252
|
+
"""
|
|
253
|
+
solver_config = self.get_solver_config(solver_name)
|
|
254
|
+
tunes = solver_config.get('tune', {})
|
|
255
|
+
name = self._effective_tune(solver_config, tune, 'planner')
|
|
256
|
+
planner = tunes.get(name, {}).get('planner')
|
|
257
|
+
if not planner:
|
|
258
|
+
raise ModelRegistryError(
|
|
259
|
+
f"Solver '{solver_name}' has no planner for tune '{name}'"
|
|
260
|
+
)
|
|
261
|
+
cmd = [
|
|
262
|
+
solver_config['command'],
|
|
263
|
+
"-o", str(model_path),
|
|
264
|
+
"-f", str(param_path),
|
|
265
|
+
"-planner", planner,
|
|
266
|
+
"-sp", str(plan_path),
|
|
267
|
+
]
|
|
268
|
+
if extra_options:
|
|
269
|
+
cmd.extend(extra_options)
|
|
270
|
+
return cmd
|
|
271
|
+
|
|
272
|
+
def build_conjure_command(self, action: str, **kwargs) -> List[str]:
|
|
273
|
+
"""
|
|
274
|
+
Build a Conjure command for different actions.
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
action: Action type ('modelling', 'translate-parameter', 'translate-solution')
|
|
278
|
+
**kwargs: Action-specific arguments:
|
|
279
|
+
- modelling: model_path
|
|
280
|
+
- translate-parameter: essence_param, eprime
|
|
281
|
+
- translate-solution: essence_param, eprime, eprime_solution, essence_solution
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
Complete command as list of strings.
|
|
285
|
+
|
|
286
|
+
Raises:
|
|
287
|
+
ModelRegistryError: If conjure solver not found in config or invalid action.
|
|
288
|
+
"""
|
|
289
|
+
solver_config = self.get_solver_config('conjure')
|
|
290
|
+
|
|
291
|
+
# Get base arguments for the action
|
|
292
|
+
action_key = f"{action.replace('-', '_')}_args"
|
|
293
|
+
if action_key not in solver_config:
|
|
294
|
+
raise ModelRegistryError(f"Unknown conjure action: {action}")
|
|
295
|
+
|
|
296
|
+
args = solver_config[action_key].copy()
|
|
297
|
+
|
|
298
|
+
# Build action-specific arguments
|
|
299
|
+
if action == 'modelling':
|
|
300
|
+
model_path = kwargs.get('model_path')
|
|
301
|
+
if not model_path:
|
|
302
|
+
raise ModelRegistryError("model_path required for conjure modelling")
|
|
303
|
+
cmd = [solver_config['command']] + args + [str(model_path)]
|
|
304
|
+
|
|
305
|
+
elif action == 'translate-parameter':
|
|
306
|
+
essence_param = kwargs.get('essence_param')
|
|
307
|
+
eprime = kwargs.get('eprime')
|
|
308
|
+
if not essence_param or not eprime:
|
|
309
|
+
raise ModelRegistryError("essence_param and eprime required for translate-parameter")
|
|
310
|
+
cmd = [solver_config['command']] + args + [
|
|
311
|
+
f"--essence-param={essence_param}",
|
|
312
|
+
f"--eprime={eprime}"
|
|
313
|
+
]
|
|
314
|
+
|
|
315
|
+
elif action == 'translate-solution':
|
|
316
|
+
essence_param = kwargs.get('essence_param')
|
|
317
|
+
eprime = kwargs.get('eprime')
|
|
318
|
+
eprime_solution = kwargs.get('eprime_solution')
|
|
319
|
+
essence_solution = kwargs.get('essence_solution')
|
|
320
|
+
if not all([essence_param, eprime, eprime_solution, essence_solution]):
|
|
321
|
+
raise ModelRegistryError(
|
|
322
|
+
"essence_param, eprime, eprime_solution, and essence_solution required for translate-solution"
|
|
323
|
+
)
|
|
324
|
+
cmd = [solver_config['command']] + args + [
|
|
325
|
+
f"--essence-param={essence_param}",
|
|
326
|
+
f"--eprime={eprime}",
|
|
327
|
+
f"--eprime-solution={eprime_solution}",
|
|
328
|
+
f"--essence-solution={essence_solution}"
|
|
329
|
+
]
|
|
330
|
+
|
|
331
|
+
else:
|
|
332
|
+
raise ModelRegistryError(f"Unknown conjure action: {action}")
|
|
333
|
+
|
|
334
|
+
return cmd
|