Closes the structural gap identified in the 2026-05-17 assessment:
the PropositionGraph was a post-hoc descriptor of what the field walk
already produced. It is now a forward constraint that shapes what the
walk is ALLOWED to produce.
== generate/graph_constraint.py (new) ==
GraphConstraint — converts a PropositionGraph into an AdmissibilityRegion
before generate() runs, not after. The region's allowed_indices are the
intersection of:
- subject versor neighbourhood (top-k by CGA inner product)
- object versor neighbourhood (top-k by CGA inner product)
- any explicitly named node surfaces already in-vocabulary
This is the Pillar 1 → Pillar 2 coupling that was missing:
geometry (CGA) → structure (graph) → propagation (generate)
build_graph_constraint(graph, vocab, *, top_k) is the public entry.
The region label encodes the graph's root node IDs so the admissibility
trace identifies the constraint source.
== generate/stream.py (updated) ==
generate() already accepts an AdmissibilityRegion. No new API needed —
graph_constraint.build_graph_constraint() produces one.
== evals/industry_demos/ (new) ==
Four standalone demo scripts that each make ONE falsifiable claim no
transformer-LLM wrapper can reproduce. Each script runs independently
via `python -m evals.industry_demos.<name>` and exits 0 on pass / 1 on
fail. Each prints structured evidence to stdout.
demo_01_forward_constraint.py
Claim: When the PropositionGraph names subject=light, obj=truth, the
generation walk is constrained to the CGA neighbourhood of those
versors BEFORE any tokens are produced. The allowed_indices set is
computed from geometry, not from a prompt filter. Demonstrated by
showing the AdmissibilityRegion is non-trivial (< full vocab) and
that all generated tokens score positive CGA inner product against
the constraint field.
demo_02_geometry_drives_identity.py
Claim: Swapping the identity pack (precision_first vs generosity_first)
on identical input produces structurally different surfaces via the
manifold alignment path — not via a system-prompt swap. Demonstrated
by running two ChatRuntime instances with different identity_pack IDs
on the same text, showing hedge_rate and identity_score.alignment
differ, and that the manifold alignment_threshold differs at the
algebra level (not just the text level).
demo_03_deterministic_audit.py
Claim: Three independently constructed ChatRuntime instances on the
same input produce byte-identical JSONL audit lines. Demonstrated
by attaching JsonlBufferSink to each, running chat(), and asserting
hash equality of the emitted lines (modulo the 'turn' field which is
per-instance sequential). This is architectural determinism — not
seeded randomness.
demo_04_exact_recall_scale.py
Claim: CGA vault recall is exact (100%) at N=100, N=1_000, N=10_000.
The needle versor is recovered at rank-1 by cga_inner scan regardless
of vault size. No approximate nearest-neighbour index. No FAISS.
No degradation curve. Demonstrated inline with timing so the
linear-scan cost is visible alongside the 100% recall.
== tests/test_graph_constraint.py (new) ==
8 tests:
- build_graph_constraint returns an AdmissibilityRegion
- allowed_indices is a strict subset of vocab (non-trivial constraint)
- all constraint indices score positive cga_inner against at least
one node versor
- empty graph returns unconstrained region (safe fallback)
- two-node graph unions both neighbourhoods
- constraint label encodes root node IDs
- round-trip: constraint region feeds generate() without raising
- forward vs post-hoc: constrained walk produces tokens in the
region; unconstrained walk may not (statistical, seeded vocab)
Co-Authored-By: Perplexity AI
159 lines
5.6 KiB
Python
159 lines
5.6 KiB
Python
"""
|
|
generate/graph_constraint.py — PropositionGraph as forward AdmissibilityRegion.
|
|
|
|
This module closes the structural gap identified 2026-05-17:
|
|
|
|
Before: PropositionGraph was built AFTER generate() ran, from
|
|
the walk's nearest-node results. It described what the
|
|
field already produced.
|
|
|
|
After: PropositionGraph is converted into an AdmissibilityRegion
|
|
BEFORE generate() runs. The region constrains which vocab
|
|
indices the walk may visit, derived purely from the CGA
|
|
geometry of the graph's named nodes.
|
|
|
|
This is the Pillar 1 → Pillar 2 → Pillar 3 coupling:
|
|
geometry (CGA versor neighbourhood) →
|
|
structure (PropositionGraph nodes) →
|
|
propagation (AdmissibilityRegion fed to generate())
|
|
|
|
Design constraints (matching the seven axioms):
|
|
- Geometry-first: the allowed set is determined by CGA inner product
|
|
against node versors, not by string matching or rule lists.
|
|
- Propagation-over-mutation: the region is computed once before
|
|
propagation begins; nothing inside generate() is mutated.
|
|
- Dual-correction: an empty graph returns an unconstrained region
|
|
(identity / pass-through) so the caller's fallback path is safe.
|
|
- Reconstruction-over-storage: the region encodes the constraint
|
|
lightly (an index set + label); it does not store every versor.
|
|
- Compilation-last: no tensors, no kernels — the index set is a
|
|
plain frozenset until AdmissibilityRegion wraps it.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import numpy as np
|
|
|
|
from algebra.cga import cga_inner
|
|
from generate.admissibility import AdmissibilityRegion, AdmissibilitySource
|
|
from generate.graph_planner import PropositionGraph
|
|
|
|
_DEFAULT_TOP_K = 8
|
|
|
|
|
|
def _node_versors(
|
|
graph: PropositionGraph,
|
|
vocab,
|
|
) -> list[np.ndarray]:
|
|
"""Collect CGA versors for every named surface in the graph.
|
|
|
|
Checks subject, predicate, and obj for each node. Surfaces not in
|
|
vocabulary are silently skipped — the constraint degrades gracefully
|
|
rather than raising on OOV nodes.
|
|
"""
|
|
versors: list[np.ndarray] = []
|
|
seen: set[str] = set()
|
|
for node in graph.nodes:
|
|
for surface in (node.subject, node.predicate, node.obj):
|
|
surface = surface.strip().casefold()
|
|
if not surface or surface in seen or surface.startswith("<"):
|
|
continue
|
|
seen.add(surface)
|
|
try:
|
|
v = vocab.get_versor(surface)
|
|
versors.append(np.asarray(v, dtype=np.float32))
|
|
except KeyError:
|
|
continue
|
|
return versors
|
|
|
|
|
|
def _neighbourhood_indices(
|
|
node_versors: list[np.ndarray],
|
|
vocab,
|
|
top_k: int,
|
|
) -> frozenset[int]:
|
|
"""Union the top-k CGA-nearest indices for each node versor.
|
|
|
|
For each anchor versor, scan the vocabulary and collect the
|
|
top_k indices with the highest cga_inner score. Union all
|
|
neighbourhoods — the region allows any index that is close to
|
|
ANY named graph node.
|
|
|
|
This is an exact scan (O(|vocab| * |nodes|)). Vocab sizes in
|
|
CORE are bounded (language packs, not embedding tables), so this
|
|
is fast in practice.
|
|
"""
|
|
indices: set[int] = set()
|
|
n = len(vocab)
|
|
for anchor in node_versors:
|
|
scores: list[tuple[float, int]] = []
|
|
for idx in range(n):
|
|
v = vocab.get_versor_at(idx)
|
|
score = float(cga_inner(np.asarray(v, dtype=np.float32), anchor))
|
|
scores.append((score, idx))
|
|
scores.sort(key=lambda x: -x[0])
|
|
for score, idx in scores[:top_k]:
|
|
if score > 0.0:
|
|
indices.add(idx)
|
|
return frozenset(indices)
|
|
|
|
|
|
def _constraint_label(graph: PropositionGraph) -> str:
|
|
"""Stable label encoding the graph's root node IDs."""
|
|
roots = graph.roots()
|
|
if not roots:
|
|
roots = tuple(n.node_id for n in graph.nodes)
|
|
return "graph:" + ",".join(sorted(roots))
|
|
|
|
|
|
def build_graph_constraint(
|
|
graph: PropositionGraph,
|
|
vocab,
|
|
*,
|
|
top_k: int = _DEFAULT_TOP_K,
|
|
) -> AdmissibilityRegion:
|
|
"""Convert a PropositionGraph into an AdmissibilityRegion.
|
|
|
|
The region's allowed_indices is the union of the CGA top-k
|
|
neighbourhoods of every named surface in the graph. The walk
|
|
is constrained to visit only indices in this set.
|
|
|
|
Empty graph (no nodes, or all OOV nodes) → unconstrained region.
|
|
This preserves the existing fallback contract: unknown-domain
|
|
inputs that produce empty graphs get the full vocab walk, not
|
|
a zero-index set that would trigger immediate exhaustion.
|
|
|
|
Parameters
|
|
----------
|
|
graph : PropositionGraph
|
|
The graph whose named nodes define the constraint geometry.
|
|
vocab : Vocabulary
|
|
The vocabulary over which index neighbourhoods are computed.
|
|
top_k : int
|
|
Number of nearest vocab indices to admit per node versor.
|
|
Default 8 — keeps the constraint meaningful (< full vocab)
|
|
while allowing sufficient combinatorial freedom for fluent
|
|
token sequences.
|
|
"""
|
|
node_versors = _node_versors(graph, vocab)
|
|
if not node_versors:
|
|
# Empty or fully OOV graph → unconstrained (safe passthrough).
|
|
return AdmissibilityRegion(
|
|
allowed_indices=None,
|
|
label="graph:unconstrained",
|
|
source=AdmissibilitySource.INTENT,
|
|
)
|
|
|
|
allowed = _neighbourhood_indices(node_versors, vocab, top_k)
|
|
if not allowed:
|
|
return AdmissibilityRegion(
|
|
allowed_indices=None,
|
|
label="graph:unconstrained",
|
|
source=AdmissibilitySource.INTENT,
|
|
)
|
|
|
|
return AdmissibilityRegion(
|
|
allowed_indices=np.asarray(sorted(allowed), dtype=np.int64),
|
|
label=_constraint_label(graph),
|
|
source=AdmissibilitySource.INTENT,
|
|
)
|