Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Chapter 13: Meta-Theory & Expressivity

Motivation

What can the 12,288 Hologram model actually compute? How does it relate to Church-Turing thesis? Can we embed lambda calculus or linear logic? This chapter characterizes the model’s expressivity, establishing both its power and its limits. We’ll prove that while the finite lattice seems restrictive, careful use of gauge freedom, content addressing, and temporal multiplexing yields surprising computational universality.

Characterizing Denotable Functions

The Space of Lawful Functions

Definition 13.1 (Denotable Function): A function f: A → B is denotable in the Hologram model if there exists a process object P such that:

[[P]](encode(a)) = encode(f(a)) for all a ∈ A

where encode maps external values to lawful configurations.

Finite but Universal

Theorem 13.1 (Bounded Universality): The class of denotable functions includes all computable functions whose space complexity is bounded by 12,288.

Proof: We construct a universal interpreter U on the lattice:

def universal_interpreter(program: Configuration, input: Configuration) -> Configuration:
    # Allocate lattice regions
    PROGRAM_REGION = range(0, 4096)       # Pages 0-15
    DATA_REGION = range(4096, 8192)       # Pages 16-31
    STACK_REGION = range(8192, 10240)     # Pages 32-39
    HEAP_REGION = range(10240, 12288)     # Pages 40-47

    # Initialize
    lattice = Lattice()
    lattice.write_region(PROGRAM_REGION, program)
    lattice.write_region(DATA_REGION, input)

    # Interpretation loop
    pc = 0  # Program counter
    sp = 0  # Stack pointer

    while pc < len(PROGRAM_REGION):
        # Fetch instruction
        instr = lattice.read(PROGRAM_REGION[pc])

        # Decode via R96 class
        opcode = R(instr)

        # Execute
        if opcode == 0:  # HALT
            break
        elif opcode == 1:  # PUSH
            value = lattice.read(DATA_REGION[instr % len(DATA_REGION)])
            lattice.write(STACK_REGION[sp], value)
            sp = (sp + 1) % len(STACK_REGION)
        elif opcode == 2:  # POP
            sp = (sp - 1) % len(STACK_REGION)
            value = lattice.read(STACK_REGION[sp])
        elif opcode == 3:  # ADD
            a = lattice.read(STACK_REGION[(sp-2) % len(STACK_REGION)])
            b = lattice.read(STACK_REGION[(sp-1) % len(STACK_REGION)])
            lattice.write(STACK_REGION[(sp-2) % len(STACK_REGION)], (a + b) % 256)
            sp = (sp - 1) % len(STACK_REGION)
        # ... more opcodes ...

        pc += 1

    # Extract result
    result = Configuration()
    for i in range(len(DATA_REGION)):
        result.set(Site.from_linear(i), lattice.read(DATA_REGION[i]))

    return result

This interpreter can simulate any Turing machine using ≤12,288 space. □

Characterization via Resource Classes

Theorem 13.2 (Expressivity Hierarchy):

CONST ⊂ CC ⊂ RC ⊂ HC ⊂ WC(log n) ⊂ WC(n) ⊂ ALL

where:

  • CONST: Constant-space functions
  • CC: Conservation-checkable functions
  • RC: Resonance-commutative functions
  • HC: Height-commutative functions
  • WC(k): Window-k verifiable functions

Each class corresponds to a verification complexity:

def classify_function(f):
    # Test if constant space
    if verify_with_receipts_only(f):
        return "CC"

    # Test if resonance-commutative
    if all_ops_within_r96_classes(f):
        return "RC"

    # Test if height-commutative
    if all_ops_within_pages(f):
        return "HC"

    # Test window size needed
    k = min_verification_window(f)
    return f"WC({k})"

Lambda Calculus Embeddings

Encoding Lambda Terms

We can embed untyped lambda calculus into the Hologram model:

Definition 13.2 (Lambda Encoding):

def encode_lambda_term(term):
    if isinstance(term, Variable):
        # Variables encoded as R96 class 0-31
        return Configuration(
            r96_class=term.index % 32,
            data=[term.index]
        )

    elif isinstance(term, Abstraction):
        # Lambda encoded as R96 class 32-63
        param = encode_lambda_term(term.param)
        body = encode_lambda_term(term.body)
        return Configuration(
            r96_class=32 + hash(term) % 32,
            data=[LAMBDA_MARKER, param, body]
        )

    elif isinstance(term, Application):
        # Application encoded as R96 class 64-95
        func = encode_lambda_term(term.func)
        arg = encode_lambda_term(term.arg)
        return Configuration(
            r96_class=64 + hash(term) % 32,
            data=[APP_MARKER, func, arg]
        )

Beta Reduction as Morphism

class BetaReduction(Morphism):
    """Implement β-reduction as a Hologram morphism."""

    def apply(self, config: Configuration) -> Configuration:
        term = decode_lambda_term(config)

        if is_redex(term):
            # (λx.e) v → e[x:=v]
            reduced = substitute(term.body, term.param, term.arg)
            return encode_lambda_term(reduced)

        # Search for redex in subterms
        if isinstance(term, Application):
            new_func = BetaReduction().apply(encode_lambda_term(term.func))
            new_arg = BetaReduction().apply(encode_lambda_term(term.arg))
            return encode_lambda_term(Application(
                decode_lambda_term(new_func),
                decode_lambda_term(new_arg)
            ))

        return config  # No reduction possible

Church Numerals

def church_numeral(n: int) -> Configuration:
    """Encode Church numeral n."""
    # n = λf.λx.f^n(x)
    if n == 0:
        # λf.λx.x
        return encode_lambda_term(
            Lambda("f", Lambda("x", Var("x")))
        )
    else:
        # λf.λx.f(...f(x)...)
        body = Var("x")
        for _ in range(n):
            body = App(Var("f"), body)
        return encode_lambda_term(
            Lambda("f", Lambda("x", body))
        )

# Arithmetic on Church numerals
def church_add():
    # λm.λn.λf.λx.m f (n f x)
    return encode_lambda_term(
        Lambda("m", Lambda("n", Lambda("f", Lambda("x",
            App(App(Var("m"), Var("f")),
                App(App(Var("n"), Var("f")), Var("x")))
        ))))
    )

Y Combinator and Recursion

def y_combinator() -> Configuration:
    """The Y combinator for recursion."""
    # Y = λf.(λx.f (x x)) (λx.f (x x))
    inner = Lambda("x", App(Var("f"), App(Var("x"), Var("x"))))
    return encode_lambda_term(
        Lambda("f", App(inner, inner))
    )

def factorial_generator():
    """Generator for factorial function."""
    # F = λf.λn. if n=0 then 1 else n * f(n-1)
    return encode_lambda_term(
        Lambda("f", Lambda("n",
            IfZero(Var("n"),
                   church_numeral(1),
                   Mult(Var("n"), App(Var("f"), Pred(Var("n")))))
        ))
    )

# Factorial = Y F
factorial = apply_morphism(
    y_combinator(),
    factorial_generator()
)

Linear Logic via Budgets

Linear Types as Budgeted Types

Linear logic’s “use exactly once” constraint maps perfectly to budget accounting:

Definition 13.3 (Linear Type Encoding):

class LinearType:
    def __init__(self, base_type, usage_budget=1):
        self.base_type = base_type
        self.usage_budget = usage_budget

    def check(self, term, context):
        # Count uses of each variable
        usage_counts = count_variable_uses(term)

        for var, count in usage_counts.items():
            if var in context:
                linear_type = context[var]
                if isinstance(linear_type, LinearType):
                    if count != linear_type.usage_budget:
                        raise LinearityViolation(f"{var} used {count} times, expected {linear_type.usage_budget}")

        return True

Linear Lambda Calculus

def encode_linear_lambda(term, context):
    """Encode linear lambda calculus with budget tracking."""
    config = encode_lambda_term(term)

    # Add budget constraints
    for var in free_variables(term):
        if var in context and isinstance(context[var], LinearType):
            # Charge budget for variable use
            config.budget_used += context[var].usage_budget

    # Verify linearity
    if not verify_linear_usage(term, context):
        config.budget_used = float('inf')  # Mark as illegal

    return config

Resource-Aware Computation

class ResourcedComputation:
    """Computation with explicit resource bounds."""

    def __init__(self, budget: int):
        self.total_budget = budget
        self.used_budget = 0

    def compute(self, term):
        if self.used_budget >= self.total_budget:
            raise BudgetExhausted()

        # Each reduction costs budget
        while is_reducible(term):
            term = reduce_once(term)
            self.used_budget += 1

            if self.used_budget >= self.total_budget:
                return PartialResult(term, self.used_budget)

        return CompleteResult(term, self.used_budget)

Proof Nets as Process Objects

Linear logic proof nets map to process objects:

def proof_net_to_process(net):
    """Convert linear logic proof net to process object."""
    process = Process.Identity()

    # Each link becomes a morphism
    for link in net.links:
        if link.type == "axiom":
            # A ⊸ A^⊥
            morph = AxiomMorphism(link.formula)
        elif link.type == "cut":
            # Connect A and A^⊥
            morph = CutMorphism(link.formula)
        elif link.type == "tensor":
            # A ⊗ B
            morph = TensorMorphism(link.left, link.right)
        elif link.type == "par":
            # A ⅋ B
            morph = ParMorphism(link.left, link.right)

        process = process.compose(morph)

    return process

Embedding Recursion Schemes

Primitive Recursion

def primitive_recursion(base_case, recursive_case):
    """Implement primitive recursion on the lattice."""

    def rec(n):
        if n == 0:
            return base_case
        else:
            # Use content addressing for memoization
            address = H(encode_int(n))

            # Check if already computed
            if lattice.get(address) != 0:
                return lattice.get(address)

            # Compute recursively
            prev = rec(n - 1)
            result = recursive_case(n, prev)

            # Store for reuse
            lattice.set(address, result)
            return result

    return rec

Structural Recursion

def structural_recursion(structure):
    """Recursion following data structure."""

    def process(config: Configuration) -> Configuration:
        structure_type = get_structure_type(config)

        if structure_type == "leaf":
            return base_morphism.apply(config)

        elif structure_type == "node":
            # Process children
            left = extract_left(config)
            right = extract_right(config)

            # Recursive calls (via content addressing!)
            left_result = process(left)
            right_result = process(right)

            # Combine results
            return combine_morphism.apply(left_result, right_result)

    return process

Corecursion and Streams

class Stream:
    """Infinite streams via corecursion."""

    def __init__(self, head, tail_generator):
        self.head = head
        self.tail_generator = tail_generator
        self._tail_cache = None

    @property
    def tail(self):
        if self._tail_cache is None:
            # Generate tail lazily
            self._tail_cache = self.tail_generator()
        return self._tail_cache

def fibonacci_stream():
    """Infinite Fibonacci sequence."""

    def fib_gen(a, b):
        return Stream(a, lambda: fib_gen(b, a + b))

    return fib_gen(0, 1)

# Take first n elements
def take(stream, n):
    result = []
    current = stream
    for _ in range(n):
        result.append(current.head)
        current = current.tail
    return result

Expressivity Limits

What Cannot Be Expressed

Theorem 13.3 (Expressivity Limits): The following cannot be directly expressed in the Hologram model:

  1. Unbounded space computation: Any computation requiring >12,288 sites
  2. True randomness: All operations are deterministic
  3. Unverifiable computation: Every operation must produce receipts
  4. Non-lawful states: Configurations violating conservation laws

Encoding Strategies for Limits

Despite limits, we can approximate:

def handle_unbounded_computation(big_computation):
    """Handle computation exceeding lattice size."""

    # Strategy 1: Temporal multiplexing
    def chunk_computation():
        chunk_size = 12288 // 2  # Half lattice for data
        for chunk in partition(big_computation, chunk_size):
            result = process_chunk(chunk)
            # Store result via CAM
            address = H(result)
            store_external(address, result)

    # Strategy 2: Streaming with witnesses
    def stream_computation():
        stream = create_stream(big_computation)
        while not stream.done():
            chunk = stream.next_chunk()
            witness = process_with_witness(chunk)
            emit_witness(witness)

    # Strategy 3: Hierarchical decomposition
    def hierarchical_computation():
        if size(big_computation) <= LATTICE_SIZE:
            return direct_compute(big_computation)
        else:
            # Recursive decomposition
            parts = decompose(big_computation)
            results = [hierarchical_computation(p) for p in parts]
            return merge_results(results)

Pseudo-Randomness via Chaos

def pseudo_random_generator(seed: Configuration) -> Configuration:
    """Generate pseudo-randomness via chaotic dynamics."""

    # Use sensitive dependence on initial conditions
    current = seed
    for _ in range(1000):  # Many iterations
        # Apply chaotic map
        current = chaotic_morphism(current)

    # Extract "random" bits from final state
    return extract_random_bits(current)

def chaotic_morphism(config: Configuration) -> Configuration:
    """A morphism with chaotic dynamics."""
    new_config = Configuration(lattice=Lattice())

    for i in range(LATTICE_SIZE):
        site = Site.from_linear(i)
        value = config.lattice.get(site)

        # Logistic map in discrete form
        new_value = (4 * value * (255 - value) // 255) % 256
        new_config.lattice.set(site, new_value)

    return new_config

Completeness Results

Computational Completeness

Theorem 13.4 (Bounded Turing Completeness): For any Turing machine M and input x, if M halts on x using space S ≤ 12,288, then there exists a process object P such that [P] = encode(M(x)).

Logical Completeness

Theorem 13.5 (Proof-Theoretic Completeness): For any proof in intuitionistic logic that fits in 12,288 sites, there exists a corresponding witness chain in the Hologram model.

Type-Theoretic Completeness

Theorem 13.6 (Type System Embedding): Any decidable type system can be embedded as conservation law checking in the Hologram model.

Exercises

Exercise 13.1: Prove that the halting problem is decidable for Hologram programs (hint: finite state space).

Exercise 13.2: Implement the SK combinator calculus in the Hologram model.

Exercise 13.3: Show that every primitive recursive function is denotable.

Exercise 13.4: Encode System F types using conservation laws.

Exercise 13.5: Prove that gauge quotient doesn’t reduce expressivity.

Takeaways

  1. Bounded but universal: Can compute anything that fits in 12,288 space
  2. Lambda calculus embeds naturally: Variables, abstractions, applications as configurations
  3. Linear logic via budgets: Resource tracking built into the model
  4. Recursion through content addressing: Automatic memoization
  5. Limits are physical: Cannot exceed lattice size or violate conservation
  6. Completeness for bounded computation: Turing-complete within space bounds

The Hologram model achieves surprising expressivity despite—or perhaps because of—its finite, lawful structure.


Next: Chapter 14 explores normalization and confluence properties.