Skip to main content
Z3 4.17.0 introduced a new FiniteSets theory solver for reasoning about finite sets. This theory provides operations for creating, manipulating, and querying finite sets over any base sort.
The FiniteSets theory was added in Z3 version 4.17.0. Support for set.range and set.map is partial. Support for set.size exists but without optimization.

Overview

The FiniteSets theory provides:
  • Set construction: Empty sets, singletons, ranges
  • Set operations: Union, intersection, difference
  • Predicates: Membership, subset
  • Functions: Size, map, filter
  • Base sorts: Sets over any sort (integers, booleans, custom datatypes, etc.)

Basic Set Operations

Creating Sets

from z3 import *

# Create finite set sort over integers
IntSet = SetSort(IntSort())

# Empty set
empty = EmptySet(IntSet)

# Singleton set
singleton = SetSingleton(IntVal(5))

# Union to build sets
set1 = SetUnion(SetSingleton(IntVal(1)), SetSingleton(IntVal(2)))
set2 = SetUnion(SetSingleton(IntVal(3)), 
                SetUnion(SetSingleton(IntVal(4)), SetSingleton(IntVal(5))))

# Set from range: {low, low+1, ..., high-1}
range_set = SetRange(IntVal(1), IntVal(10))  # {1, 2, 3, ..., 9}

Set Operations

from z3 import *

IntSet = SetSort(IntSort())

A = Const('A', IntSet)
B = Const('B', IntSet)

# Union: A ∪ B
union = SetUnion(A, B)

# Intersection: A ∩ B
intersect = SetIntersect(A, B)

# Difference: A \ B (elements in A but not in B)
diff = SetDifference(A, B)

# Membership: x ∈ A
x = Int('x')
member = SetMember(x, A)

# Subset: A ⊆ B
subset = SetSubset(A, B)

# Example: prove (A ∪ B) \ B = A \ B
s = Solver()
lhs = SetDifference(SetUnion(A, B), B)
rhs = SetDifference(A, B)
s.add(lhs != rhs)
print(s.check())  # unsat (property holds)

Set Cardinality

from z3 import *

IntSet = SetSort(IntSort())
A = Const('A', IntSet)

# Cardinality: |A|
card = SetSize(A)

s = Solver()

# Create specific set
my_set = SetUnion(SetSingleton(IntVal(1)), 
                  SetUnion(SetSingleton(IntVal(2)), SetSingleton(IntVal(3))))

s.add(A == my_set)
s.add(card == SetSize(A))

if s.check() == sat:
    m = s.model()
    print(f"Set: {m[A]}")
    print(f"Size: {m.eval(card)}")

Membership and Subset Constraints

Membership Constraints

from z3 import *

s = Solver()

IntSet = SetSort(IntSort())
A = Const('A', IntSet)
x, y, z = Ints('x y z')

# Elements in set
s.add(SetMember(x, A))
s.add(SetMember(y, A))
s.add(Not(SetMember(z, A)))

# Constraints on elements
s.add(x > 0)
s.add(y > x)
s.add(z > y)

# Set size
s.add(SetSize(A) == 2)

if s.check() == sat:
    m = s.model()
    print(f"A = {m[A]}")
    print(f"x = {m[x]}, y = {m[y]}, z = {m[z]}")

Subset Relationships

from z3 import *

s = Solver()

IntSet = SetSort(IntSort())
A = Const('A', IntSet)
B = Const('B', IntSet)
C = Const('C', IntSet)

# A ⊆ B ⊆ C
s.add(SetSubset(A, B))
s.add(SetSubset(B, C))

# C has exactly 5 elements
s.add(SetSize(C) == 5)

# A is non-empty
s.add(SetSize(A) > 0)

# B is strictly between A and C
s.add(A != B)
s.add(B != C)

if s.check() == sat:
    m = s.model()
    print(f"A = {m[A]}")
    print(f"B = {m[B]}")
    print(f"C = {m[C]}")

Advanced Operations

Set Mapping

Apply a function to all elements of a set:
from z3 import *

s = Solver()

IntSet = SetSort(IntSort())
A = Const('A', IntSet)

# Function: double each element
x = Int('x')
double = Lambda([x], x * 2)

# Map function over set
B = SetMap(double, A)

# Original set: {1, 2, 3}
s.add(A == SetUnion(SetSingleton(IntVal(1)),
                    SetUnion(SetSingleton(IntVal(2)), 
                            SetSingleton(IntVal(3)))))

if s.check() == sat:
    m = s.model()
    print(f"Original: {m[A]}")
    print(f"Doubled: {m[B]}")  # Should be {2, 4, 6}
Support for set.map is currently partial in Z3 4.17.0. Some complex mapping operations may not be fully supported.

Set Filtering

Filter elements based on a predicate:
from z3 import *

s = Solver()

IntSet = SetSort(IntSort())
A = Const('A', IntSet)

# Predicate: keep only even numbers
x = Int('x')
is_even = Lambda([x], x % 2 == 0)

# Filter set
evens = SetFilter(is_even, A)

# Original set: range 1..10
s.add(A == SetRange(IntVal(1), IntVal(11)))

if s.check() == sat:
    m = s.model()
    print(f"Original: {m[A]}")
    print(f"Evens: {m.eval(evens)}")

Integer Ranges

from z3 import *

s = Solver()

IntSet = SetSort(IntSort())

# Range: {low, low+1, ..., high-1}
range1 = SetRange(IntVal(1), IntVal(5))   # {1, 2, 3, 4}
range2 = SetRange(IntVal(3), IntVal(8))   # {3, 4, 5, 6, 7}

# Intersection of ranges
intersection = SetIntersect(range1, range2)

s.add(SetSize(intersection) > 0)

if s.check() == sat:
    m = s.model()
    print(f"Range 1: {m.eval(range1)}")
    print(f"Range 2: {m.eval(range2)}")
    print(f"Intersection: {m.eval(intersection)}")
    print(f"Intersection size: {m.eval(SetSize(intersection))}")
Support for set.range is partial in the current implementation. Basic range operations work, but complex range constraints may have limitations.

Sets Over Different Sorts

Boolean Sets

from z3 import *

BoolSet = SetSort(BoolSort())

# There are only 4 possible boolean sets
empty = EmptySet(BoolSet)
just_true = SetSingleton(BoolVal(True))
just_false = SetSingleton(BoolVal(False))
both = SetUnion(SetSingleton(BoolVal(True)), SetSingleton(BoolVal(False)))

s = Solver()
B = Const('B', BoolSet)

# B is non-empty and not the full set
s.add(B != empty)
s.add(B != both)

if s.check() == sat:
    m = s.model()
    print(f"B = {m[B]}")  # Either {True} or {False}

Sets of Bit-Vectors

from z3 import *

BV8Set = SetSort(BitVecSort(8))

A = Const('A', BV8Set)
s = Solver()

# Set contains specific bit-vectors
s.add(SetMember(BitVecVal(0xFF, 8), A))
s.add(SetMember(BitVecVal(0x00, 8), A))
s.add(SetSize(A) == 2)

if s.check() == sat:
    print(s.model()[A])

Sets of Custom Datatypes

from z3 import *

# Define a datatype
Person = Datatype('Person')
Person.declare('person', ('name', StringSort()), ('age', IntSort()))
Person = Person.create()

# Set of persons
PersonSet = SetSort(Person)

group = Const('group', PersonSet)
s = Solver()

# Create some people
alice = Person.person(StringVal("Alice"), IntVal(30))
bob = Person.person(StringVal("Bob"), IntVal(25))

# Add to set
s.add(SetMember(alice, group))
s.add(SetMember(bob, group))
s.add(SetSize(group) == 2)

if s.check() == sat:
    print(s.model()[group])

Practical Examples

Sudoku Constraints

from z3 import *

def all_different_set(cells):
    """All cells have different values using sets"""
    IntSet = SetSort(IntSort())
    value_set = Const('values', IntSet)
    
    constraints = []
    
    # Each cell value is in the set
    for cell in cells:
        constraints.append(SetMember(cell, value_set))
    
    # Set has same size as number of cells (all different)
    constraints.append(SetSize(value_set) == len(cells))
    
    return And(constraints)

# Sudoku row constraint
row = [Int(f'cell_{i}') for i in range(9)]
s = Solver()

# Each cell 1-9
for cell in row:
    s.add(cell >= 1, cell <= 9)

# All different
s.add(all_different_set(row))

if s.check() == sat:
    m = s.model()
    print([m[cell] for cell in row])

Set Partitioning Problem

from z3 import *

# Partition {1,2,3,4,5,6} into two sets with equal sum
s = Solver()

IntSet = SetSort(IntSort())
A = Const('A', IntSet)
B = Const('B', IntSet)

# Full set
full_set = SetRange(IntVal(1), IntVal(7))  # {1,2,3,4,5,6}

# A and B partition the full set
s.add(SetUnion(A, B) == full_set)
s.add(SetIntersect(A, B) == EmptySet(IntSet))

# Both non-empty
s.add(SetSize(A) > 0)
s.add(SetSize(B) > 0)

# Equal sums (using helper function)
def set_sum(S):
    # This is conceptual - actual implementation would need
    # to enumerate or use quantifiers
    result = Int('sum')
    # ... implementation details ...
    return result

# For this example, we'll constrain manually
s.add(SetSize(A) == 3)  # Force specific partition structure

if s.check() == sat:
    m = s.model()
    print(f"Set A: {m[A]}")
    print(f"Set B: {m[B]}")

C API Reference

The C API for finite sets (defined in api_finite_set.cpp):
#include <z3.h>

// Create finite set sort
Z3_sort elem_sort = Z3_mk_int_sort(ctx);
Z3_sort set_sort = Z3_mk_finite_set_sort(ctx, elem_sort);

// Check if sort is finite set
bool is_set = Z3_is_finite_set_sort(ctx, set_sort);

// Get base sort
Z3_sort base = Z3_get_finite_set_sort_basis(ctx, set_sort);

// Empty set
Z3_ast empty = Z3_mk_finite_set_empty(ctx, set_sort);

// Singleton
Z3_ast elem = Z3_mk_int(ctx, 5, Z3_mk_int_sort(ctx));
Z3_ast singleton = Z3_mk_finite_set_singleton(ctx, elem);

// Operations
Z3_ast union_set = Z3_mk_finite_set_union(ctx, set1, set2);
Z3_ast intersect = Z3_mk_finite_set_intersect(ctx, set1, set2);
Z3_ast diff = Z3_mk_finite_set_difference(ctx, set1, set2);

// Membership
Z3_ast member = Z3_mk_finite_set_member(ctx, elem, set);

// Subset
Z3_ast subset = Z3_mk_finite_set_subset(ctx, set1, set2);

// Size
Z3_ast size = Z3_mk_finite_set_size(ctx, set);

// Map (partial support)
Z3_ast mapped = Z3_mk_finite_set_map(ctx, func, set);

// Filter
Z3_ast filtered = Z3_mk_finite_set_filter(ctx, predicate, set);

// Range
Z3_ast range = Z3_mk_finite_set_range(ctx, low, high);

Key Functions

FunctionDescription
Z3_mk_finite_set_sortCreate finite set sort
Z3_mk_finite_set_emptyCreate empty set
Z3_mk_finite_set_singletonCreate singleton set
Z3_mk_finite_set_unionSet union
Z3_mk_finite_set_intersectSet intersection
Z3_mk_finite_set_differenceSet difference
Z3_mk_finite_set_memberMembership test
Z3_mk_finite_set_subsetSubset test
Z3_mk_finite_set_sizeSet cardinality
Z3_mk_finite_set_mapMap function over set (partial)
Z3_mk_finite_set_filterFilter set by predicate
Z3_mk_finite_set_rangeInteger range (partial)

Current Limitations

As noted in the Z3 4.17.0 release notes:
  • set.range support is partial: Basic integer ranges work, but complex range operations may be limited
  • set.map support is partial: Simple mappings work, but complex transformations may not be fully supported
  • set.size lacks optimization: Cardinality constraints work but are not optimized. Performance may be slower for large sets or complex size constraints
For production use, test your specific use case and file GitHub issues for improvements.

See Also

Build docs developers (and LLMs) love