Data Structures

Reasoning about pure functions on data — the foundation of verified algorithm design.

import logos
import logos.tactics as t

Reasoning About External Functions

When you can’t inspect a function’s source (C extensions, third-party libraries), declare it externally and axiomatize its contract:

# Declare Python's built-in sorted() as an external function
my_sorted = logos.extern("my_sorted", [list], list)

xs = logos.var("xs", list)
i = logos.var("i", int)
j = logos.var("j", int)

# Axiomatize what we know about sorted()
@logos.axiom
def sorted_length(xs: list):
    return my_sorted(xs) == my_sorted(xs)  # placeholder

# Verify concrete behavior with decide()
logos.check(logos.prove(
    logos.lit(1) < logos.lit(3),
    [t.decide()],
))
print("Ordering check: 1 < 3 ✓")
Ordering check: 1 < 3 ✓

Pure Accumulator Functions

Accumulators are a common pattern — prove properties about their results:

@logos.define
def increment(n: int):
    """n + 1"""
    return n + logos.lit(1)

# increment is strictly greater than its input
@logos.theorem
def increment_gt(n: int):
    return increment(n) > n

logos.check(increment_gt.proof(t.intro("n"), t.unfold(increment), t.linarith()))
print("increment(n) > n ✓")

# increment is non-negative when input is non-negative
@logos.theorem
def increment_nonneg(n: int):
    return (n >= logos.lit(0)) >> (increment(n) >= logos.lit(0))

logos.check(increment_nonneg.proof(t.intro("n", "h"), t.unfold(increment), t.linarith()))
print("n ≥ 0 → increment(n) ≥ 0 ✓")
increment(n) > n ✓
n ≥ 0 → increment(n) ≥ 0 ✓

Monotone Functions

A function is monotone if x ≤ y → f(x) ≤ f(y). This property is essential for binary search correctness:

@logos.define
def linear_fn(x: int):
    return logos.lit(3) * x + logos.lit(5)

@logos.theorem
def linear_fn_monotone(x: int, y: int):
    return (x <= y) >> (linear_fn(x) <= linear_fn(y))

logos.check(linear_fn_monotone.proof(
    t.intro("x", "y", "h"),
    t.unfold(linear_fn),
    t.linarith(),
))
print("3x + 5 is monotone ✓")

@logos.define
def square(x: int):
    return x * x

# Algebraic identity: (x+1)² = x² + 2x + 1
@logos.theorem
def square_succ(x: int):
    return square(x + logos.lit(1)) == square(x) + logos.lit(2) * x + logos.lit(1)

logos.check(square_succ.proof(t.intro("x"), t.unfold(square), t.ring()))
print("(x+1)² = x² + 2x + 1 ✓")

# Difference of squares: (x-y)*(x+y) = x² - y²
@logos.theorem
def square_diff(x: int, y: int):
    return (x - y) * (x + y) == square(x) - square(y)

logos.check(square_diff.proof(t.intro("x", "y"), t.unfold(square), t.ring()))
print("(x-y)(x+y) = x² - y² ✓")
3x + 5 is monotone ✓
(x+1)² = x² + 2x + 1 ✓
(x-y)(x+y) = x² - y² ✓

Function Composition Properties

Composition of linear scaling functions: prove that applying a 2× scale twice equals a 4× scale.

@logos.define
def scale_2(x: int):
    return logos.lit(2) * x

@logos.define
def scale_4(x: int):
    return logos.lit(4) * x

# scale_2 ∘ scale_2 = scale_4
@logos.theorem
def double_twice_is_quadruple(x: int):
    return scale_2(scale_2(x)) == scale_4(x)

logos.check(double_twice_is_quadruple.proof(
    t.intro("x"), t.unfold(scale_2), t.unfold(scale_2), t.unfold(scale_4), t.ring()
))
print("2*(2*x) = 4*x ✓")

# scale_2 preserves the sign
@logos.theorem
def scale_2_nonneg(x: int):
    return (x >= logos.lit(0)) >> (scale_2(x) >= logos.lit(0))

logos.check(scale_2_nonneg.proof(t.intro("x", "h"), t.unfold(scale_2), t.linarith()))
print("x ≥ 0 → 2x ≥ 0 ✓")
2*(2*x) = 4*x ✓
x ≥ 0 → 2x ≥ 0 ✓

Composing Function Properties

Prove properties of function compositions from properties of the parts:

@logos.define
def double(x: int):
    return logos.lit(2) * x

@logos.define
def triple(x: int):
    return logos.lit(3) * x

@logos.define
def sextuple(x: int):
    return logos.lit(6) * x

@logos.theorem
def double_triple_is_sextuple(x: int):
    return double(triple(x)) == sextuple(x)

logos.check(double_triple_is_sextuple.proof(
    t.intro("x"), t.unfold(double), t.unfold(triple), t.unfold(sextuple), t.ring()
))
print("2 * (3 * x) = 6 * x ✓")

# And multiplication is commutative in composition order
@logos.theorem
def triple_double_is_sextuple(x: int):
    return triple(double(x)) == sextuple(x)

logos.check(triple_double_is_sextuple.proof(
    t.intro("x"), t.unfold(triple), t.unfold(double), t.unfold(sextuple), t.ring()
))
print("3 * (2 * x) = 6 * x ✓")
2 * (3 * x) = 6 * x ✓
3 * (2 * x) = 6 * x ✓

API Contracts as Theorems

A real-world pattern: encode a function’s postcondition as a logos theorem. If the proof breaks, the contract was violated:

# A pricing function that applies a fixed flat discount
@logos.define
def apply_flat_discount(price: int):
    """price - 10  (fixed $10 discount)"""
    return price - logos.lit(10)

# Contract 1: discounted price is strictly less than original
@logos.theorem
def flat_discount_reduces_price(price: int):
    return apply_flat_discount(price) < price

logos.check(flat_discount_reduces_price.proof(
    t.intro("price"), t.unfold(apply_flat_discount), t.linarith()
))
print("Discount contract: discounted < original ✓")

# A shipping fee function
@logos.define
def add_shipping(price: int):
    """price + 5  (fixed $5 shipping)"""
    return price + logos.lit(5)

# Contract 2: after shipping, total is always greater than base price
@logos.theorem
def shipping_increases_price(price: int):
    return add_shipping(price) > price

logos.check(shipping_increases_price.proof(
    t.intro("price"), t.unfold(add_shipping), t.linarith()
))
print("Shipping contract: total > base price ✓")

# Contract 3: discount then shipping equals shipping then discount
@logos.theorem
def order_matters(price: int):
    return add_shipping(apply_flat_discount(price)) == apply_flat_discount(add_shipping(price))

logos.check(order_matters.proof(
    t.intro("price"),
    t.unfold(add_shipping), t.unfold(apply_flat_discount),
    t.ring(),
))
print("discount∘shipping == shipping∘discount (same result) ✓")
Discount contract: discounted < original ✓
Shipping contract: total > base price ✓
discount∘shipping == shipping∘discount (same result) ✓

Ground Computation Verification

Verify that specific computations give the correct answer — essentially a proof-carrying unit test:

# Verify that our formulas compute the right values for concrete inputs
for n, expected in [(1, 1), (2, 4), (3, 9), (4, 16), (5, 25)]:
    logos.check(logos.prove(
        logos.lit(n) ** logos.lit(2) == logos.lit(expected),
        [t.norm_num()],
    ))
    print(f"{n}² = {expected} ✓")
1² = 1 ✓
2² = 4 ✓
3² = 9 ✓
4² = 16 ✓
5² = 25 ✓