Skip to content

Exact Math models, per-call UFs for transcendental functions, bounded queries over them - #80

Closed
nils-loose wants to merge 5 commits into
svcomp27/integrationfrom
feat/math-models
Closed

nils-loose wants to merge 5 commits into
svcomp27/integrationfrom
feat/math-models

Conversation

@nils-loose

Copy link
Copy Markdown
Contributor

What changes and why

Exact Math models (MathInvocation, checked against the JDK in MathInvocationSpec):

  • Math.round rounded ties to even (round(2.5) = 2), but the JDK rounds them toward positive infinity. It is now floor(x) plus one if x - floor(x) >= 0.5, which is exact.
  • max/min of floats and doubles used SMT-LIB fp.max/fp.min. Those return the other argument when one argument is NaN, and either zero for +0.0/-0.0. Both now follow the JDK definition.
  • New: signum, and floorDiv/floorMod for int/long with a concrete non-zero divisor. A symbolic divisor would need a branch for the ArithmeticException, so it stays unmodelled (context loss).

Transcendental functions are per-call UFs (SampledMath, now including sin/cos):

  • Each call is an application of its own function math_<name>_<n>(x).
  • A trace constraint defines that function as the existing sample table: exact at the samples, the concrete result everywhere else.
  • Precision loss is now recorded only when a branch constraint contains such a function (the existing UF check in DTOBuilder). A Math result that never reaches a branch no longer rules out SAFE.
  • Each call has its own function, so the definitions never constrain each other or the inputs.
  • SinCosUF is removed. Its trace-global constraint "the argument is one of the samples" also applied to branches before the call, and gave a false SAFE. Reproducer: if (x > 0.7 && x < 0.8) assert false; Math.sin(x); gives SAFE on the integration branch and VIOLATION here.

Explorer:

  • UF definitions are sliced per query. A query keeps only the definitions whose functions it contains, transitively, plus any constraint without functions. This is sound because each left-out definition can be satisfied by its own function value.
  • A query over a math_ function can only give a guess, and its trace is already precision loss. In sv-comp mode it gets at most --approximate-rlimit (default 2.5e8, about 1-1.5 min). If it runs out, the explorer gives up on that branch (UNKNOWN) and does not escalate the limit.
  • The guess flag (expected divergence) now follows the query instead of the trace. A divergence from an exact query in a trace that had a Math call stays an error, so it still blocks SAFE.
  • Function interpretations in a Z3 model are no longer read as input values.

Range axioms were not added. The table definition fixes the value at every argument, so axioms add no information. A free UF limited only by range axioms would give the solver guesses that diverge almost every time.

Measured

Competition config (nosa, 300 s, sv-comp.cfg, 15 GB), compared with baseline-results.jsonl. 437 runs:

  • 337 affected: KB context-loss-math / precision-loss-uf / uf-global-constraints-unsliced / fp-encoding-imprecise, all float/coral/Math families, and the 25 runs that the SampledMath change had slowed to timeouts.
  • 30 baseline-correct runs whose tasks use Math or the String UFs.
  • 70 other baseline-correct runs.
task prop expected base branch points
float-nonlinear-calculation/coral17 valid-assert VIOLATION unknown violation +1
float-nonlinear-calculation/coral21 valid-assert VIOLATION violation unknown -1
float-nonlinear-calculation/coral71 valid-assert VIOLATION violation unknown -1
  • Net -1 (+1 / -2). No new wrong verdict. All three flips reproduce on a re-run.
    • coral21: the query needs more than the cap. It still runs out at 3.5e8.
    • coral71: the sin/cos sample set changed, the search takes another path, and it ends in DONT-KNOW.
  • CPU: 47070 s -> 39911 s over the 437 runs (-15%). Timeouts: 140 -> 93.
  • The 25 slowed runs: 7500 s -> 4408 s. 21 of them now end with UNKNOWN in 97-271 s instead of timing out. EulerMethod no-runtime-exception now ends in OOM (0 points, as before).
  • Regression set: 0 flips, CPU 740 s -> 661 s.

Tests

  • MathInvocationSpec: round, max/min, signum, floorDiv/floorMod against the JDK on edge values. It fails on the old models.
  • tests/test_math_models.py: slicing, approximate-query cap, model extraction.
  • Executor gradlew test and all explorer unit tests pass.

Known limits

  • addExact/multiplyExact/... and floorDiv with a symbolic divisor are still unmodelled (context loss). They need a branch for the exception from inside a model. No current SV-COMP task uses them.
  • copySign and hypot are not modelled.
  • --approximate-rlimit is a trade-off: a higher cap recovers violations behind hard queries (coral21) but brings back long runs.
  • The cap applies only in sv-comp mode.

🤖 Generated with Claude Code

nils-loose and others added 5 commits October 18, 2026 21:53
Math.round rounded ties to even (round(2.5) = 2), but the JDK rounds them
toward positive infinity. It is now floor(x) plus one if x - floor(x) >= 0.5,
which is exact. max/min of floats and doubles used SMT-LIB's fp.max/fp.min,
which return the other argument for one NaN and either zero for +0.0/-0.0;
they now follow the JDK's definition. signum is new, as are floorDiv and
floorMod of ints and longs for a concrete non-zero divisor (a symbolic one
would need a branch for the ArithmeticException, so it stays unmodelled).

MathInvocationSpec checks every model against the JDK on edge values
(NaN, zeros, ties, infinities, saturation, MIN_VALUE).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each call of a sampled Math function (now including sin and cos) is an
application of its own function math_<name>_<n>, defined by a trace
constraint as the sample table: exact at the samples, the concrete result
elsewhere. Before, the table was inlined and every call recorded precision
loss; now precision loss is recorded where it matters, when a branch
constraint contains such a function (DTOBuilder's UF check), so a Math
result that never reaches a branch no longer rules out SAFE. The function
names also let the explorer tell which queries depend on a model.

Every call has its own function, so the definitions never constrain each
other or the inputs: each can be satisfied by its function value.

SinCosUF is removed. It restricted the argument of every symbolic sin/cos
call to its sample inputs through a trace-global constraint, which also
held for branches before the call; with the result reaching no branch, no
precision loss was recorded, and a feasible branch could be reported
unreachable: x in (0.7, 0.8) followed by an unused Math.sin(x) gave SAFE.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A query now carries only the UF definitions whose functions it (or a
definition it needs) contains, plus constraints without functions. The
left-out ones only constrain functions that occur nowhere else in the
query; as every Math model call has its own function, each of them can be
satisfied whatever the inputs, so satisfiability does not change.

A query over a sampled Math function can only give a guess, and its trace
is precision loss, so it cannot end in SAFE anyway. In sv-comp mode such a
query gets at most --approximate-rlimit (default 4e8, one to two minutes);
running out of it gives up on the branch (UNKNOWN) instead of retrying.
This bounds the runs that used to end in a quick UNKNOWN with the Math
calls concretised and now spent the whole CPU limit in one query.

A solution counts as a guess (an expected divergence) if its query depends
on a Math model or its trace used an approximate model, as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A model of a query over a Math model function also interprets the function
(math_tan_3); reading it as an input value failed with an unsupported
prefix and stopped the exploration.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nils-loose added a commit that referenced this pull request Oct 19, 2026
SinCosUF.java: deleted as in #80 (its trace-global sample constraint gave
a false SAFE); #71's eager precision loss there goes with it.
StrategyService.py: solve_branch takes both #74's extra_exprs and #80's
approximate_rlimit, on #80's sliced query.
SVCompDriver.py: retries (#74) and first queries both get the
approximate-query cap (#80); a solution keeps #74's record and #80's
query-based guess flag.
SymbolicExplorer.py: both sets of options.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nils-loose added a commit that referenced this pull request Oct 19, 2026
Combining #71 and #80: each per-call math_<f>_<n> function is declared
APPROXIMATE_MODEL_FUNCTION in the SymbolRegistry, so a branch over one is
a precision loss by kind, not only as an undeclared symbol. The registry
test builds its function through SampledMath, as SinCosUF is gone, and
the divergence tests pass #80's approximate_rlimit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nils-loose added a commit that referenced this pull request Oct 19, 2026
_extract_variables: one copy (#82 cherry-picks #72's patch).
StrategyService.py, Database.py: #82 walks the Database's tree under the
lock; visit_tree passes the solved and #74's retired branches, in #72's
'with lock' style.
SVCompDriver.py: #82's shallow snapshot of the target node for
check_divergence, with #74's solution record and #80's guess flag; the
ingest-failure (#72) and stack-overflow (#78) checks read the Database
without copying the tree.
DTOBuilder.java: #82's FormulaDumper with #71's SymbolRegistry check.
DFS.py: #82 logs the per-node line only with an SA graph.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nils-loose added a commit that referenced this pull request Oct 19, 2026
EqualsIgnoreCaseUF, ToLowerCaseUF: deleted as in #90 (replaced by exact
models); #71's registry entries go with them. UFHandler keeps no models,
as #80 removed SinCosUF too. StringValue.java: keep #71's SymbolRegistry
import.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@nils-loose nils-loose mentioned this pull request Oct 20, 2026
@nils-loose

Copy link
Copy Markdown
Contributor Author

Included in dev through #92 (merge commit 534c182), together with the fixes for interactions between PRs. This PR stays as the record of the change and its measured flips.

@nils-loose nils-loose closed this Oct 20, 2026
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