solver-support 0.3.1__tar.gz → 0.3.2__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.3.1/solver_support.egg-info → solver_support-0.3.2}/PKG-INFO +1 -1
- {solver_support-0.3.1 → solver_support-0.3.2}/docs/timing-model.md +18 -1
- {solver_support-0.3.1 → solver_support-0.3.2}/pyproject.toml +1 -1
- {solver_support-0.3.1 → solver_support-0.3.2}/solver_support/solver_stats.py +27 -6
- {solver_support-0.3.1 → solver_support-0.3.2/solver_support.egg-info}/PKG-INFO +1 -1
- {solver_support-0.3.1 → solver_support-0.3.2}/tests/test_solver_stats.py +41 -0
- {solver_support-0.3.1 → solver_support-0.3.2}/LICENSE +0 -0
- {solver_support-0.3.1 → solver_support-0.3.2}/MANIFEST.in +0 -0
- {solver_support-0.3.1 → solver_support-0.3.2}/README.md +0 -0
- {solver_support-0.3.1 → solver_support-0.3.2}/docs/injection-api.md +0 -0
- {solver_support-0.3.1 → solver_support-0.3.2}/docs/solver-runner.md +0 -0
- {solver_support-0.3.1 → solver_support-0.3.2}/setup.cfg +0 -0
- {solver_support-0.3.1 → solver_support-0.3.2}/solver_support/__init__.py +0 -0
- {solver_support-0.3.1 → solver_support-0.3.2}/solver_support/command_builders.py +0 -0
- {solver_support-0.3.1 → solver_support-0.3.2}/solver_support/file_management.py +0 -0
- {solver_support-0.3.1 → solver_support-0.3.2}/solver_support/global_state.py +0 -0
- {solver_support-0.3.1 → solver_support-0.3.2}/solver_support/horizon_utils.py +0 -0
- {solver_support-0.3.1 → solver_support-0.3.2}/solver_support/model_registry.py +0 -0
- {solver_support-0.3.1 → solver_support-0.3.2}/solver_support/objective.py +0 -0
- {solver_support-0.3.1 → solver_support-0.3.2}/solver_support/run_summary.py +0 -0
- {solver_support-0.3.1 → solver_support-0.3.2}/solver_support/solver_base.py +0 -0
- {solver_support-0.3.1 → solver_support-0.3.2}/solver_support/solver_enhsp.py +0 -0
- {solver_support-0.3.1 → solver_support-0.3.2}/solver_support/solver_fd.py +0 -0
- {solver_support-0.3.1 → solver_support-0.3.2}/solver_support/solver_horizon.py +0 -0
- {solver_support-0.3.1 → solver_support-0.3.2}/solver_support/solver_runner.py +0 -0
- {solver_support-0.3.1 → solver_support-0.3.2}/solver_support/solver_upfplan.py +0 -0
- {solver_support-0.3.1 → solver_support-0.3.2}/solver_support/utilities.py +0 -0
- {solver_support-0.3.1 → solver_support-0.3.2}/solver_support.egg-info/SOURCES.txt +0 -0
- {solver_support-0.3.1 → solver_support-0.3.2}/solver_support.egg-info/dependency_links.txt +0 -0
- {solver_support-0.3.1 → solver_support-0.3.2}/solver_support.egg-info/not-zip-safe +0 -0
- {solver_support-0.3.1 → solver_support-0.3.2}/solver_support.egg-info/requires.txt +0 -0
- {solver_support-0.3.1 → solver_support-0.3.2}/solver_support.egg-info/top_level.txt +0 -0
- {solver_support-0.3.1 → solver_support-0.3.2}/tests/conftest.py +0 -0
- {solver_support-0.3.1 → solver_support-0.3.2}/tests/fixtures/models.yaml +0 -0
- {solver_support-0.3.1 → solver_support-0.3.2}/tests/test_command_builders.py +0 -0
- {solver_support-0.3.1 → solver_support-0.3.2}/tests/test_helpers.py +0 -0
- {solver_support-0.3.1 → solver_support-0.3.2}/tests/test_horizon_utils.py +0 -0
- {solver_support-0.3.1 → solver_support-0.3.2}/tests/test_import.py +0 -0
- {solver_support-0.3.1 → solver_support-0.3.2}/tests/test_registry_dump.py +0 -0
- {solver_support-0.3.1 → solver_support-0.3.2}/tests/test_run_summary.py +0 -0
- {solver_support-0.3.1 → solver_support-0.3.2}/tests/test_run_summary_success.py +0 -0
- {solver_support-0.3.1 → solver_support-0.3.2}/tests/test_savilerow_info_recording.py +0 -0
- {solver_support-0.3.1 → solver_support-0.3.2}/tests/test_solution_naming.py +0 -0
- {solver_support-0.3.1 → solver_support-0.3.2}/tests/test_solver_runner.py +0 -0
- {solver_support-0.3.1 → solver_support-0.3.2}/tests/test_solvers.py +0 -0
|
@@ -193,7 +193,7 @@ establishes nothing. `SolverStatsExtractor.savilerow_verdict` separates them and
|
|
|
193
193
|
| verdict | evidence | meaning |
|
|
194
194
|
|---|---|---|
|
|
195
195
|
| `sat` / `unsat` | `SolverSatisfiable` present | decided (`proven_optimal` too, on optimisation models) |
|
|
196
|
-
| `unknown` | absent, with `verdict_reason` | `solver-limit`, `tailoring-limit`, `memory`, `encoding-limit`, or `unexplained` |
|
|
196
|
+
| `unknown` | absent, with `verdict_reason` | `solver-limit`, `tailoring-limit`, `memory`, `tailoring-memory`, `encoding-limit`, or `unexplained` |
|
|
197
197
|
| `error` | `SavileRowUncaughtException` | Savile Row itself failed |
|
|
198
198
|
| `killed` | no `.info` at all | recorded by the caller, from the phase's `timed_out` / `launch_failed` |
|
|
199
199
|
|
|
@@ -202,6 +202,23 @@ the solver having run: Savile Row can settle an instance during tailoring and
|
|
|
202
202
|
never invoke a solver, leaving `SolverTotalTime: 0` beside a perfectly good
|
|
203
203
|
verdict. Testing "did the solver run?" reports every one of those as undecided.
|
|
204
204
|
|
|
205
|
+
**Each limit names the process it bound.** `solver-limit` / `tailoring-limit`
|
|
206
|
+
are the two time limits (see above), and `memory` / `tailoring-memory` are the
|
|
207
|
+
same split for the heap: `SolverMemOut` is the backend, `SavileRowOutOfMemory`
|
|
208
|
+
the JVM running Savile Row. The front-end memory-out is `unknown` and not
|
|
209
|
+
`error` for the same reason its timeout is — a resource cap stopped the run
|
|
210
|
+
before it decided; nothing malfunctioned. It is checked **before** the verdict,
|
|
211
|
+
and before `SavileRowUncaughtException`: one heap exhaustion sets both flags,
|
|
212
|
+
because the `OutOfMemoryError` reaches Savile Row's top-level handler, and the
|
|
213
|
+
cause is a more useful report than the symptom. `error` is then what is left
|
|
214
|
+
when Savile Row has no account of what went wrong.
|
|
215
|
+
|
|
216
|
+
You will mostly see this flag if your limits are **in band**. A harness that
|
|
217
|
+
caps memory externally (`runsolver` and friends) kills the JVM before it reaches
|
|
218
|
+
its own `-Xmx`, so Savile Row never writes the flag and you get a killed process
|
|
219
|
+
with no statistics instead — the same trade the time limits make. Bounding a run
|
|
220
|
+
from the inside buys diagnostics that killing it from outside destroys.
|
|
221
|
+
|
|
205
222
|
**A horizon's `unsat` is about the bound, not the problem.** It means "not at
|
|
206
223
|
this horizon", never "not at all" — refuting horizons 1..N says nothing about
|
|
207
224
|
N+1. Only the registry knows which models decide the problem itself
|
|
@@ -20,7 +20,7 @@ name = "solver-support"
|
|
|
20
20
|
# escape hatch for a version that genuinely cannot be known without building.
|
|
21
21
|
# Ours can. Carrying a `.devN` suffix between releases keeps the working tree
|
|
22
22
|
# from claiming a version that is already tagged and published.
|
|
23
|
-
version = "0.3.
|
|
23
|
+
version = "0.3.2"
|
|
24
24
|
description = "Generic Conjure / Savile Row / Fast Downward / SymK / ENHSP / UPF solver pipeline"
|
|
25
25
|
readme = "README.md"
|
|
26
26
|
requires-python = ">=3.9"
|
|
@@ -101,7 +101,8 @@ SAVILEROW_VERDICTS = ('sat', 'unsat', 'unknown', 'error', 'killed')
|
|
|
101
101
|
# Why a run ended without deciding, keyed by the ``.info`` flag that explains it,
|
|
102
102
|
# in the order they are checked. Savile Row sets at most one of these in practice;
|
|
103
103
|
# the order records which explanation would win if it ever set two, most specific
|
|
104
|
-
# (the solver's own limit) first.
|
|
104
|
+
# (the solver's own limit) first. ``memory`` is the *backend* running out; the
|
|
105
|
+
# front end running out is an abort flag below, not one of these.
|
|
105
106
|
_SAVILEROW_STOP_FLAGS = (
|
|
106
107
|
('SolverTimeOut', 'solver-limit'),
|
|
107
108
|
('SavileRowTimeOut', 'tailoring-limit'),
|
|
@@ -109,6 +110,23 @@ _SAVILEROW_STOP_FLAGS = (
|
|
|
109
110
|
('SavileRowClauseOut', 'encoding-limit'),
|
|
110
111
|
)
|
|
111
112
|
|
|
113
|
+
# Savile Row itself did not finish. Checked BEFORE the verdict test - a run that
|
|
114
|
+
# died mid-flight cannot be trusted to have written a sound one - and so, unlike
|
|
115
|
+
# the stop flags above, these outrank a ``SolverSatisfiable`` that is present.
|
|
116
|
+
#
|
|
117
|
+
# Ordered cause before symptom: a JVM heap exhaustion reaches Savile Row's
|
|
118
|
+
# top-level handler, so it sets BOTH ``SavileRowOutOfMemory`` and
|
|
119
|
+
# ``SavileRowUncaughtException``, and "ran out of memory" is the more useful of
|
|
120
|
+
# the two reports. Out-of-memory is ``unknown`` rather than ``error`` because
|
|
121
|
+
# nothing malfunctioned: it is the front-end twin of ``SolverMemOut``, a resource
|
|
122
|
+
# cap that stopped the run before it decided, exactly as the two time limits are
|
|
123
|
+
# a matched ``solver-limit`` / ``tailoring-limit`` pair. ``error`` stays for the
|
|
124
|
+
# case where Savile Row has no account of what went wrong.
|
|
125
|
+
_SAVILEROW_ABORT_FLAGS = (
|
|
126
|
+
('SavileRowOutOfMemory', 'unknown', 'tailoring-memory'),
|
|
127
|
+
('SavileRowUncaughtException', 'error', 'uncaught-exception'),
|
|
128
|
+
)
|
|
129
|
+
|
|
112
130
|
|
|
113
131
|
class SolverStatsExtractor:
|
|
114
132
|
"""Extract structured statistics from solver output."""
|
|
@@ -492,11 +510,14 @@ class SolverStatsExtractor:
|
|
|
492
510
|
except (TypeError, ValueError):
|
|
493
511
|
return False
|
|
494
512
|
|
|
495
|
-
# Checked before the verdict: a Savile Row that
|
|
496
|
-
#
|
|
497
|
-
# so
|
|
498
|
-
|
|
499
|
-
|
|
513
|
+
# Checked before the verdict: a Savile Row that died cannot be trusted to
|
|
514
|
+
# have written a sound one. An abort has never been observed together
|
|
515
|
+
# with a verdict, so that half of the ordering only decides a case not
|
|
516
|
+
# yet seen in practice; the cause-before-symptom half of it decides a
|
|
517
|
+
# real one, since an out-of-memory sets the exception flag as well.
|
|
518
|
+
for key, verdict, reason in _SAVILEROW_ABORT_FLAGS:
|
|
519
|
+
if _flagged(key):
|
|
520
|
+
return {'verdict': verdict, 'verdict_reason': reason}
|
|
500
521
|
|
|
501
522
|
# `SolverSatisfiable` is written if and only if something DECIDED this
|
|
502
523
|
# run, and is deliberately not keyed on the solver having run: Savile Row
|
|
@@ -281,6 +281,47 @@ class TestSavileRowVerdict:
|
|
|
281
281
|
assert SolverStatsExtractor.savilerow_verdict(info) == {
|
|
282
282
|
'verdict': 'error', 'verdict_reason': 'uncaught-exception'}
|
|
283
283
|
|
|
284
|
+
def test_out_of_memory_is_reported_by_cause_not_symptom(self, tmp_path):
|
|
285
|
+
"""Both flags are set by one event, and the cause is the useful one.
|
|
286
|
+
|
|
287
|
+
A JVM heap exhaustion reaches Savile Row's top-level handler, so the
|
|
288
|
+
``.info`` carries ``SavileRowUncaughtException: 1`` beside the memory
|
|
289
|
+
flag. Reporting the exception says *Savile Row has a bug*; reporting the
|
|
290
|
+
memory says *this model did not fit*, which is what decides whether a
|
|
291
|
+
consumer retries at a larger scale or files a fault. Keys verbatim from
|
|
292
|
+
a real run (Savile Row 1.11.1, ~183 s, exit 1, ``ERROR: Out of Memory``).
|
|
293
|
+
"""
|
|
294
|
+
info = self._write_info(
|
|
295
|
+
tmp_path,
|
|
296
|
+
"SavileRowClauseOut:0\nSavileRowTotalTime:182.786\n"
|
|
297
|
+
"SavileRowOutOfMemory:1\nSavileRowUncaughtException:1\n"
|
|
298
|
+
"SavileRowTimeOut:0\n")
|
|
299
|
+
assert SolverStatsExtractor.savilerow_verdict(info) == {
|
|
300
|
+
'verdict': 'unknown', 'verdict_reason': 'tailoring-memory'}
|
|
301
|
+
|
|
302
|
+
def test_out_of_memory_alone_is_not_unexplained(self, tmp_path):
|
|
303
|
+
"""An ``OutOfMemoryError`` caught and reported cleanly sets one flag.
|
|
304
|
+
|
|
305
|
+
Without its own branch this falls past the exception test, finds no stop
|
|
306
|
+
flag set, and lands on ``unexplained`` - which for a consumer that sets
|
|
307
|
+
the backend's limit itself reads as the routine solver timeout, turning
|
|
308
|
+
a fault into a cap.
|
|
309
|
+
"""
|
|
310
|
+
info = self._write_info(
|
|
311
|
+
tmp_path,
|
|
312
|
+
"SavileRowTotalTime:120.5\nSavileRowOutOfMemory:1\n"
|
|
313
|
+
"SavileRowTimeOut:0\nSolverTimeOut:0\n")
|
|
314
|
+
assert SolverStatsExtractor.savilerow_verdict(info) == {
|
|
315
|
+
'verdict': 'unknown', 'verdict_reason': 'tailoring-memory'}
|
|
316
|
+
|
|
317
|
+
def test_backend_memout_stays_distinct_from_tailoring_memory(self, tmp_path):
|
|
318
|
+
"""The two memory reasons name different processes' heaps."""
|
|
319
|
+
info = self._write_info(
|
|
320
|
+
tmp_path,
|
|
321
|
+
"SolverTotalTime:88.1\nSolverMemOut:1\nSolverTimeOut:0\n")
|
|
322
|
+
assert SolverStatsExtractor.savilerow_verdict(info) == {
|
|
323
|
+
'verdict': 'unknown', 'verdict_reason': 'memory'}
|
|
324
|
+
|
|
284
325
|
def test_proven_optimum_marked_on_sat(self, tmp_path):
|
|
285
326
|
info = self._write_info(
|
|
286
327
|
tmp_path,
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|