Linear integer arithmetic#
This theory follows the shared assignment and explanation contract.
Both standalone QF_LIA and combined QF_UFLIA use one LIA adapter implementing
TheoryComponent; the integer algorithms described here run behind that
boundary. The interface inventory
explains the adapters and their responsibilities.
An arithmetic assignment can satisfy every linear inequality and still fail
to be an integer model. For example, 2*x - 2*y = 1 has the real solution
x = 1/2, y = 0, but no integer solution: its left side is even. Primo first
solves the real relaxation, then tries to repair the assignment or
prove that the current Boolean choice has no integer completion.
This chapter describes the integer stages shared by QF_LIA and QF_UFLIA.
Configured resource limits can stop the search with unknown. Removing those
limits alone does not guarantee a terminating decision procedure for all
quantifier-free linear integer inputs: with unbounded variables, the current
branch-and-bound search can follow an infinite path. The
budgets and incomplete cases section distinguishes this
algorithmic limitation from resource exhaustion and gives a concrete example.
Representation and ownership#
The integer procedures reuse LraTableau. A row has the form
where \(x_b\) is basic, the \(x_j\) are nonbasic, and the coefficients are exact rationals. “Basic” means that the row determines the variable’s value from the nonbasic values; pivoting changes which variable occupies that role. Bounds and assignments use DeltaRational: a pair \(q+d\delta\), with \(\delta\) a positive infinitesimal used to represent strict inequalities.
State |
Owner and meaning |
|---|---|
Prepared atoms and term-to-variable map |
PreparedLraTheory connects arithmetic terms and SAT atoms to tableau variables and bounds. |
|
The caller owns a vector of |
Working tableau |
SMT integration owns a copy of a rationally feasible tableau and lends it by mutable reference to complete_integer_assignment. Patching changes assignments; cuts allocate variables and rows; branching adds temporary bounds. These changes stay within this copy. |
|
This explanation object stores the blocking literals contributed by GCD checks, cuts and branching in a sorted, duplicate-free vector. The stages share one object so a later conflict retains the reasons for earlier cuts. |
Branch frames |
branch_and_bound owns an explicit DFS stack. Each frame stores a variable ID, a split threshold, a tableau checkpoint and which side has been tried. |
LraVarId is an index within a tableau and its matching copies, not a core
TermId or a SAT variable. Appending a cut gives it a new ID without changing
existing IDs. Neither the temporary cut variable nor a branch bound creates
a core term or a new SAT atom. The working copy is discarded when its Boolean
candidate is rejected; a winning copy supplies the integer model.
An auxiliary variable for a normalized polynomial need not be integer-valued.
For example, dividing 2*x + y by its leading coefficient produces
\(s=x+y/2\). Even with integer \(x,y\), \(s\) may be a half-integer. Conversely,
normalizing 3*x <= 5 gives the direct bound \(x\le5/3\): no auxiliary row
is needed. The variable is integer, but its current bound is fractional.
These are different cases of the normalization described in LRA.
The integer set records semantic obligations, independently of the current
basis. A pivot can move a noninteger auxiliary variable into another row as
an ordinary term. No integer rule may assume that nonbasic variables are
integers just because the original input used Int.
When integer reasoning runs#
The standalone solve_qf_lia_impl recipe selects one LiaTheoryComponent
and calls solve_component_recipe. Online rational propagation stays enabled
independently of EUF settings. Model-guided combination is disabled: with only
one component, there are no shared scopes, interface arrangements, equality
bridges, or combination fallback. QF_UFLIA selects the same LIA component
alongside EUF.
The driver observes otherwise unowned Boolean constants through SAT’s assignment interface, in the original atom registry order. Their assignments remain propositional: the dispatcher does not route them into the LIA component. Observing them preserves the standalone route’s SAT elimination policy while making their values available in the shared assignment view and final model.
At the full-model check boundary, the LIA adapter copies the rationally feasible online tableau once, or moves a newly rebuilt rational tableau, into integer completion. It passes LiaCompletionLimits (patch moves, Gomory rounds, and branch nodes). Both routes run this pipeline:
Order |
Operation |
Result used by the caller |
|---|---|---|
1 |
Patch fractional integer values |
A feasible assignment with fewer fractional integer variables, or no progress. |
2 |
Check row divisibility using GCDs |
A conflict, or no contradiction found. |
3 |
Add a bounded sequence of Gomory cuts and recheck |
A conflict, or a feasible tableau strengthened by the cuts. |
4 |
Branch on fractional integer variables |
An integer model, exhaustion of all branches, or a node-limit result. |
GCD checks and cuts run before branching, not at each branch node. A zero cut or patch budget skips that stage. Even if patching makes every required variable integral, the final success decision comes through branch-and-bound’s integrality check, which then needs no branch nodes.
A refuted candidate produces a clause over the original arithmetic atoms.
The shared driver adds it to the SAT backend and searches for another Boolean
candidate. It reports unsat for the whole query when that search is exhausted;
integer infeasibility of one candidate does not by itself refute a formula
with other Boolean choices.
The standalone recipe sets CombinationRecipe::forgettable_clauses to false: its online external theory clauses remain permanent in the SAT backend, preserving the standalone route’s retention policy. ComponentDispatcher stores this policy for its lifetime and applies it to external clauses, including after the driver rebuilds the dispatcher for new atoms. Combined recipes retain the default true policy, which permits SAT clause deletion. This affects search and memory use, not the validity of the theory clauses.
QF_UFLIA calls the same complete_integer_assignment operation through
complete_lia_integer_model. Its
LIA component adapter
keeps the winning tableau, exports shared integer values, or returns a learned
clause/resource-limit result to the general combination driver. Rational
online propagation and final integer completion have separate responsibilities.
LiaTheoryComponent owns this adapter and the optional online rational
execution state. ComponentAdapterSupport owns the assignment view, registered
atoms, interface terms, and model-token revision. Atom growth destroys the
online execution state before changing the preparation it borrows; assignment
and backtracking invalidate the exported model token.
The recipe passes model-projection policy and LiaComponentTiming explicitly.
The adapter does not read QF_UFLIA combination options to select its behavior.
The combined recipe retains its check, rational, and integer timer keys; empty
keys disable timer sampling. Both rebuilt rational checks and completion of an
online tableau use LiaComponentAdapter::translate to replace the retained
model and return a consistent, learned-clause, or resource-limit result. If
requested projection omits a shared Int term, translation returns Unknown
with that diagnostic. Shared
model projection is produced only when the recipe requests it. The shared-sort
contract is Int-only.
The combination chapter explains the remaining equality arrangements and model-agreement checks.
LiaCompletionResult returns a typed Sat, Unsat, or ResourceLimit,
one accumulated LiaExplanation, the number of patched variables, and
LiaPatchingStats. The completion operation does not copy or prepare the
tableau and preserves the supplied integer-variable ordering. On success the
caller reads the winning assignment directly; after a failed or limited branch
search, cuts remain but speculative branch bounds are removed. It does not
restore the entry basis or assignment. The caller discards unsuccessful working
copies. A zero branch
budget permits an already integral assignment but no branch node.
Both routes use SMT integration’s integer_conflict_clause to collect blocking literals from the asserted arithmetic atoms. It handles an empty coarse clause explicitly; otherwise the existing optional generalization checks the accumulated explanation against that clause. Individual failed integer branches do not increment the aggregate conflict budget; the SMT caller counts the reported theory conflict. The patcher’s fully_integral statistic uses its maintained remaining-variable count, avoiding another scan. Both routes publish it when patching is enabled.
The completion boundary preserves stage order, variable order, limits, the winning assignment, and antecedents from cuts used by later branching. It adds no tableau copies, preparation, or full scans. The stage-specific sources and limitations below apply to both callers.
This design keeps integer branch choices inside the theory procedure. SAT learns the resulting conflict clause, but does not see individual integer splits as CDCL decisions. That simplifies the boundary and the lifetime of temporary rows; it also means that internal branch choices and cuts are not reused across successive Boolean candidates.
Input lowering and integer bounds#
Preprocessing profiles explains the automatic and explicit integer pass schedules. The transformations below run even when the optional repeated round is disabled.
Here, lowering means translating an input construct into simpler terms and constraints supported by the solving stages. Each translation below specifies the replacement and any fresh variables it introduces.
The frontend admits linear Int expressions
and comparisons under QF_LIA; uninterpreted functions require QF_UFLIA.
The integer logics reject Real declarations. Products must have a constant
coefficient. Primo also accepts constant-divisor div/mod and integer
abs as extensions to its QF_LIA input; that is not a claim of strict QF_LIA
grammar conformance.
Extended-term lowering
runs even when optional preprocessing is off. For positive integer \(k\), a
quotient \(q\) for div(t,k) is constrained by
mod(t,k) becomes the residual \(t-kq\). Equal numerator/divisor pairs share
one quotient. The inequalities characterize a unique integer quotient and
a remainder in \([0,k-1]\): two distinct quotients would make the remainders
differ by at least \(k\), which that interval cannot contain. For instance,
\(-7=3(-3)+2\), so the quotient is \(-3\) and the remainder is \(2\).
Term construction normalizes
a negative divisor by negating the positive-divisor quotient and preserving
the remainder. Zero and nonconstant divisors are rejected.
abs(t) is lowered through ite(0 <= t, t, -t). Non-Boolean ite introduces
a fresh constant with guarded equalities to its two possible values.
Equality lowering, despite its lower_real_equalities name, also handles
Int: \(a=b\) becomes \((a\le b)\land(b\le a)\). Negating the equality therefore
lets SAT choose a false non-strict comparison. Integer strictness must account
for that polarity, not just syntactic < atoms.
bound_assertion computes the bound direction and strictness from the normalized relation, orientation and truth value. When the bounded variable is known to be integer, it uses
Thus 3*x < 5 yields \(x\le1\), while a false 3*x <= 5 yields \(x\ge2\).
Subtracting or adding one to the rational threshold \(5/3\) would be wrong.
Non-strict bounds retain their rational threshold; they are not all rounded
eagerly.
PreparedLraTheory::append_atom marks direct bounds on Int terms
as integer. For compound polynomials its current sufficient condition is more
conservative: the input is integer-sorted and the normalization scale is
\(1\) or \(-1\). Other auxiliary variables use the real strict-bound encoding,
even if a stronger analysis could prove some of them integer-valued.
Why the relaxation can still contain infinitesimals#
The current invariant is that a completed integer assignment has no
infinitesimal component. Intermediate relaxation assignments can have one,
including on variables corresponding to Int terms.
For example, let \(P=2x+3y\) with integer \(x,y\). Normalizing by the leading coefficient \(2\) introduces \(s=x+\tfrac32y=P/2\). Consider \(P<0\):
The current fallback represents it as \(s\le-\delta\). The initial assignment \(x=y=s=0\) violates this bound.
If simplex pivots on \(x\), the row becomes \(x=s-\tfrac32y\). Moving \(s\) to \(-\delta\) gives \(x=-\delta\), \(y=0\). Every real constraint holds, but \(x\) is not integer-valued.
Patching can move \(s\) to \(-1\), giving \(x=-1\), \(y=0\). This satisfies the bound and makes both integer variables exact integers.
If patching does not resolve the assignment, branch-and-bound uses \(\lfloor-\delta\rfloor=-1\) and splits on \(x\le-1\) or \(x\ge0\). The first branch admits that same integer solution.
This is a possible tableau trace, not a promised preprocessing or pivot order.
The integer-value helpers require both an integral rational part and a zero infinitesimal part. In particular, \(\lfloor n-\delta\rfloor=n-1\) and \(\lceil n+\delta\rceil=n+1\) for integer \(n\). Treating \(n-\delta\) as having floor \(n\) would make branching repeatedly choose an already-satisfied bound. Dutertre–de Moura’s technical report §4.1, pp. 20–21, gives this delta-aware floor for branch-and-bound [20]. Patching targets exact integers; branch bounds have zero infinitesimal components. The cut generator skips a basic value with a nonzero infinitesimal, since its rational fractional-part formula does not cover that case. The regression map links the existing branching, patching and cut guards.
There is room to avoid introducing these infinitesimals. Since \(P\) is integer-valued, \(P<0\) is equivalent to \(P\le-1\), giving \(s\le-1/2\) without a delta. This must use the spacing of \(P/2\): rounding \(s\) as if it were an integer would incorrectly require \(s\le-1\) and exclude \(x=1,y=-1\), where \(s=-1/2\). The strengthened relaxation can still have fractional assignments; integer completion remains necessary. King’s thesis §4.3 describes the underlying integer tightening, including strict bounds obtained from negated SAT atoms [30]. Preserving this spacing during bound preparation is an open lead. Simply discarding a nonzero delta would change a strict inequality and is not a valid replacement.
Patching a feasible assignment#
patch_integer_variables tries to find an integer model by moving nonbasic values without violating any bounds. It derives from Bjørner and Nachmanson §4.1, with a bounded candidate search and a global improvement criterion specific to Primo.
For a nonbasic variable \(y\), its freedom interval is the intersection of its own bounds with the intervals induced by every basic row containing \(y\). If \(x_b\) has coefficient \(a\ne0\) for \(y\), moving \(y\) by \(\Delta\) changes \(x_b\) by \(a\Delta\). To set \(x_b\) to an integer \(n\), the candidate move is
where \(\beta\) denotes the current assignment. A candidate is admissible only if the proposed value of \(y\) lies in its freedom interval. This preserves both row equations and feasibility; patching does not call simplex to repair an inadmissible move. A fixed variable has a singleton freedom interval and cannot move away from its fixed value.
Primo also considers directly moving a fractional nonbasic integer variable. Unlike the simplifying assumption in the paper, nonbasic variables here may sit inside their bounds, and even an integer variable’s bound may be fractional. For each fractional target, the search considers eight integers around its delta-aware floor \(m\): \(m,m+1,m-1,m+2,\ldots,m-3,m+4\), ordered by distance from the rational part. This is a heuristic window, not an exhaustive search of its feasible values.
The width is currently the hard-coded kTargetWindow = 4; the existing
--lia-patch-limit controls accepted moves, not this width. A
separate open lead proposes a window parameter
and compares more selective candidates with
MIP rounding and repair heuristics.
Every candidate is scored by the total number of required integer variables that would remain nonintegral. Only a strict reduction is accepted. Among inspected candidates with the same count, a smaller rational move wins; the scan stops early on finding a move that makes the whole assignment integral. Each accepted move decreases a nonnegative integer, so the sweep terminates even apart from its configured move budget. It can make an individual previously integral variable fractional, provided the total count improves. An integral assignment is still subject to the ordinary model checks.
Why a nearest move is insufficient#
Use the paper’s Example 3:
with \(3\le x\le10\), \(-3\le y\le4\), \(-5\le z\le12\), and initial values \((x,y,z)=(3,3/2,1)\). All three variables must be integers. The freedom interval of nonbasic \(x\) is \([3,8]\): \(y\le4\) supplies the tighter upper endpoint. Moving \(x\) to \(4\) fixes \(y\) but makes \(z=4/3\) fractional; the total remains one fractional variable, so Primo rejects that move. Moving \(x\) to \(6\) gives \((6,3,2)\) and reduces the count to zero. Considering only the floor and ceiling of \(y=3/2\) would miss that target.
The scoring index stores, for each nonbasic variable, the basic integer rows that contain it. Scoring adjusts a cached global count using only the moved variable and those affected rows. No pivot occurs during the sweep, so this incidence index stays valid. The scoring cost per candidate is proportional to the affected integer column, rather than all integer variables; generating candidates and calculating freedom intervals still have their own costs.
LiaPatchingStats
exposes occurrence entries, candidates scored, affected rows inspected and
the equivalent full-scan work. The public counters include
:lia.patch-occurrence-entries, :lia.patch-candidates-scored,
:lia.patch-affected-rows-inspected and :lia.patch-full-scan-equivalent.
The patching tests cover the
paper example, fixed and strict bounds, and comparison of indexed scoring
with an independent full-scan definition.
Detecting integer-infeasible rows with GCDs#
The equation \(2x-2y=1\) has no integer solution because its left side is always divisible by \(2\). More generally, for integer coefficients \(C_i\) and integer variables \(z_i\), every value of \(\sum_i C_i z_i\) is divisible by \(g=\gcd(C_1,\ldots,C_k)\). An equation \(\sum_i C_i z_i=R\) is therefore impossible when \(g\) does not divide \(R\).
This is the classical divisibility criterion for a linear Diophantine equation, a consequence of Bézout’s identity. It predates Bjørner and Nachmanson: Pugh’s Omega test, §2.1 of the expanded 1992 paper, already uses it when normalizing equalities. Griggio’s 2009 thesis, §2.6.1, Algorithm 2.6, step 4, applies the same criterion in an SMT equation solver. Bjørner and Nachmanson §4.3 is Primo’s reference for applying the test to tableau rows after fixed variables have been substituted, not for the origin of the criterion.
The read-only procedure gcd_inconsistent_row prepares each row as follows:
Rewrite \(x_b=\sum_j a_jx_j\) as \(x_b-\sum_j a_jx_j=0\).
Substitute variables fixed by equal lower and upper bounds with zero infinitesimal part, including \(x_b\) if it is fixed. The remaining equation is \(\sum_i c_i z_i=r\), with possibly rational coefficients and residual.
Require every remaining variable to belong to
integer_vars. Otherwise skip the row: a noninteger auxiliary variable could absorb a fractional value. Pivoting can put such a variable on either side of the row.Scale the coefficients and the residual together to integers \(C_i,R\) using
primitive_integer_scale, and check whether their coefficient GCD divides \(R\).
If no variables remain after substitution, the row supplies no additional integer test; rational consistency has already been checked. Otherwise, failure of divisibility proves a conflict. Passing the test establishes only that the isolated equation has an integer solution when the remaining bounds are ignored. It does not establish consistency of the whole tableau.
For \(2x-2y=1\), normalization can represent \(s=x-y\) fixed to \(1/2\). Substitution leaves \(x-y=1/2\). Scaling the coefficients and residual by \(2\) gives \(2x-2y=1\); the coefficient GCD is \(2\), which does not divide \(1\). Scaling only the coefficients would destroy this argument.
The conflict depends on the bounds used to fix variables. Both bound reasons are collected for each fixed variable; row equations and the declared integer sorts are unconditional invariants of the prepared problem. The scan builds a membership bitmap and visits basic rows with exact rational/GCD operations; numeric cost depends on coefficient size, not just row length.
The bounded-variable GCD extension is an open lead carried forward from the original register. It can reject an interval containing no admissible residue even when no variable is fixed. Primo does not implement that extension or a general Diophantine equation solver. The GCD regressions cover fractional normalization, bounded-but-unfixed rows, and noninteger auxiliary variables in both basic and nonbasic positions.
Excluding a fractional point with a Gomory cut#
apply_gomory_cuts uses
King’s thesis §4.4.3, Lemma 4.1, equation (4.9). This mixed cut requires the
basic variable to be integer in every solution. It treats nonbasic variables
as continuous, so their bounds may be fractional and they need not belong to
integer_vars.
The helper find_eligible_row chooses the row from which to derive a cut. It takes the first eligible variable in the caller’s integer list: it must be basic, its current value must be fractional with zero infinitesimal part, and every nonbasic variable in its row must sit exactly at a lower or upper bound. A variable merely having a bound is insufficient. An ineligible row is skipped in its entirety. For a variable at both bounds, the lower bound is chosen.
Write the row as \(x_b=\sum_j a_jx_j\), let \(v=\beta(x_b)\), and define \(f=v-\lfloor v\rfloor\), so \(0<f<1\). Let \(b_j\) be the rational part of the bound occupied by \(x_j\). Subtracting the bound-site assignment gives
The left side must be an integer. Group the nonnegative contributions in the sum into \(P\), and write its nonpositive contributions as \(-N\), with \(P,N\ge0\). If \(P-N>0\), integrality forces \(P-N\ge1-f\), hence \(P\ge1-f\). Otherwise it forces \(P-N\le-f\), hence \(N\ge f\). In either case,
This is the cut. In terms of the stored row coefficients, it is \(\sum_j C_j(x_j-b_j)\ge1\), with:
Occupied bound |
Sign of \(a_j\) |
\(C_j\) |
|---|---|---|
Lower |
Positive |
\(a_j/(1-f)\) |
Lower |
Negative |
\(-a_j/f\) |
Upper |
Positive |
\(-a_j/f\) |
Upper |
Negative |
\(a_j/(1-f)\) |
The rational-bound reasoning also applies when a selected bound is strict: forgetting its infinitesimal gives a weaker bound. The row-selection requirement that the basic value has zero infinitesimal ensures that its \(v\) is the rational bound-site sum used above. It skips a basic value such as \(3-\delta\) entirely; computing a fractional part from its rational part would give zero and make the divisions invalid.
Turning the inequality into a row#
Expanding the constants yields
apply_one_cut adds a new basic variable \(c=\sum_j C_jx_j\) and asserts
the right side as its lower bound, using the synthetic reason
kDerivedBoundReason. Then it checks rational feasibility again. A conflict
ends the cut phase; otherwise it searches for another eligible row until
the cut budget is exhausted or no row qualifies. A false return means
“no conflict found”, not “no changes made”.
Take \(x_b=2u+3w\), with \(u\) at lower bound \(0\) and \(w\) at lower bound \(1/2\). Only \(x_b\) is required to be integer. Its value is \(3/2\), so \(f=1/2\) and
The current point \((u,w)=(0,1/2)\) gives \(3<4\) and is excluded. The feasible integer-\(x_b\) point \((0,2/3)\) gives equality and \(x_b=2\). For an upper-bound example, \(x_b=2u-3w\) with \(u=1/3\) at its lower bound and \(w=-1\) at its upper bound gives \(f=2/3\) and \(6u-9w\ge12\). It excludes the current value \(11\) and keeps \((u,w)=(1/3,-10/9)\), where \(x_b=4\) and the cut is tight.
The cut tests check both examples by rejecting the fractional point and retaining the integer point. They also cover a cut that proves infeasibility, a term inside its bounds, zero budget and a basic variable with nonzero infinitesimal. The end-to-end cut regression uses \(3x+5y=7\), \(x,y\ge0\): the real relaxation is feasible, but \(y=0\) or \(1\) requires a fractional \(x\), and \(y\ge2\) already exceeds \(7\). Cuts resolve it under a branch budget too small for branching alone.
Cuts live in the private tableau for one integer completion. They remain present while branching explores descendants, but are not retained for the next Boolean candidate. No backtrackable row-insertion mechanism is needed for this schedule. Primo does not use nonbasic integrality to strengthen King’s formula, the CAV 2024 cut-selection heuristics, or that paper’s temporary handling of large-coefficient cuts.
Branching and backtracking#
branch_and_bound
starts from a rationally feasible tableau. It scans integer_vars for the
first nonintegral assignment \(v\) and splits on
Every integer satisfies one side, and the current fractional value satisfies neither. At \(v=7/3\), for example, the branches are \(x\le2\) and \(x\ge3\). Each side is asserted as an ordinary tableau bound and checked by simplex. A feasible side is searched further; a conflict closes that side. After both sides close, the search backtracks to the previous frame. It tries the lower side first and uses the caller’s variable order, without a nearest-fraction or activity heuristic.
The explicit stack stores checkpoints rather than tableau copies per node.
On Sat, the tableau stays at the winning leaf, including the successful
branch bounds, so the caller can extract the model. On Unsat or
LimitReached, the procedure removes its speculative bounds. With the
default sparse simplex, popping restores bounds rather than the previous
assignment and basis. The optional SOI path also trails assignments and
pivots; see LRA backtracking.
Callers should rely on the bound-restoration contract, not require identical
internal tableau states.
The node counter increments when a new split is created, not for every simplex pivot or every attempted side. Integrality is checked before the limit, so an already integral assignment succeeds even with a zero node budget. The search stack grows with depth; the node cap bounds the number of splits in this call but is not a wall-clock or bit-complexity bound on the intervening exact arithmetic.
Explaining integer conflicts to SAT#
The coarse conflict clause contains the blocking literal of every arithmetic atom used in the current candidate. If their conjunction is integer-infeasible, its negation is a valid clause. A smaller clause can exclude more Boolean candidates, so Primo normally attempts to use the integer derivation’s antecedents instead.
LiaExplanation stores
literals already signed for the outgoing clause, following the tableau’s
blocking-literal convention. It drops the synthetic literal 0 used for
cuts and split bounds. Dropping it is justified by different arguments:
A GCD conflict includes both reasons fixing each substituted variable.
A cut includes the reasons for the occupied bounds used in its derivation. They are recorded when the cut is added, even if that cut does not immediately cause a conflict. A later cut or branch conflict may depend on it.
A split is an integer tautology. If assumptions \(A\) refute \(x\le n\) and assumptions \(B\) refute \(x\ge n+1\), then \(A\land B\) is impossible because those cases cover all integers. After both sides close, their antecedents suffice without mentioning either temporary split bound.
For example, let \(p\) mean the normalized bound \(x\ge1/2\) and \(q\) mean
\(x\le1/2\), with integer \(x\). These can arise from 2*x >= 1 and 2*x <= 1.
The lower branch \(x\le0\) conflicts with \(p\); the upper branch \(x\ge1\)
conflicts with \(q\). Combining the closed sides yields \(\neg p\lor\neg q\).
Keeping only the first conflict and dropping its branch assumption would
incorrectly yield \(\neg p\). The implementation emits the accumulated clause
only after integer infeasibility, never after a model or a resource limit.
The explanation can contain reasons from cuts or closed branches unnecessary to the final proof. Such extra disjuncts weaken a valid clause; they do not invalidate it. This is conservative explanation collection, not minimal-core extraction or a replayable external proof certificate.
generalized_lia_lemma checks that the explanation is nonempty and that each literal occurs in the coarse clause. Otherwise it falls back to the coarse clause. This catches foreign or synthetic literals and ensures that the chosen clause blocks the candidate. Containment alone does not prove theory validity: an arbitrary subset of a valid clause can be invalid. Soundness still depends on the GCD, cut and exhaustive-branch arguments above. The explanation regressions exercise antecedent retention, synthetic-reason removal and collection across stages.
The counters :lia.generalized-lemmas, :lia.generalized-lemma-literals,
:lia.coarse-lemma-literals and :lia.generalization-fallbacks describe the
clauses emitted. A smaller clause can change the SAT search substantially;
it is not by itself evidence of a performance improvement.
Budgets and incomplete cases#
The integer options are shared by the pure and combined routes:
Option |
Default |
Meaning per integer completion |
|---|---|---|
|
|
Maximum accepted patch moves; zero disables patching. |
|
|
Maximum cut-and-recheck iterations; zero disables cuts. Exhaustion falls through to branching. |
|
|
Maximum new split nodes. Exhaustion before finding a model or refuting the tree returns a resource-limit result. |
|
Absent |
Disables the default attempt to replace coarse clauses with accumulated explanations. |
These are finite operational limits, not empirically optimal settings.
Both routes report typed ResourceLimit to the shared driver, producing
unknown with the diagnostic integer branch-and-bound node limit. An
unfinished search must not become unsat. Cancellation and the aggregate
theory-conflict budget remain SMT-driver outcomes, independent of internal
failed integer branches.
Consider unbounded integers \(x,y\) with \(2\le4x+4y\le3\). No multiple of four lies in that interval, yet the relaxation admits \(x+y=1/2\). The normalized auxiliary variable \(s=x+y\) is bounded, not fixed, so the current GCD check does not refute the interval. With unbounded nonbasic terms there is no eligible initial Gomory row. Patching cannot produce an integer model, and branching can keep translating the fractional assignment along the unbounded direction. The node-limit regression preserves this incomplete case. Adding GCD checks and cuts before DFS does not supply a completeness proof.
Dillig, Dillig and Aiken’s Cuts from proofs [16] provides a different branching rule based on integer-infeasible intersections. Its §3.3 termination argument uses satisfiability-preserving finite variable bounds and bounded split coefficients; it is not a guarantee for arbitrary unbounded cut generation. The paper overview derives the split for the example above and identifies the mechanisms missing from Primo.
Bromberger’s thesis, Chapter 6, addresses precisely the difference between bounded row expressions and bounded variables. Its Double-Bounded reduction and Mixed-Echelon-Hermite transformation provide a route to a finite integer search while preserving solutions through a change of coordinates. They are not implemented here. The thesis reading guide works through this example in coordinates \(u=x+y\), \(v=y\) and identifies the remaining implementation obligations.
The cube test, extended bounded-variable GCD reasoning, general Diophantine or Hermite-normal-form reasoning, cuts within branch nodes, and CDCL-visible integer splits remain outside the implementation. In particular, cube tests attempt to establish feasibility by solving a strengthened relaxation; failure of that test is not an integer-infeasibility proof. The cube-test explanation derives its strengthening, rounding guarantee and equality limitation. The earlier thesis note and integer open leads retain the separate investigations and their prerequisites.
QF_LIRA, mixing declared Int and Real variables, is not admitted. The
stage APIs already take an integer subset, and fractional auxiliary variables
exercise some mixed-tableau cases. That does not establish end-to-end QF_LIRA
support: contextual numeral typing, coercions such as to_real, frontend
admission and combined model construction still need design and validation.
Models, regression coverage and retained evidence#
An integer result requires every variable in integer_vars to have a zero
infinitesimal part and an integral rational part. Both routes export the
winning values through LiaTheoryComponent to
assemble_component_model.
The shared assembler restores preprocessing conversions and completes declared
Boolean constants even when rewriting removed their last occurrence. The driver
supplies assignments for otherwise unowned Boolean constants that remain in the
SAT abstraction.
Combined models must additionally agree with EUF on evaluated function
arguments and shared values; see model construction.
Obligation |
Existing regression coverage |
|---|---|
Find a model, close both sides, respect the budget, handle \(n-\delta\) |
|
Guard every free row variable before using integrality |
|
Exclude fractional vertices while retaining legal integer points |
|
Preserve feasibility and score all affected integer variables |
|
Keep antecedents of cuts used by later conflicts |
|
Strict polarity, normalization, extensions, model completion and stage controls |
|
Integer completion within equality/model combination |
Tests of hand-built tableaux exercise algorithm preconditions; whole-solver tests exercise admission, lowering and route integration. Fuzzing must cover both generated inputs and mutations of real formulas. Historical failures involved large coefficients and repeated proportional polynomials whose nearby thresholds shared a fractional auxiliary variable. Small-coefficient generator campaigns missed them, and merely increasing coefficient width also missed the original failure in a recorded probe. The lasting requirement is structural diversity, including pivoted auxiliary variables and original input model validation; see the correctness guide.
The section preservation map accounts for the original
chapter, including repaired bugs, competing designs, experiment losses and
incomplete gates. Those records remain attached to their measured revisions
in the development history and
run log. For example, the August patching
comparison reports gains and losses, and the generalization comparison records
a severe regression on one rings instance. Neither fewer fractional values
nor shorter lemmas guarantees a faster subsequent search.
Historical solved counts exclude clean unknown results and timeout rows,
even if an answer was printed before termination. This chapter documents
the current mechanism; it does not rerun those campaigns or promote their
measurements to results for the current source revision.
Sources and implementation status#
The local paper-to-code checklist is the stage order, guards, derivations, state contracts and regression obligations above. The primary sections used for this chapter are identified in the bibliography; the table records the specific sections and their implementation status.
Source |
Implemented subset and boundary |
|---|---|
The Omega Test, expanded CACM 1992 version of the 1991 paper, §2.1 [35]; An Effective SMT Engine for Formal Verification, thesis, §2.6.1, Algorithm 2.6 [27] |
Earlier uses of the classical GCD divisibility criterion. Added for attribution; Primo’s row-local check does not implement their equality-elimination procedures. The retained Griggio thesis note covers the broader equation handler. |
Nikolaj Bjørner and Lev Nachmanson, Arithmetic Solving in Z3, CAV 2024, §§4.1, 4.3–4.4 |
Freedom-interval patching, basic row GCD consistency and integer splits. Primo adds a finite target window and a strictly improving global patch score, and branches inside a private theory search. The paper’s cubes, extended GCD and specialized cuts are not implemented. |
Effective Algorithms for the Satisfiability of Quantifier-Free Formulas Over Linear Real and Integer Arithmetic, thesis, §§4.3, 4.4.3, Lemma 4.1, equation (4.9) [30] |
The mixed-cut derivation, represented by a fresh tableau row and bound. Nonbasic integrality is not used to strengthen the cut; no complete cutting-plane algorithm is claimed. Section 4.3 supplies integer-bound normalization for the open lead on scaled auxiliaries; Primo’s current preparation has the limitations described above. |
Martin Bromberger, Decision Procedures for Linear Arithmetic, PhD thesis, Saarland, 2019, Chapters 4–6 and §§7.2.3–7.2.4 |
Cube tests, equality/bounded-direction analysis, bounding transformations and the separation of preparation from per-node work. These supply comparisons and concrete further work; Primo does not implement that complete combination. See the reading guide. |
Bruno Dutertre and Leonardo de Moura, Integrating Simplex with DPLL(T), SRI-CSL-06-01, §§2–3 and 4.1 [20] |
The rational tableau, bound assertions and consistency checks explained in LRA, plus integer splitting with delta-aware floor in §4.1. Section 3.2.4 saves a successful assignment; Primo’s sparse mode retains the current assignment and basis when popping bounds. These implemented mechanisms do not establish completeness of Primo’s integer procedure. |
Original Primo design: private integer completion and accumulated explanations |
The owned tableau copy, internal DFS, cut lifetime and union of antecedents connect the cited arithmetic rules to Primo’s SAT and component interfaces. Defensive clause containment is not a substitute for those rules’ soundness. |
The Griggio and Bromberger tableau engineering references remain mapped in LRA’s source table; they are not additional implemented integer stages.