Fix FPA soundness issue in incremental solving with push/pop#9028
Draft
Fix FPA soundness issue in incremental solving with push/pop#9028
Conversation
Copilot AI changed the title [WIP] [Z3-8345] Fix FPA soundness issue in incremental solving Fix FPA soundness issue in incremental solving with push/pop Mar 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
After a
(push), Z3 incorrectly reportsunsatfor satisfiable FPA formulas involving uninterpreted functions returning floating-point values. The root cause is that side conditions generated bymk_uf(which connect FP UFs to their BV counterparts) are asserted as scopedCLS_TH_AXIOMclauses that get deleted on DPLL backtrack, but them_conversionscache retains entries at the base scope level and short-circuits the rewriter — preventing side conditions from being re-emitted.Root Cause
The PR #8712 fix (
m_rw.reset()inpop_scope_eh) only helps whenm_conversionsdoes not have a cached entry.theory_fpa::convert()checksm_conversionsbefore calling the rewriter, so if an entry exists at base scope (scope 1, after userpush), the rewriter is never invoked,mk_ufis never called, and no side conditions are added tom_extra_assertions. Meanwhile, the side conditions originally asserted during that conversion (at a deeper DPLL scope) have been deleted by backtracking.Additionally,
context::internalize_termis a no-op for already-internalized terms (returns early ife_internalized(n)), so side conditions from the first internalization of terms like(fp.to_real (fp.add ...))are never re-asserted after backtracking removes the associated clauses.Change
In
theory_fpa::pop_scope_eh, clearm_conversionsin addition to resettingm_rw, so all conversions are re-run after any pop and side conditions are unconditionally re-emitted:The converter's
m_uf2bvufmap (FP UF → BV UF mapping) is intentionally preserved to ensure the same BV function is reused across re-conversions, maintaining consistency.Original prompt
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.