tabular-pinned@v1 + calculus-limit@v1 + calculus-series@v1 + linear-algebra@v1 + function-sampled@v1 — all reserved stubs graduated; the π* registry is now 15 concrete kernels with no remaining reserved-stub entries. #000030 Phase 4 — calculus-limit@v1 ==================================== sp.limit with thread-timeout. One-sided dir support (+/-/+-). Pinned spelling for infinity cases: b"+oo" / b"-oo" / b"zoo" (complex infinity) — bypasses sp.expand since Infinity isn't algebraic. Finite results re-canonicalize through algebra-symbolic recipe (sp.expand + sp.srepr). Unevaluated cases / timeouts emit b"unevaluated:" + sp.srepr(<Limit>) sentinel, mirroring calculus-integral's pattern. #000030 Phase 5 — calculus-series@v1 ===================================== sp.series(f, x, x0, n).removeO() → sp.expand → sp.srepr. Drops O(x**n) remainder explicitly so the canonical form is finite-byte. Sentinel format mirrors limit/integral: b"unevaluated:Series(...)" on timeout. n must be a positive int; 0 / float / negative rejected. #000030 Phase 6 — linear-algebra@v1 ==================================== Single π* covers the whole linear-algebra surface via {op, matrix} JSON. Ops: rref / det / eigenvalues / inverse. Matrix cells go through Fraction(Decimal(str(...))) for floats so 1, 1.0, "1.0" all collapse to Rational(1, 1) — matching arithmetic@v1's discipline. Without this fold, sp.sympify keeps floats as Float (separate type) and downstream det/inverse return Float-shaped bytes. Eigenvalues are sorted by srepr for determinism. Output formats: rref / inverse: rows/cols header + cells joined by | (rows by ||) det: det:<num/den-or-srepr> eigenvalues: eigenvalues:<value-1>x<mult-1>|... #000030 Phase 7 — function-sampled@v1 ====================================== Bridge to time-series-quantized@v1. SymPy expression + linspace grid → quantized integer-vector signature in time-series's exact output format (dt=...;dv=...;n=...;t0=0:v0|v1|...). Two functions that render identically (within sample-grid tolerance) collapse to the same canonical bytes. This is what plotting CAN become in π* terms — the PNG render is a downstream view of the same canonical evidence. Math-only sampler (no numpy in the dep surface); Python's round() is banker's-rounding so the bytes are interchangeable with time-series-quantized@v1's output. Complex / non-finite samples raise PiStarError rather than silently dropping imaginary parts. tabular-pinned@v1 — last reserved stub graduates ================================================= JSON-rows input ({schema, key_columns, rows}); declared key_columns sort policy (stable sort by primary-key tuple); type-fold per column (int/rational/bool through arithmetic@v1 discipline; str verbatim; bool normalized). Header case is PINNED EXACT — Excel and PostgreSQL both care about case; defaulting to lowercase-fold would break operator expectations. Output: header (schema + key + n) + rows joined by \n + cells by |. The π* registry has no remaining reserved stubs. Every modality the substrate paper reserved is now real. Test suite: 1568 passed (was 1467; +101). New closure-criterion test (test_no_stub_pi_stars_remain) replaces the old reserved-stub parametrize — adding a future stub re-opens this list. 110/110 fixtures pass across the 5 new bench-5s-* targets. PHASE_1_CARRIERS gained calculus / linear-algebra / function-sampled / tabular.
212 lines
8.6 KiB
ReStructuredText
212 lines
8.6 KiB
ReStructuredText
π* domain library
|
||
=================
|
||
|
||
Arborist's canonical-projection registry — the substrate that
|
||
implements SQD whitepaper §3's invariant projection
|
||
``π*: Σ* → 𝓘 ∪ {⊥}``. Every modality the bench surface or audit
|
||
chain touches has (or reserves) a registered π* that maps surface
|
||
bytes to canonical bytes; the SHA-256 of the canonical bytes is
|
||
the equivalence-class identity.
|
||
|
||
Registry overview
|
||
-----------------
|
||
|
||
Lookup is by ``name@version`` key. **Fifteen** concrete π*'s ship
|
||
today; the registry has no remaining reserved stubs.
|
||
|
||
================================ ================= =====================================================
|
||
Key Domain Status
|
||
================================ ================= =====================================================
|
||
``wikitext-base@v1`` text Wikitext → plain prose
|
||
``claim-lattice@v1`` text Claim lines → JSON parsed-claim list
|
||
``code-py-ast@v1`` code Python source → canonical AST S-expression
|
||
``arithmetic@v1`` arithmetic Expression → exact rational ``num/den`` (SQD §14.1)
|
||
``logic-kernel@v1`` logic Boolean expression → canonical CNF (SQD §14.3)
|
||
``time-series-quantized@v1`` time-series JSON sample array → quantized integer vector (SQD §13.5)
|
||
``tabular-pinned@v1`` tabular JSON-rows → pinned-schema canonical bytes
|
||
``algebra-symbolic@v1`` symbolic-algebra SymPy ``expand + srepr`` (#000030 Phase 1)
|
||
``algebra-symbolic-simplified@v1`` symbolic-algebra SymPy ``simplify + srepr`` — collapses trig identities
|
||
``calculus-derivative@v1`` calculus ``sp.diff`` re-canonicalized through algebra-symbolic
|
||
``calculus-integral@v1`` calculus ``sp.integrate`` + unevaluated-Integral sentinel
|
||
``calculus-limit@v1`` calculus ``sp.limit`` + ±∞/complex-infinity sentinels
|
||
``calculus-series@v1`` calculus Truncated Taylor / Maclaurin (drops ``O(x**n)``)
|
||
``linear-algebra@v1`` linear-algebra RREF / det / eigenvalues / inverse via ``{op, matrix}``
|
||
``function-sampled@v1`` function-sampled SymPy expr → quantized integer vector (bridge to time-series)
|
||
================================ ================= =====================================================
|
||
|
||
The math π*'s (algebra-symbolic / calculus-* / linear-algebra /
|
||
function-sampled) gate on the optional ``[math]`` extra:
|
||
``pip install 'arborist[math]'`` installs SymPy. A fresh checkout
|
||
without the extra still passes the test suite — the modules
|
||
self-skip registration when ``import sympy`` fails.
|
||
|
||
Cross-modality discipline
|
||
-------------------------
|
||
|
||
Every bench fixture and audit-bound canonicalization names its
|
||
:class:`PiStar` via:
|
||
|
||
- ``carrier`` — a Phase-1 whitelist enforced by
|
||
:data:`bench.batteries.base.PHASE_1_CARRIERS`
|
||
- ``pi_star_ref`` — the registry key
|
||
|
||
Unsupported carriers fail or skip explicitly with
|
||
``reason="unsupported_carrier"`` — never silently accepted.
|
||
Hidden-channel work is defensive only (detection / flagging,
|
||
never generation or concealment).
|
||
|
||
The discipline gives every benchmark, every audit row, and every
|
||
selection score a stable answer to "what canonicalizer was used"
|
||
that survives schema migrations and is reproducible from the
|
||
canonical bytes alone.
|
||
|
||
Math π*'s — SQD §14
|
||
-------------------
|
||
|
||
Two of the most recently graduated π*'s implement the SQD
|
||
whitepaper's math substrate:
|
||
|
||
**arithmetic@v1** — closed-form rational arithmetic. Solves the
|
||
canonical SQD test exactly:
|
||
|
||
.. code-block:: python
|
||
|
||
from arborist.pi_star import get
|
||
ps = get("arithmetic@v1")
|
||
|
||
ps.canonicalize(b"0.1+0.2") # → b"3/10"
|
||
ps.canonicalize(b"0.3") # → b"3/10"
|
||
ps.canonicalize(b"1+2") # → b"3/1"
|
||
ps.canonicalize(b"6/4") # → b"3/2" (lowest terms)
|
||
|
||
No floating-point drift — ``Decimal(str(0.1))`` gives exact
|
||
``1/10``, then ``fractions.Fraction`` arithmetic stays in ℚ.
|
||
Identifiers, function calls, division by zero, and non-integer
|
||
exponents raise :class:`PiStarError`.
|
||
|
||
**logic-kernel@v1** — propositional Boolean expression →
|
||
Conjunctive Normal Form (CNF):
|
||
|
||
.. code-block:: python
|
||
|
||
ps = get("logic-kernel@v1")
|
||
|
||
ps.canonicalize(b"A AND B") # → b"A AND B"
|
||
ps.canonicalize(b"B AND A") # → b"A AND B" (commutativity)
|
||
ps.canonicalize(b"A IMPL B") # → b"(NOT A OR B)"
|
||
ps.canonicalize(b"(NOT B) IMPL (NOT A)") # → b"(NOT A OR B)" (contrapositive)
|
||
ps.canonicalize(b"NOT NOT A") # → b"A" (double negation)
|
||
ps.canonicalize(b"A OR NOT A") # → b"TRUE" (tautology)
|
||
|
||
Atom cap: 8 (CNF expansion is exponential; cap keeps
|
||
canonicalization deterministic in bounded time).
|
||
|
||
Equivalences preserved:
|
||
commutativity, associativity, IMPL/IFF/XOR rewrites, De Morgan,
|
||
double negation, distribution, idempotence, tautology collapse,
|
||
contrapositive.
|
||
|
||
CLI surfaces
|
||
------------
|
||
|
||
Two ways to reach a registered π* from the shell — direct or
|
||
auto-routed.
|
||
|
||
**Direct call** — ``arborist canon <key> "<input>"`` runs the named
|
||
π* against the given input and prints the canonical bytes. No
|
||
shards, no LLM, no audit chain. Pure projection.
|
||
|
||
.. code-block:: console
|
||
|
||
$ arborist canon arithmetic@v1 "0.1 + 0.2"
|
||
3/10
|
||
$ arborist canon logic-kernel@v1 "(NOT B) IMPL (NOT A)"
|
||
(NOT A OR B)
|
||
$ arborist canon time-series-quantized@v1 \
|
||
'{"dt":1,"dv":0.1,"samples":[[0,1.0],[1,2.0]]}'
|
||
dt=1;dv=0.1;n=2;t0=0:10|20
|
||
|
||
# registry contents:
|
||
$ arborist canon --list
|
||
arithmetic@v1 arithmetic
|
||
claim-lattice@v1 text
|
||
code-py-ast@v1 code
|
||
logic-kernel@v1 logic
|
||
tabular-pinned@v1 tabular
|
||
time-series-quantized@v1 time-series
|
||
wikitext-base@v1 text-or-wikitext
|
||
|
||
# JSON envelope w/ canonical-bytes SHA-256:
|
||
$ arborist canon --json arithmetic@v1 "0.1 + 0.2"
|
||
{"pi_star_ref":"arithmetic@v1","input":"0.1 + 0.2",
|
||
"canonical":"3/10","canonical_sha256":"088ae5cc..."}
|
||
|
||
**Auto-routed via** ``arborist query`` — pure-arithmetic and
|
||
pure-propositional questions short-circuit the RAG path and answer
|
||
directly through ``arithmetic@v1`` / ``logic-kernel@v1``. The result
|
||
gets a synthetic ``audit_mode = CANONICAL_PROJECTION`` and renders as
|
||
``CANONICAL · via <pi_star_ref>``:
|
||
|
||
.. code-block:: console
|
||
|
||
$ arborist query "0.1 + 0.2"
|
||
0.1 + 0.2
|
||
CANONICAL · via arithmetic@v1 0.0s (projected)
|
||
|
||
3/10
|
||
|
||
$ arborist query "A IMPL B"
|
||
A IMPL B
|
||
CANONICAL · via logic-kernel@v1 0.0s (projected)
|
||
|
||
(NOT A OR B)
|
||
|
||
The sniff is conservative — pure-arithmetic shape (digits + ops, no
|
||
letters) or pure-propositional shape (uppercase atoms + reserved
|
||
keywords only). Anything natural-language ("what is 0.1 + 0.2?")
|
||
falls through to the existing RAG path. Pass
|
||
``--no-canonical-preflight`` to force the RAG path even on
|
||
matching input — useful when bench-comparing the canonical answer
|
||
against the LLM's reply. Disable per-call via
|
||
``policy["canonical_projection_preflight"] = False``.
|
||
|
||
Composition algebra
|
||
-------------------
|
||
|
||
Two π*'s can be composed into a third via
|
||
:func:`arborist.pi_star.compose`:
|
||
|
||
.. code-block:: python
|
||
|
||
from arborist.pi_star import compose
|
||
|
||
chain = compose("wikitext-base@v1", "claim-lattice@v1")
|
||
# Auto-registers as "wikitext-base-then-claim-lattice@v1"
|
||
chain.canonicalize(b"- The release date was July 3, 1985. [E1]")
|
||
|
||
Each composition is itself a registered π* with its own key. See
|
||
:file:`docs/pi-star-composition.md` for the algebra (type
|
||
compatibility, determinism preservation, equivalence-class
|
||
preservation, projective vs invertible compositions).
|
||
|
||
Authoring a new π*
|
||
------------------
|
||
|
||
1. Add ``arborist/pi_star/<name>.py`` with a frozen dataclass
|
||
declaring ``name``, ``version``, ``domain``, and a
|
||
``canonicalize(self, raw: bytes) -> bytes`` method.
|
||
2. Call :func:`arborist.pi_star.register` at module import time.
|
||
3. Import the new module in ``arborist/pi_star/__init__.py`` so
|
||
the registration fires at package load.
|
||
4. Write tests covering: idempotency on the canonical form (or
|
||
document projective behavior), equivalence classes preserved,
|
||
equivalence classes kept distinct, error paths (bad input
|
||
rejected explicitly).
|
||
5. Add bench fixtures under ``bench/fixtures/5s/{syntax,semantics}-<name>-v1.jsonl``
|
||
exercising the new carrier through the existing 5S Syntax and
|
||
Semantics runners.
|
||
6. Add the carrier name to
|
||
:data:`bench.batteries.base.PHASE_1_CARRIERS`.
|
||
7. Add ``make bench-5s-<name>`` Makefile target.
|
||
|
||
See :file:`docs/spec-methodology.md` for the full discipline.
|