Interactive Theorem Proving (ITP)
Overview
The arlib.itp
module provides a framework for interactive theorem proving with SMT solvers. It offers a Python interface for writing formal proofs, defining axioms, and verifying mathematical properties.
This module serves as a bridge between Python and various SMT (Satisfiability Modulo Theories) solvers like Z3, CVC5, and Vampire, providing:
A kernel that maintains the logical integrity of proofs
Various tactics for constructing proofs
Support for datatypes and inductive definitions
A library of theories (arithmetic, sets, sequences, etc.)
Notation helpers for writing mathematical formulas
Utilities for simplification and rewriting
Core Components
Solvers
A shim layer in smt.py
that enables using different SMT solvers:
Z3 (default)
CVC5
Vampire
You can select the solver by setting the KNUCKLE_SOLVER
environment variable before import.
import os
os.environ["KNUCKLE_SOLVER"] = "cvc5" # or "vampire"
from arlib.itp import *
import arlib.itp.smt as smt
Kernel
The core logical system in kernel.py
that ensures the soundness of proofs:
Proof
- Represents a proven theoremprove()
- Attempts to prove a theorem with the selected SMT solveraxiom()
- Introduces an axiom (use with caution)define()
- Creates definitions with their corresponding axioms
# Prove a theorem
x = smt.Real('x')
theorem = smt.Implies(x > 0, x*x > 0)
proof = prove(theorem)
# Define a function
square = define("square", [x], x * x)
Tactics
Higher-level proof strategies in tactics.py
:
Lemma
- For defining and proving lemmasCalc
- For equational reasoningVarious other tactics for common proof patterns
@Lemma
def square_positive(x):
premise = x > 0
conclusion = square(x) > 0
return smt.Implies(premise, conclusion)
# Equational reasoning
@Lemma
def square_expand(a, b):
calc = Calc(square(a + b))
calc.equals((a + b) * (a + b))
calc.equals(a*a + a*b + b*a + b*b)
calc.equals(a*a + 2*a*b + b*b)
return calc.proof
Datatypes
Support for defining and working with complex data structures in datatype.py
:
Struct
- For product typesEnum
- For sum typesInductive
- For inductive datatypesInductiveRel
- For inductive relations
# Define a list datatype
List = Inductive("List")
List.declare("nil")
List.declare("cons", ("head", smt.IntSort()), ("tail", List))
List = List.create()
# Define a point struct
Point = Struct("Point", x=smt.RealSort(), y=smt.RealSort())
Theories
Mathematical theories implemented in the theories
directory:
Basic types:
int
,bool
,nat
,bitvec
,float
Data structures:
list
,set
,seq
,option
Algebraic structures in
algebra/
Real analysis in
real/
Logical foundations in
logic/
# Import a theory
from arlib.itp.theories import set
# Use the theory
A = smt.Const('A', set.SetSort(smt.IntSort()))
B = smt.Const('B', set.SetSort(smt.IntSort()))
# Prove a property about sets
union_commutative = prove(set.union(A, B) == set.union(B, A))
Notation
Helpers for writing mathematical formulas in notation.py
:
QForAll
- Universal quantifier with nicer syntaxQExists
- Existential quantifier with nicer syntaxcond
- Conditionals
# Define variables
x = smt.Real('x')
y = smt.Real('y')
# Universal quantification
forall_xy = QForAll([x, y], x + y == y + x)
# Existential quantification
exists_x = QExists([x], x*x == smt.RealVal(4))
Rewriting
Support for term rewriting in rewrite.py
:
simp
- Simplifies terms according to rewrite rules
# Define a term
x = smt.Real('x')
term = x + 0
# Simplify it
simplified = simp(term) # x
Advanced Features
Proofs by Induction
# Prove a property about list length
@Lemma
def length_non_negative(xs):
# The property to prove
P = smt.Lambda([xs], length(xs) >= 0)
# Base case: nil
base_case = prove(P(List.nil))
# Inductive case
x = smt.Int('x')
xs1 = smt.Const('xs1', List)
inductive_case = prove(
smt.Implies(P(xs1), P(List.cons(x, xs1)))
)
# Apply induction principle
return prove(P(xs), by=[base_case, inductive_case])
Debugging Proofs
try:
proof = prove(complex_theorem, timeout=10000, dump=True)
except Exception as e:
print(f"Proof failed: {e}")
External Solver Integration
The module supports integrating with external provers through the solvers
package:
VampireSolver
- Interface to the Vampire theorem proverOther solvers like Gappa, EggLog, and APRove
Configuration
Global configuration options in config.py
:
solver
- The default solver to useadmit_enabled
- Whether to allow admitting theorems without prooftiming
- Enable/disable performance logging
Installation
The module is part of the arlib package. External solvers may need separate installation.
See solvers/install.sh
for installing supported external solvers.
API Reference
Core Classes
Proof
class Proof:
def __init__(self, thm: smt.BoolRef, reason: list[Any], admit: bool = False)
Represents a proven theorem.
Properties:
thm
: The theorem that has been proven (a Z3 BoolRef)reason
: The reasons or premises used in the proofadmit
: Whether the theorem was admitted without proof
Methods:
__call__(*args)
: Instance a universally quantified theorem with specific terms
Core Functions
prove
def prove(thm: smt.BoolRef, by: Proof | Iterable[Proof] = [],
admit=False, timeout=1000, dump=False, solver=None) -> Proof
Attempts to prove a theorem using the specified SMT solver.
Parameters:
thm
: The theorem to proveby
: Previously proven lemmas to use as premisesadmit
: If True, admit the theorem without prooftimeout
: Solver timeout in millisecondsdump
: Whether to dump solver state (for debugging)solver
: Custom solver to use
Returns:
A
Proof
object representing the proven theorem
axiom
def axiom(thm: smt.BoolRef, by=["axiom"]) -> Proof
Declares an axiom (use with caution).
Parameters:
thm
: The axiom to declareby
: A description or reason for the axiom
Returns:
A
Proof
object for the axiom
define
def define(name: str, args: list[smt.ExprRef], body: smt.ExprRef,
lift_lambda=False) -> smt.FuncDeclRef
Defines a new function and creates its corresponding definition axiom.
Parameters:
name
: The name of the functionargs
: The arguments of the functionbody
: The body/definition of the functionlift_lambda
: Whether to lift lambda expressions
Returns:
The function declaration