Skip to content

Upgrade Ibex/CAPD to Codac - #4

Open
kunalsheth wants to merge 79 commits into
cav26from
upgrade-ibex
Open

Upgrade Ibex/CAPD to Codac#4
kunalsheth wants to merge 79 commits into
cav26from
upgrade-ibex

Conversation

@kunalsheth

Copy link
Copy Markdown
Member

No description provided.

kunalsheth and others added 30 commits December 18, 2025 17:23
…nch for direct lemma additions without matching.
- Move trie implementation to own subfolder.
- Move stats struct to independent header file.
…ed_map to flat vector for better cache performance.
…unit test. (Fixed alpha-hashing bug that was previously causing it to fail.)
…a/Expressions optional— to avoid unnecessary copying in production.
kunalsheth and others added 28 commits June 5, 2026 00:33
…dpoint contraction

The previous implementation used LohnerAlgorithm (Codac's simple first-order
method with contractions=1) in a single direction, accumulating a hull of
enclosures that intersected X_t.  This gave weak contraction because:
  - contractions=1 produces loose per-step global enclosures
  - The hull over all n_steps=200 time steps grows wide for long trajectories
  - Each contractor call only narrowed one endpoint (X_0 or X_t)

Replace with CtcLohner::contract(tube, FWD_BWD):
  - FWD pass propagates X_0 forward, narrowing X_t
  - BWD pass propagates X_t backward, narrowing X_0
  - Both endpoints are contracted in a single contractor call
  - contractions=5 gives tighter per-step enclosures (Codac default)
  - n_steps=20 (down from 200) keeps per-call cost low; Lohner is nearly
    exact for smooth ODEs with this step size

Add vars_0_narrowed to CodacOdeResult so Prune() can narrow m_vars_0
from the backward pass in the same call, eliminating the need for the
separate BWD contractor to provide X_0 contraction.

Benchmark: bouncing_ball_with_drag_10_0.smt2
  before (n_steps=200, contractions=1, LohnerAlgorithm): >>30s
  after  (n_steps=20, contractions=5, CtcLohner FWD_BWD): 13s

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Seven files covering architecture, contractors, ODE integration,
pattern matching (CAV26), C++ API, QF_NRA_ODE semantics, and SMT2
syntax reference. ODE semantics doc is grounded in parser.yy,
scanner.ll, symbolic_odes.cc, and contractor_odes.cc, including
negated-invariant ignore behavior and all edge cases.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds benchmark/ scripts for rapid regression detection during development:
- select.py: picks 8 random + all anomaly benchmarks, outputs TSV
- run_batch.sh: parallel runner with gtime -v -o and timeout 300
- parse_results.py: parses gtime + solver output into summary.csv
- aggregate.py: compares vs frozen DRPM_0L baseline, flags regressions
  (>1.5x) and exceptional speedups (<0.6x), updates state.json
- baseline.csv: frozen reference from prior CAV26/TACAS26 experiments
- state.json: persistent anomaly tracker (initially empty)

Also adds .claude/skills/benchmark.md (/benchmark) and
benchmark-baseline.md (/benchmark-baseline) for one-command invocation
from Claude Code. The /benchmark skill delegates result interpretation
to a Haiku subagent to keep the main context clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…alies

benchmark.md: run_batch.sh now launched with run_in_background so the
~5-minute batch doesn't block; wait for task notification instead of polling.

state.json: records first real benchmark run (bfcef46) results — 5 anomalies
added (3 solve→timeout, 1 ERR, 1 correctness flip) and 3 exceptional wins
(tacas benchmarks now solving where baseline timed out).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- benchmark/select.py: anomaly names in state.json lack .smt2 suffix but
  baseline.csv names include it; normalize with removesuffix before comparing
  so tracked anomalies are always guaranteed to run
- .claude/skills/benchmark.md, benchmark-baseline.md: replace run_in_background
  on run_batch.sh with foreground execution (420s/1200s timeout); the background
  mode killed grandchild solver processes on task cleanup, leaving empty gtime files
- benchmark/state.json: updated anomaly list from completed test run

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…e skill

- Fix SAR-ADC benchmark resolution: point SARADC_DIRS to the complete
  AMS-verification-bundle-of-sticks/saradc/rolled/ directory (all 21
  baseline entries now resolve); update CLAUDE.md accordingly
- Eliminate permission prompts in /benchmark-baseline: extract the
  inline python3 heredoc from do_baseline.sh into set_local_baseline.py,
  and pre-compute per-family frozen vs local averages inside aggregate.py
  (--frozen-baseline flag) so the Haiku subagent only needs one Read call
- Migrate skills from flat .md files to subdirectory format (skill.md)
- Add do_benchmark.sh / do_baseline.sh shell drivers; select_baseline.py
  for stratified 3-family sampling; set_local_baseline.py for state updates
- Record accumulated run history and anomaly state in state.json

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Prevents resource contention on a 16-core machine that was causing
false timing regressions when all benchmarks ran simultaneously.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two-pass optimization to close the post-migration regressions in both ODE
and non-ODE solving while staying inside Codac v2 + lebarsfa/ibex-lib.

ODE contractor (contractor_odes_codac.cc, contractor_odes.cc):
- CodacOdeCache reuses the per-flow AnalyticFunction and CtcLohner across
  every Prune call; keyed by OdeFlow* with a static dedup map so hybrid
  systems with N_modes copies of one flow translate the symbolic RHS just
  once instead of N_modes x 2 times.
- contractions=2 (was 5): empirical speed/tightness sweet spot.
- BWD contractor skips CtcLohner (Step 4): the constructor's m_vars_0 <->
  m_vars_t swap turns CtcLohner FWD_BWD into the wrong question for
  non-time-symmetric dynamics. FWD's FWD_BWD already narrows both endpoints
  jointly, so skipping BWD's Step 4 loses no contraction. See
  CODAC_MIGRATION.md for the correctness analysis.
- Trivial-flow short-circuit: flows where every RHS is the literal 0
  bypass CtcLohner entirely and just intersect X_0 ∩ X_t.
- Adaptive n_steps: clamp(max(n_steps_hint, ceil(t_ub * 2)), n_steps_hint,
  60). Keeps the historical 20-step floor for short horizons (bouncing
  ball unchanged at ~2.8 s) and only adds steps when t_ub > ~10 so cardiac
  (t_ub up to 30) gets h <= 0.5 instead of h = 1.5. Cardiac k4 went from
  29 s baseline (TIM on a noisy day) to ~5 s consistently.

IBEX fwdbwd contractor (contractor_ibex_fwdbwd.cc):
- Replaced the full-box IntervalVector iv_before = iv snapshot + std::set
  <int> changed_vec with a thread_local std::vector<(int, ibex::Interval)>
  saving only the input bits' intervals before contraction. Cost per Prune
  goes from O(|box|) to O(|free_vars(f)|) (typically 50-500 vs 2-10), and
  the std::set node allocations disappear entirely. thread_local keeps the
  buffer's capacity across calls so steady-state Prune does zero alloc.

Headline timings vs baseline:
- cardiac_new_cardiac k4: 29 s -> ~5 s (6x)
- tacas c2e2 k10 NOR__sigmoid SAT: 142 s -> 0.8 s (170x)
- tacas c2e2 k17 NOR__sigmoid UNS: TIM -> 13.6 s (resolved)
- tacas c2e2 k21 NOR__sigmoid SAT: 234 s -> 7.7 s (30x)
- bouncing_ball_with_drag_10_0: unchanged at ~2.8 s (n_steps floor)

Hard TIMs that remain (quad k128, planning k1280/k1536, crazyflie k16,
prostate k2 variants, cardiac k64) are at Codac v2's order-2 Taylor
ceiling — not fixable without library changes. Full writeup in
CODAC_MIGRATION.md under "ODE Contractor Optimization Pass" and "Second
Optimization Pass".

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ic tests

The BWD ODE contractor's Step 4 (previously an early-return no-op) is
re-enabled via Codac LohnerAlgorithm(forward=false). The FWD path's
CtcLohner FWD_BWD is unchanged — this is purely additive. Adds ~50% per
BWD Prune; bouncing_ball_with_drag_10_0 goes from ~2.8s to ~4.2s.

Soundness is verified by a new semantic test suite — closed-form
fixtures for trivial, decay, and rational-coupled (mock-prostate) flows.
All 7 soundness gates pass on both HEAD and the experiment, including
the load-bearing MockProstateTest cases whose rational coupling mirrors
the prostate_h2 benchmark's dynamics.

The prior "any SAT↔UNSAT flip aborts" criterion conflated soundness
regressions with completeness improvements. The new aggregate.py
classifier distinguishes SOUNDNESS REGRESSION (flip away from annotated
ground truth) from CORRECTNESS IMPROVEMENT (flip toward it) from
UNDETERMINED FLIP (no annotation). Ground truth is populated only from
filename conventions (VNAMSCwI _SAT/_UNS, SARADC -1e/5000e); dReal3 is
not propagated because it has the same δ-completeness limitations as
dReal4. A frozen-baseline fallback makes flips on anomaly-list rows
missing from baseline_local.csv visible to the classifier.

Re-classifying the prior experiment batch: 0 soundness regressions, 5
UNDETERMINED FLIPS (4 prostate variants + 1 water-double-network-sat,
all SAT→UNSAT — likely δ-witness refutations).

CLAUDE.md, CODAC_MIGRATION.md, and DEPENDENCIES.md are updated to reflect
the BWD restoration and the new performance baseline. The new
CODAC_MIGRATION.md "Third Pass" section records the verification audit,
documents the cache-key bug in make_codac_ode_cache (raw OdeFlow*
pointers, vulnerable to pointer reuse — worked around in the test file
via inline static fixtures), and lists the natural next steps.

codac_docs/ collects the Codac v1/v2 reference material and analysis
notes (including alternatives_to_ctclohner.md, which gated this
experiment) that informed the work.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
make_codac_ode_cache used a raw OdeFlow* as the cache-map key. If an
OdeFlow was destroyed and a new one was later allocated at the same
address, the lookup returned a stale cache built for the previous flow.
Hit reproducibly when test fixtures created and destroyed OdeFlows
between TEST_F instances.

The cache map now stores a CacheSlot { shared_ptr<const OdeFlow> flow,
shared_ptr<CodacOdeCache> cache } keyed by raw pointer. The held
shared_ptr keeps the OdeFlow alive for the cache's lifetime, so the
address cannot be reused while the entry is live. make_codac_ode_cache's
signature changes from (const OdeFlow& flow, ...) to
(shared_ptr<const OdeFlow> flow, ...) — the single call site in
contractor_odes.cc now passes icc->get_flow() directly instead of
dereferencing it.

The semantic test fixtures keep their inline-static OdeFlow members for
tidiness (one cache entry per fixture class instead of per TEST_F
instance), but the comment is updated to note the fix.

Note: TimePropag::FWD-only on the FWD contractor (item 5) was
attempted and reverted in this session — it caused
github_oct5_0hz_k4_cardiac_new_cardiac to flip UNSAT → delta-SAT
(soundness-direction). The deferred-item recommendation in
CODAC_MIGRATION.md ("re-lock the regression baseline before measuring")
stands.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
CODAC_MIGRATION.md collapses from a multi-phase planning doc into a finished
migration summary (-711 lines). CLAUDE.md, DEPENDENCIES.md, and
docs/ode-integration.md trim their Codac sections to point at the consolidated
doc instead of repeating it. examples/README.md replaces stale Bazel build
instructions with a current manual-compile recipe against the CMake build.

codac_docs/analysis/alternatives_to_ctclohner.md gains a "Why #6 is not
justified" section spelling out the soundness, wrapping-effect, and
maintenance arguments against rolling a custom higher-order Taylor
integrator, so the existing "not justified for CAV26" verdict isn't
re-litigated without that context.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The cache-key pointer-reuse bug previously listed under "Known issues"
was fixed in commit fed8a02 (CacheSlot holds a shared_ptr<const
OdeFlow> alongside the cache). Update the four spots that still
described it as unfixed:

- "Current implementation": describe the new CacheSlot layout and the
  shared_ptr-based liveness guarantee.
- "Open lines of attack" item 2 (eviction): clarify that the slot now
  pins each flow in memory until process exit, so eviction is the
  remaining cache-lifetime concern.
- "Open lines of attack" item 7 (Fix the cache-key bug): removed —
  done.
- "Known issues": rewritten as a "Resolved" entry pointing at the fix
  commit.

Record the item-5 attempt under "What we tried but reverted": the
TimePropag::FWD-only change on the FWD contractor caused
github_oct5_0hz_k4_cardiac_new_cardiac to flip UNSAT → δ-SAT
(soundness-direction). The new ground-truth-aware classifier caught
it. Reverted before commit. Lesson noted for any future re-attempt
(BWD contractions=1 doesn't replace FWD_BWD's BWD pass on cardiac).

Refresh benchmark/state.json with the post-cache-fix run's anomalies
and exceptional speedups (water-triple, water-double, thermostat-
triple, tacas_c2e2 k30 — likely real wins from the cache fix landing
properly).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
CAPD master now exposes CAPD_INTERVAL_TYPE=NATIVE, which makes capdExt
skip add_subdirectory(filibsrc) entirely and uses CAPD's own ARM64-capable
DoubleRounding. The previously-abandoned FILIB-bypass patch is no longer
needed. CAPD runs alongside Codac's order-2 CtcLohner via a gate on
(t_ub, n_state_vars); CAPD divergence falls back to Lohner so no narrowing
is ever lost.

* CMakeLists.txt: ExternalProject_Add(capd_external) pinning master SHA
  b353e170 (2026-05-18) + INTERFACE_COMPILE_DEFINITIONS=__USE_NATIVE__.
* src/dreal/contractor/odes/contractor_odes_capd.{h,cc}: per-flow IMap
  cache (forward + -f(x) for backward integration), per-call IOdeSolver +
  ITimeMap. Soundness identical to Pass 3's backward-image argument.
* src/dreal/contractor/odes/to_capd_string.h: forward-ported Expression ->
  CAPD-IMap string builder.
* --capd-t-gate (default 5.0) and --capd-ndim-gate (default 6) CLI flags.
* 38 new unit tests: 26 to_capd_string direct tests + 12 contractor
  semantic tests (gate dispatch / long-horizon CAPD / backend consistency
  / 6D ndim-gate / trivial-flow precedence). All 550 tests pass.
* Benchmark sweep (30 stratified): 0 regressions, 2 SARADC anomalies
  resolved, tacas family 0.44x vs CAV26 frozen baseline.
* Docs: CODAC_MIGRATION.md, codac_docs/analysis/*.md, CLAUDE.md updated.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
DEPENDENCIES.md: add CAPD section (CAPDGroup/CAPD@b353e170, CAPD_INTERVAL_TYPE=NATIVE,
ExternalProject); correct IBEX version (ibex-2.8.9.1 → ibex-2.8.9.20250626) and build
method (ExternalProject → configure-time zip download) for both IBEX and Codac; update
ODE Contractor Status to describe the Codac-default / CAPD-gated hybrid.

CLAUDE.md: update Prerequisites line, Auto-downloaded Dependencies list (IBEX/Codac as
prebuilt zips; CAPD as ExternalProject), and upgrade-ibex branch summary (remove stale
"CAPD abandoned" sentence; describe working gated hybrid).

docs/ode-integration.md: fix CtcLohner configuration (contractions 5→2, steps 50→20
adaptive); update Performance section; replace "CAPD not taken" section with "CAPD
gated hybrid" section; correct IBEX version in "What was replaced".

docs/contractors.md, docs/architecture.md: update ODE contractor description to mention
the CAPD gate dispatch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
TIM/OOM/ERR results are now penalized at 2× the solver timeout (600 s)
instead of being excluded from family averages. This fixes a misleading
analysis in CODAC_MIGRATION.md where benchmarks transitioning from TIM
to solved appeared to "raise" the average — under PAR2 they correctly
lower it.

Changes:
- aggregate.py: add par2_time() helper; unify solve→TIM, TIM→solve, and
  plain timing regression/exceptional into a single PAR2-ratio check;
  compute_family_comparison now includes all benchmarks (formerly TIM
  rows excluded); output keys renamed frozen_avg_par2/local_avg_par2
- CLAUDE.md: update threshold documentation to reference PAR2 time
- CODAC_MIGRATION.md: replace old raw-average table with corrected PAR2
  numbers from a fresh baseline run; tacas was not "2.3× faster" —
  it is 2.61× slower once timed-out benchmarks are counted
- Skill files: reference PAR2 scores and updated JSON key names
- baseline_local.csv, state.json: refreshed from new baseline run

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The CODAC_MIGRATION.md trade-off table claimed the loss of ncsys-lab's
`_grad = nullptr` hack was "minor memory overhead." It is not. `sample`
profiling on `1mhz_k20_saradc_2b_box_4a_-1e` puts ~65% of total wall time
inside `ibex::Function::init` building `_grad` (Gradient ctor +
ExprLinearity walk) — work `Function::backward` (the only IBEX entry
point fwdbwd uses) never reads. End-of-run `--verbose info` stats
confirm: 0.022 s in Pruning, 77 s in Theory CheckSat, 877 unique-formula
cache misses × ~88 ms/each = the missing 77 s.

The big fix — restore the gradient-suppression hack on the upstream
lebarsfa IBEX — is documented as "Open lines of attack" item 7 (switch
`ibex_external` from prebuilt-zip to source `ExternalProject_Add` with a
small `PATCH_COMMAND`) and deferred to a follow-on session because it
needs source-IBEX build wiring. Expected impact: saradc family PAR2 from
567 s toward ~190 s.

What landed in this commit:
- `contractor_ibex_polytope.cc::Prune` switches to the Pass-2
  input-restricted thread_local snapshot recipe. Quiet win for
  `--polytope` users; no effect on default-config saradc, which never
  hits polytope.
- Temporary `ContractorIbexPolytopeStat` block added alongside, mirroring
  the fwdbwd one — useful for future debug under `--polytope --verbose
  info`.
- `CODAC_MIGRATION.md`: trade-off row rewritten to flag actual magnitude;
  new "Re-investigation 2026-06-07" section with the profile evidence;
  "Open lines of attack" item 7 added; stale `1mhz_k28_saradc_3b_box_4a_-1e`
  "TIMs at HEAD" claim corrected (it now solves in 279 s).
- `DEPENDENCIES.md`: "performance impact is acceptable" claim corrected
  with link to the re-investigation section.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…Codac

Benchmarking on 2026-06-07 confirmed native ARM64 CAPD order-20 is at
least as fast as Codac CtcLohner order-2 on all tested integral-based ODE
benchmarks, and 23% faster on the canonical bouncing_ball reference case
(3391 ms vs 4410 ms). No verdict differences were observed. This eliminates
the performance argument for keeping Codac.

CODAC_MIGRATION.md:
- Add "Strategic reassessment 2026-06-07" conclusion section with the
  two-option analysis, full benchmark table, and Option B decision rationale.
- Update open-lines-of-attack item 7 to target ibex-team/ibex-lib (not
  lebarsfa) and fold in Codac removal as part of the same work item.
- Fix CLI flag documentation: --capd-t-gate/--capd-ndim-gate validators
  reject 0; correct invocation is --capd-t-gate 1e-300 --capd-ndim-gate 1.

CLAUDE.md:
- Note the 2026-06-07 strategic direction in the upgrade-ibex branch desc.
- Update ODE performance baseline with measured numbers (4.4 s Codac,
  3.4 s CAPD forced) and remove the stale "needs a benchmark sweep" caveat.
- Fix the wrong --capd-t-gate 0 force-CAPD invocation in two places.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This bundles two related changes:

1. **IBEX URL switch.** The ExternalProject source-build for IBEX now
   defaults to https://github.com/ncsys-lab/ibex-lib.git instead of the
   sibling file://../ibex-fork path. The IBEX_GIT_REPOSITORY cache
   variable remains overridable for local-dev iteration against an
   unpushed checkout. Updates the Dockerfile (drops the COPY ibex-fork
   step), DEPENDENCIES.md, and CLAUDE.md to match.

2. **Soundness regression tests** (`ibex_log_pow_edge_cases_test.cc`).
   Header rewritten to document the patched-and-shipped state of ibex
   commit 33eb6676 ("gaol: fix Interval::log and Interval::pow soundness
   gaps"). Adds two new tests:
   - PowSubnormalUnderflow: confirms `pow([0.5,0.5], 1075.0)` returns
     the rigorous overapproximation [0, DBL_TRUE_MIN] (not [0, 0]).
   - DISABLED_PowSubnormalUnderflowEndToEnd: documents the remaining
     ibex HC4-backward unsoundness in pow-of-degenerate-constant chains
     (the deeper layer of dreal/dreal4#321, beyond what the gaol wrapper
     fix addresses). DISABLED_ so CI stays green; the diagnostic stays
     in tree.

Note: this commit also picks up some pre-existing upgrade-ibex WIP that
was already in the working tree (Codac removal narrative in CLAUDE.md /
DEPENDENCIES.md, eigen apt-get removal in the Dockerfile). They're
conceptually part of the same "fork-aware build" arc, so bundling them
keeps the working tree clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Removes the Codac CtcLohner contractor and the CAPD/Codac gated-hybrid
dispatch. CAPD order-20 Taylor now handles every ODE Prune call.

- Delete contractor_odes_codac.{cc,h} and Codac includes from contractor_odes.
- Add run_capd_trace in contractor_odes_capd.{cc,h} to replace
  run_lohner_trace for the --visualize flag (one IVector slice per step).
- Drop --capd-t-gate / --capd-ndim-gate CLI flags and the matching
  Config::kDefaultCapdTGate / kDefaultCapdNdimGate constants.
- Update CODAC_MIGRATION.md with a resolved-status banner.
- Strip CAPD-forced test variants and gate-boundary tests from
  contractor_odes_semantic_test; reframe long-horizon tests as plain
  soundness gates against the single CAPD backend.
- contractor_capd_test: drop the Lohner-pinning code; update the BWD
  expectations to match CAPD's one-way backward image (no p0 narrowing,
  no constraints recorded).
The dreal-perf-patches branch adds an optional callback to
ibex::Function::backward (commit 4d61b841) that fires once per narrowed
variable. Use it from contractor_ibex_fwdbwd::Prune to populate the
output bitset directly, avoiding the per-call snapshot of input
intervals. On boxes with 50-500 variables and a fwdbwd constraint
touching only 2-10, this skips an O(|free_vars(f)|) copy and the
post-hoc comparison loop.

Add three companion regression tests for the patch series:

- ibex_backward_callback_test: scalar callback fires for each narrowed
  var, receives correct old/new intervals, skips unchanged vars.
- ibex_backward_callback_vector_matrix_test: covers the f04f5db5 audit
  fix in read_arg_domains so vector/matrix-typed symbols also get
  per-component notifications (forward-compat for upstream PR).
- ibex_function_backward_compat_test: defaulted-arg overload still
  compiles for the two-arg call site dReal uses today.
Covers ncsys-lab/ibex-lib commit edbd8159 which nulls out the Gradient
and ExprLinearity allocations in Function::init. dReal only calls
Function::backward, which never touches _grad / _lin, so the lazy
initialization is sound for our call path. Profiling on the saradc
family attributed ~65% of wall time to those allocations pre-patch.

The test exercises a Function pipeline that would page-fault if either
field were dereferenced, providing observational evidence the patch
holds.
Add two smoke tests calibrated against ncsys-lab/ibex-lib commits
fc986657, 4f845aa3 (FPU management on Apple Silicon / x86) and
059d1fe7 (gaol::init wrapper). None of these are part of the current
dreal-perf-patches minimal fork — mainline's 971f8eb0 gaol-rounding fix
covers the arm64 path without the wholesale FPU rewrite, and the
gaol-init cleanup didn't clear the upstream-PR bar.

The tests are retained as DOCUMENTATION of where mainline ibex differs
from the cav26-era FPU-clean baseline. Goldens can be updated to
mainline if the behavior is acceptable, or the patches re-added to
flip them to PASS.
The aggregate initializer was passing the second field positionally
after a designated .filtered, which some compilers warn on / reject.
Add the explicit .changed = ... designator.
…overrun)

Two distinct crash modes were surfacing as ERR exits in the benchmark suite.

Mode A — SIGABRT in CaDiCaL (saradc + several tacas). With factor/BVA
inprocessing on, cadical->vars() includes solver-internal extension
variables; the model-reading loops call val() on them, which aborts
("extension variable ... defined by the solver internally"). Disable factor
in the SatSolver ctor. The model-reading loops are correct only while factor
is off, so each is annotated with a BRITTLE hazard comment pointing at the
(cadical_next_var - 1) bound that would be required if factor is re-enabled.

Mode B — heap buffer overrun in the CAPD ODE integrator (car/automaton
models). The cached IMap was built over every flow variable while the
C0Rect2Set was built from only the evolving state vars, so CAPD wrote an
n x n Jacobian into undersized buffers (confirmed via guard malloc: a write
in Map::operator() during run_capd_fwd -> moveSet). Flow variables with
d/dt == 0 are parameters: emit them in CAPD's par: section and bind them
per-call via setParameter on a private map copy (copying keeps the cached
map immutable, which is required because it is shared across parallel ICP
workers), instead of as integration variables. Mirrors cav26's
build_capd_string. Adds ContractorCapdParamTest exercising the var/par
dimension split that previously corrupted the heap.

Both fixes are satisfiability-preserving. They do NOT address a separate,
pre-existing false-UNSAT on some param-bearing ODE instances (GT=SAT) that
the crashes were masking; that is tracked separately.

Note: contractor_odes_capd.cc also carries a pre-existing, uncommitted
simplification of run_capd_fwd (removal of the in-FWD backward sweep) that
was already in the working tree; it is interleaved with the Mode B edit and
included here.

569/569 tests pass against gcc_build at this tree.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
gaol (ibex's interval backend) is sound only with the FPU in round-upward
mode; under any other mode its directed rounding inverts (lo>hi), so an
inexactly-FP-representable subexpression collapses to an empty interval and
wrongly empties the box. ContractorIbexFwdbwd::Prune historically relied on
ambient FE_UPWARD, but linking CAPD (the ODE backend) leaves the process FPU
in FE_TONEAREST via its DoubleRounding static init, so the ambient mode can
no longer be assumed. The result was a silent false-UNSAT on formulas with
inexact constant arithmetic inline in inequalities (e.g. (* 3.3 x),
(* C (pow 10 -N))) -- the GT=SAT false-UNSATs on the 1mhz_*_saradc_*_box_*
benchmarks. Exactly-representable values (0.5, *1.0) need no rounding and so
hid the bug.

Establish FE_UPWARD explicitly per Prune (cheap; runs on every ICP worker
thread, where FPU mode is thread-local). Also guard FE_TONEAREST in
contractor_ode_lohner::generate_trace (the --visualize path), the only CAPD
entry point that lacked it -- the solve-critical Prune/cache paths were
already guarded.

Adds an end-to-end regression test and documents the gaol<->CAPD
rounding-mode invariant (plus the known flaky-test trio) in CLAUDE.md.

Verified: the three saradc benchmarks now return delta-sat; full suite green
except the pre-existing flaky trio (ITE-eliminator counter, Timer.Test1);
benchmark batch shows 0 regressions, 0 correctness flips.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…pty)

Regression tests for two dreal-perf-patches fork fixes, authored during the
fork patch audit:
- 1836b569 (function: copy old-value in backward callback to avoid aliasing
  box storage)
- d2b978b9 (HC4Revise: report partial narrowings on EmptyBoxException)

Both pass against the current fork build.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant