Solving ranges

TU Wien Guest Lectures October 2025

Nikolaj Bjørner
Microsoft Research

Example formula

Check satisfiability of

\[\begin{mdmathpre}%mdk \mdmathindent{2}\mathid{l}~<~\mathid{x}~<~\mathid{y}~<~\mathid{u}\\ \mdmathindent{2}[\mathid{l}..\mathid{u}]~\setminus \{~\mathid{x},~\mathid{y}~\}~\subseteq \mathid{A}~\subseteq [\mathid{l}..\mathid{u}]~\setminus \{~\mathid{x}~\} \end{mdmathpre}%mdk \]

Array Property Fragment Encoding

(declare-fun RangeLU (Int) Bool)
(declare-fun A (Int) Bool)
(declare-const lo Int)
(declare-const hi Int)
(declare-const x Int)
(declare-const y Int)

(assert (< lo x y hi))

; express that RangeLU = [lo..hi]
(define-fun below_lo ((i Int)) Bool (=> (<= i (- lo 1)) (not (RangeLU i))))
(define-fun in_range ((i Int)) Bool (=> (>= i (+ hi 1)) (not (RangeLU i))))
(define-fun above_hi ((i Int)) Bool (=> (and (<= lo i) (<= i hi)) (RangeLU i)))
(assert (forall ((i Int)) (below_lo i)))
(assert (forall ((i Int)) (in_range i)))
(assert (forall ((i Int)) (above_hi i)))



; express that [l..u] \ { x, y } \subseteq A \subseteq [l..u] \ { x }
(define-fun below-A ((i Int)) Bool (=> (and (RangeLU i) (not (= i x)) (not (= i y))) (A i)))
(define-fun above-A ((i Int)) Bool (=> (A i) (and (RangeLU i) (not (= i x)))))

(assert (forall ((i Int)) (below-A i)))
(assert (forall ((i Int)) (above-A i)))

(check-sat)
(get-model)
(reset)

Attempt 1 - manual encoding

; read set of free variables:
; R := { x, y }
; read set for bound variables:
; B := { lo - 1, hi + 1, lo, hi }
; 

(declare-fun RangeLU (Int) Bool)
(declare-fun A (Int) Bool)
(declare-const lo Int)
(declare-const hi Int)
(declare-const x Int)
(declare-const y Int)

(assert (< lo x y hi))
(assert (< (+ lo 10) hi))

(define-const r1 Int x)
(define-const r2 Int y)
(define-const b1 Int (- lo 1))
(define-const b2 Int (+ hi 1))
(define-const b3 Int lo)
(define-const b4 Int hi)


(define-fun below-lo ((i Int)) Bool (=> (<= i (- lo 1)) (not (RangeLU i))))
(define-fun in-range ((i Int)) Bool (=> (>= i (+ hi 1)) (not (RangeLU i))))
(define-fun above-hi ((i Int)) Bool (=> (and (<= lo i) (<= i hi)) (RangeLU i)))
(assert (below-lo r1))
(assert (below-lo r2))
(assert (below-lo b1))
(assert (below-lo b2))
(assert (below-lo b3))
(assert (below-lo b4))
(assert (in-range r1))
(assert (in-range r2))
(assert (in-range b1))
(assert (in-range b2))
(assert (in-range b3))
(assert (in-range b4))
(assert (above-hi r1))
(assert (above-hi r2))
(assert (above-hi b1))
(assert (above-hi b2))
(assert (above-hi b3))
(assert (above-hi b4))



; express that [l..u] \ { x, y} \subseteq A \subseteq [l..u] \ { x }
(define-fun below-A ((i Int)) Bool (=> (and (RangeLU i) (not (= i x)) (not (= i y))) (A i)))
(define-fun above-A ((i Int)) Bool (=> (A i) (and (RangeLU i) (not (= i x)))))


(assert (above-A r1))
(assert (above-A r2))
(assert (above-A b1))
(assert (above-A b2))
(assert (above-A b3))
(assert (above-A b4))
(assert (below-A r1))
(assert (below-A r2))
(assert (below-A b1))
(assert (below-A b2))
(assert (below-A b3))
(assert (below-A b4))

(check-sat)
(get-model)

Examining the result of manual encoding

Run z3 with model.compact=false We get a model, corresponding to assigned atoms:

  (define-fun RangeLU ((x!0 Int)) Bool
    (ite (= x!0 1) true
    (ite (= x!0 2) true
    (ite (= x!0 (- 1)) false
    (ite (= x!0 11) true
    (ite (= x!0 12) false
    (ite (= x!0 0) true
      true)))))))
  (define-fun A ((x!0 Int)) Bool
    (ite (= x!0 1) false
    (ite (= x!0 2) false
    (ite (= x!0 (- 1)) false
    (ite (= x!0 12) false
    (ite (= x!0 0) true
    (ite (= x!0 11) true
      false)))))))

Examining the result (II)

  • The ite guards are based on assigned atoms. We can ignore the default case. Here it is arbitrary.

  • For RangeLU we should have the interpretation $[0..11]$.

  • The set $A$ should be equal to $[0..11]$ except two elements, these are $\{1, 2\}$. So the expected interpretation of $A$ is $\{0\} \cup [3..11]$.

Array property fragment by example

Let us recall the correctness proof for the array property fragment. We will consider the special case for a single bound variable to keep it simple.

  • $inst := \{ lo - 1 \rightarrow -1, lo \rightarrow 0, x \rightarrow 1, y \rightarrow 2, hi \rightarrow 11, hi + 1 \rightarrow 12\}$

Define projection function project such that

  • for every integer z: project(z) is the nearest integer that is in the range of inst.

For example project(-3) = project(-2) = projec(-1) = -1.

Constructing $\Model_\forall$

We would like to build an interpretation $\Model_\forall$ over finite sets from the above interpretation $\Model_{inst}$ using project. Define:

\[\begin{mdmathpre}%mdk \mdmathindent{2}\Model_\forall(\mathid{z})~:=~\Model_{\mathid{inst}}(\mathid{project}(\mathid{z})) \end{mdmathpre}%mdk \]

for every integer z.

Then $\Model_\forall(x \in A) \Leftrightarrow \Model_{inst}(project(x)) \in \Model_{inst}(A).$

Proving model presevation (main theorem)

Given

\[\begin{mdmathpre}%mdk \forall \mathid{i}~:~\mathid{Guard}(\mathid{i})~\Rightarrow \mathid{F}_\mathid{V}(\mathid{i}) \end{mdmathpre}%mdk \]

Suppose we have a model for $\Model_{inst}(Guard(t) \Rightarrow F_V(t))$ for every $t$ in the instantation set $inst$.

Let us show that for every z:

  1. $\Model_\forall(Guard(z)) \Rightarrow \Model_{inst}(Guard(project(z)))$

  2. $\Model_{inst}(F_V(project(z))) \Rightarrow \Model_\forall(F_V(z))$

Then it follows from transitivity of $Guard(project(z)) \Rightarrow F_V(project(z))$ that $Guard(z) \Rightarrow F_V(z)$.

Recall what are guards and value formulas

Recall that $Guard(z)$ can be assumed to be a conjunction $z \geq t$, $z \leq t$. Then as project(z) is the a term in inst that maps to the nearest value close to z. Then, the truth value of $project(z) \geq t$, or $project(z) \leq t$ is the same. This proves (1)

$F_V$ is a disjunction of ground formulas or (negated) membership constraints $z \in A$. Assume $\Model_{inst}(F_V(project(z)))$. Then one of the disjunctions in $F_V$ is true. Suppose it is project(z) in A. Now, we constructed $\Model_\forall(z \in A) \Leftrightarrow \Model_{inst}(project(z) \in A)$. Therefore $\Model_\forall(z \in A)$.

But there is a problem!

If we construct a model from our instantiation set we don't get a well-formed model. What is going on?

The problem is that our quantified formula wasn't in the right format.

(define-fun below-A ((i Int)) Bool (=> (and (RangeLU i) (not (= i x)) (not (= i y))) (A i)))

is not in the format of $Guard \Rightarrow F_V$. Guards cannot be atoms $i \neq x$.

Every problem represents a solution in a sublime disguise

Instead we have to create multiple guards to capture the property in the array fragment:

(define-fun below-A-1 ((i Int)) Bool (=> (and (RangeLU i) (< i x) (< i y)) (A i)))
(define-fun below-A-1 ((i Int)) Bool (=> (and (RangeLU i) (< x i) (< i y)) (A i)))
(define-fun below-A-1 ((i Int)) Bool (=> (and (RangeLU i) (< y i) (< i x)) (A i)))
(define-fun below-A-1 ((i Int)) Bool (=> (and (RangeLU i) (> i x) (> i y)) (A i)))

Thus, the index set now includes $\{ x - 1, x + 1, y - 1, y + 1 \}$.

Programming the theory solver

We can now turn our attention to the following question: how do we best automatically detect instantation sets from finite set formulas?

In a nutshell we want to ensure that we have enough membership constraints for ranges to force composite constraints to evaluate correctly if we use the nearest neighbor projection function.

For example if the atomic formula $i \in [l..u] \setminus \{x, y\}$ exists then we should also evaluate it on $i - 1, i + 1$.

Outline of a procedure (I)

  • Range local terms: For every range expression $[l..u]$ define the range local terms as initially $\{l-1, l, u, u + 1\}$.

  • Saturating range local terms: Whenever $x \in [l..u]$ occurs and is assigned to true and x is not a range local term, then add $\{x-1, x + 1\}$ to range local terms and add atoms for $x - 1 \in [l..u], x + 1 \in [l..u]$.

Outline of a procedure (II) - Problem!

If we can't assume complete assignments for ground formulas, this saturation rule is insufficient.

For example, we could have ($i \in [l..u] \setminus \{ x, y \} \Rightarrow i \in A$) holds for $i = x$ because $i \in [l..u] \setminus \{ x, y \} \Rightarrow i \in [l..u]$ and $i \neq x$ and $i \neq y$ and we don't need the truth value of $i \in [l..u]$ to evaluate this formula (to false) for $i = x$.

Outline of a procedure (III) - Solution

The way we got from finite set constraints to the array property fragment was to encode every connective with universal axioms. For example,

\[\begin{mdmathpre}%mdk \mdmathindent{2}\mathid{i}~\in [\mathid{l}..\mathid{u}]~\setminus \{~\mathid{x},~\mathid{y}~\}~\Leftrightarrow \mathid{i}~\in [\mathid{l}..\mathid{u}]~\wedge \mathid{i}~\neq \mathid{x}~\wedge \mathid{i}~\neq \mathid{y} \end{mdmathpre}%mdk \]

It suffices to retrace range-local terms from axioms.

Claim: The following extension will do the trick: Whenever $x \in setop([l..u],..)$ occurs, and x is not a range local term, then add range local terms $x - 1, x + 1$.

Pub/Champaigne Quiz

A set expression is a value if:

\[\begin{mdmathpre}%mdk \mdmathindent{2}\mathid{isvalue}(\mathid{s}~\cup \mathid{t},~\mathid{s}~\cap \mathid{t},~\mathid{s}~\setminus \mathid{t})~:=~\mathid{isvalue}(\mathid{s})~\wedge \mathid{isvalue}(\mathid{t})\\ \mdmathindent{2}\mathid{isvalue}(\{~\mathid{v}~\})~~:=~\mathid{isvalue}(\mathid{v})\\ \mdmathindent{2}\mathid{isvalue}(\emptyset)~:=~\true\\ \mdmathindent{2}\mathid{isvalue}([\mathid{l}..\mathid{u}])~:=~\mathid{isvalue}(\mathid{l})~\wedge \mathid{isvalue}(\mathid{u}) \end{mdmathpre}%mdk \]

Assume isvalue can be evaluated over the base sort $s$ of $FiniteSet(s)$.

Task: Given two sets $s, t$ that satisfy isvalue, develop a good algorithm and implementation to check if they are equal.