Full-corpus evaluation at longer limits#
The evaluation covers every supported non-incremental division at 1,200 seconds per input. The October 22–23 attempt stopped with 429 complete timing shards after a Primo process was killed on quantified UF. Its memory reservation had not established an effective hard cap. Those timings remain preserved as an uncapped historical cohort and are excluded from the new bounded comparison.
The October 24 replacement uses a hard 8 GiB virtual-address-space limit per
process. A read-only inspection on 28 October 2026 found timing and
selected ground SAT-model replay complete. The final audit is
complete-diagnostic-native-errors: successful ground SAT rows have model
coverage, but native execution errors still block headline claims and default
promotion. Worker provenance records both address-space limits as exactly 8 GiB. Runs 2026-10-22-all-theories-long-limits and
2026-10-24-all-theories-enforced-memory in the run ledger
record the separate attempts, exact snapshots, limits, jobs and artifacts.
Division |
Inputs |
|---|---|
QF_UF |
7,503 |
QF_LRA |
1,753 |
QF_LIA |
13,306 |
QF_UFLRA |
1,284 |
QF_UFLIA |
659 |
UF, quantified |
7,590 |
Total |
32,095 |
This is the full existing corpus, with no competition selection or easy-input
filter. Existing copies are reused read-only and freshly hashed in CPU SLURM
allocations. Each timed solver process uses one physical CPU and inherits an
8 GiB soft and hard RLIMIT_AS; the batch scripts set ulimit -v 8388608 before
starting Python. Worker provenance records both byte limits, and workers and
audits reject a missing or different limit. The SLURM reservation remains
8 GiB per task; GPU nodes are excluded. This is a per-process virtual-address-
space limit, including mappings and libraries.
CPU probe 242893 on the failed job’s node found memory.max=max throughout
the job cgroup hierarchy, even though SLURM requested 8 GiB and its configuration
said to constrain RAM. Probe 242895 then verified the explicit process limit:
both RLIMIT_AS values were 8,589,934,592 bytes, and a 9 GiB anonymous mapping
was rejected with ENOMEM. No large physical allocation was needed for that
check. The old 429 complete shards cannot establish compliance with this new
limit and are not reused as bounded timings. Input corpora and preserved solver
executables can be reused with matching hashes.
Primo and references receive the same 1,200-second wall limit and hard address- space limit. A shard contains at most 24 inputs and receives nine hours; that allocation limit is distinct from the per-input limit.
Completion snapshot, 28 October 2026#
The audit summary
records the inspection time, full audit-file hashes, immutable plan identity,
native outcomes and remaining reference-corroboration gaps. The timing audit
reports timing-complete; the final audit reports complete coverage for all
13,484 successful ground SAT rows across the measured Primo profiles,
checked separately with Dolmen. No owner jobs remained in the scheduler queue
at the recorded inspection time. These statements report the existing audits;
this documentation review did not rerun or reaggregate the experiment.
The final audit retains 1,052 native execution-error rows across QF_LRA, QF_LIA, QF_UFLRA and quantified UF, so the comparison remains diagnostic. Explicit allocation failures remain separate from cleanup aborts. The earlier case-specific memory triage below does not reclassify every error. Quantified UF has no independent printed-model validation, and some definite answers lack reference corroboration. Complete task coverage therefore does not establish unrestricted correctness or qualify the current implementation. The frozen revision and resource limits remain the comparison’s scope.
Profiles and references#
The native implementation is frozen at 4419dc9, with the exact previously
qualified binary preserved. That frozen binary’s defaults are measured in every division.
QF_LRA also measures --lra-model-phase --monotone-elimination, its strongest
recent full-corpus profile. The equality experiment remains outside this
comparison. This campaign does not qualify later default-policy or solver changes.
The reference candidates are Z3, cvc5, Yices and OpenSMT, each with its defaults. Versioned binaries are preserved before measurements. Admission probes run separately, retain diagnostics and exercise SAT and UNSAT cases. A reference that rejects a division is reported unavailable there, rather than assigned zero solves. Quantifier probes must require real instantiation: trivial quantified formulas can simplify away without testing general admission. Admission does not establish completeness or support for every input feature.
Each cactus will select one static Primo profile for the entire division:
highest solved count, then lowest PAR2, then profile name to break a tie.
Both native profiles remain in the audit where measured. There is no per-input
oracle selection or implied shared-budget portfolio. Integer and quantified
instance limits retain their existing values; unknown remains unsolved even
when it is returned before the wall limit.
Correctness and phase barriers#
A fresh 6,000-case generated gate covered the six routes before headline timings. Existing release/ASan checks and the QF_LRA option qualification refer to the identical native binary. Capability probes retain Primo’s documented incomplete quantified SAT behavior separately from wrong answers and unsupported logics.
The maintained timing/model adapter records each input and executable hash, exact arguments and parsed answers in CSV/journals. Raw timing stdout/stderr is retained for timeouts and errors; successful timing rows do not retain complete raw streams. Model replay retains its separate printed-model and validator artifacts. Campaign execution records add hardware, resources, immutable plan identity and global shard identity. Explicit small manifests prevent every worker from traversing the whole corpus; the shared adapter processes each supplied manifest as one local shard. The campaign audit checks the global disjoint coverage.
The journal guard cancels the campaign on a wrong answer or opposite definite
answers from two profiles. Process crashes and execution errors, including
native failures, remain unsolved error rows with their exit status and raw
stdout/stderr; they do not cancel unrelated tasks. Recognized explicit allocation
failures, such as std::bad_alloc, are counted separately as memory-out results.
All these outcomes receive the unsolved PAR2 penalty and never corroborate an
answer. The final audit hashes the retained raw logs.
Native execution errors block headline claims and default promotion. The final report names the affected divisions/profiles and labels their plots diagnostic. Successful SAT rows still require their separate model checks; a diagnostic comparison does not qualify the failing profile. A deliberately reduced 64 MiB control on the earlier failing UF input produced a CaDiCaL cleanup abort rather than a clean allocation-failure message. It is retained as a native error under forced small memory, not counted as a memory-out pass. The actual evaluation limit is 8 GiB. This task changes the harness, not the native implementation.
Two October 26 diagnostic replays then established the allocation-failure path
at the actual 8 GiB evaluation limit, using the preserved binary and original
input hashes. The exception replay script
uses GDB on CPU SLURM allocations; its target inherits exactly 8 GiB soft and
hard address-space limits, while the debugger has a separate 16 GiB budget.
The debugger stops at the first C++ throw and records the allocation stack and
target process limits before continuing to the abort.
Replayed division/input |
Failing allocation |
|---|---|
QF_LIA: |
Binary-clause vector growth in SAT preprocessing |
UF: |
Term-vector growth during quantifier instantiation |
Both traces stop in the allocation-failure path of operator new, then abort
while AssignmentConnection
is destroyed and calls external-propagator disconnection during exception
unwinding. These exact runs are confirmed memory exhaustion followed by a
cleanup failure; the QF_LIA trace does not establish integer branching as the
cause. GDB could not print the exception’s C++ type without library debug info,
but the allocation origin and subsequent abort stacks were retained.
The contemporaneous native CSV census found 266 explicit std::bad_alloc rows
and 652 rows with the same cleanup diagnostic. The two replays establish the
mechanism for their exact input/profile/binary combinations; the matching
message alone does not individually confirm every remaining abort. Confirmed
memory-outs are ordinary unsolved budget exhaustion. Raw timing rows retain
their original exit codes, and the generic report still counts cleanup aborts
as execution errors; the case-specific triage is recorded separately in the run
ledger. These diagnostic runs contribute no speed measurements, and did not supply SAT-model qualification. The separate completed ground
replay is recorded in the completion snapshot above.
The two exact inputs are also recorded as memory-attribution candidates. The UF case motivates term-storage and reclamation profiling; the QF_LIA case helps distinguish backend storage pressure. Neither trace establishes that reference counting would avoid the failure.
Harness or filesystem failures stop only their own task and are recorded
separately. Unreadable live-journal snapshots are advisory and retried; every
terminal CSV and the global coverage audit remain strict. Earlier CPU smoke
jobs 225341 and 225508 established that a reference SIGSEGV can be followed
by a successful solve. The memory probe and metadata regressions cover the new
limit and failure classification.
The first workflow used an overly broad guard: an OpenSMT SIGSEGV on
20210219-Dartagnan/ReachSafety-Loops/benchmark51_polynomial-O0.smt2 canceled all
arrays after 158 timing shards completed. That attempt and its partial rows are
preserved. CPU job 225341 reproduced that SIGSEGV under the repaired guard, then
successfully solved the following known-good input. The regression suite also
checks that execution failures remain unsolved while answer contradictions
remain blocking. Native failures block headline/default qualification.
The recovery tool leaves the original plan, limits, executable snapshots and corpus inventories unchanged. It validates complete timing shards with the same coverage and provenance audit used by reporting, then freezes hashes of each reused CSV, provenance and execution record. Interrupted shard directories and the original cancellation records move to an explicitly recorded archive; only unfinished shards are rescheduled. An immutable recovery record binds the old plan and worker identity to the repaired worker and its pending task list. Reports verify reused hashes and the recovery identity for new executions. Further recovery attempts archive the prior recovery record, retain its worker and record hashes as explicit ancestors, and recertify all completed shards; identical repeat submission of the same worker is rejected.
The global timing audit runs after all timing arrays terminate, including failed arrays, and must accept all shards before any model replay starts. Each model task records that barrier’s hash. Wrong answers, decisive reference disagreements and invalid models block result acceptance. Native execution errors block headline/default qualification even when diagnostic plots and successful-row model coverage are available.
Every successful Primo SAT row in the five ground divisions receives a separate direct Dolmen check in the original logic, using the same native arguments plus model production and internal checking. Model production gets 1,800 seconds and Dolmen 300 seconds, with Dolmen’s size budget set to 5G inside the 8 GiB allocation. Missing or incomplete checks prevent full ground qualification; checking time never enters measured runtime or PAR2.
Primo does not print models for quantified formulas. Quantified UF therefore receives reference-answer corroboration and explicit uncorroborated-answer counts, without independent printed-model qualification. Ground model passes must not be generalized to this route.
Progress cactus snapshots#
The snapshot exporter
can plot all six divisions before the campaign finishes, without advancing the
strict timing/model barriers. It writes a fresh output directory and reads the
measurement tree without modifying its rows or original phase reports. Run it
in the CPU SLURM audit allocation, with the same frozen worker adapter and a
fresh PRIMO_PREVIEW_OUT; the maintained audit batch wrapper invokes its
preview stage.
Within each division it selects only complete shard indices shared by every planned solver/profile, including both native QF_LRA profiles. Every plotted series therefore covers identical inputs; unknown, timeout, allocation-failure and process-error rows remain in that cohort as unsolved. Each included shard receives the normal strict timing-provenance audit, and definite answers are cross-checked before plotting. One static Primo profile is selected by solved count, then PAR2, then name on this cohort. There is no per-input oracle and no easy-input filter.
The gallery and SVG captions distinguish full timing coverage from a partial,
completion-selected cohort. Full-corpus rankings can change when pending shards
arrive. Neither kind of progress plot establishes independent SAT-model
qualification. The exporter retains minimal series CSVs, exact input hashes,
source-file certificates, outcome/family summaries and its selection.json.
Pass that selection to --selection with a fresh output directory to reproduce
the same cohort. The run ledger records each saved snapshot and its limits.
Recorded outputs#
The campaign driver and configuration write fresh inventories, capability records, a frozen plan, durable timing journals, per-shard provenance, separate model coverage, and strict timing/final audits. The final stage generates one full-corpus cactus per division, selects the native profile by the rule above, and reports solved, SAT/UNSAT, timeout, unknown, error, PAR2 and family totals. Historical 30-, 60- and 300-second measurements remain unchanged and are not substituted for fresh 1,200-second reference runs.
The submission controller splits arrays below the scheduler’s 1,001-task index limit and keeps total timing concurrency at or below 128 physical CPUs. Model replay uses a separate phase with at most 64 concurrent tasks. Failed dependencies cancel later compute rather than leaving a nominally successful partial evaluation.
The initial controller divides the timing budget once: seven arrays each get
an 18-task throttle, for at most 126 workers. It does not automatically transfer
unused shares when an array drains. On October 27, idle CPU nodes and pending
JobArrayTaskLimit work exposed this avoidable tail. The three arrays with
queued work were raised to 36 tasks each while the draining array retained its
18-task cap: the maximum across the four arrays remains 126, below the original
128-task ceiling. The run ledger records this scheduler-only adjustment and
its verification; solver binaries, per-input limits, inputs and timing rows
were unchanged. Automatic redistribution remains unimplemented.
The final audit remains explicit about missing results and quantified-model limitations. Submission alone is not completion.