fan-out: 5 π* graduations close the registry chapter

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.
This commit is contained in:
russell@unturf.com 2026-05-09 13:04:43 -04:00
parent d34ecb27c1
commit abe5988bef
No known key found for this signature in database
22 changed files with 1950 additions and 35 deletions

View file

@ -298,6 +298,36 @@ bench-5s-time-series: bootstrap ## 5S time-series-quantized π* (SQD §13.5; qua
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5s --sub semantics \
--fixtures bench/fixtures/5s/semantics-time-series-v1.jsonl
bench-5s-tabular: bootstrap ## 5S tabular-pinned π* (#000030/Phase tabular; declared-schema 2D structured-data canonicalizer)
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5s --sub syntax \
--fixtures bench/fixtures/5s/syntax-tabular-v1.jsonl
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5s --sub semantics \
--fixtures bench/fixtures/5s/semantics-tabular-v1.jsonl
bench-5s-calculus-limit: bootstrap ## 5S calculus-limit π* (#000030 Phase 4)
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5s --sub syntax \
--fixtures bench/fixtures/5s/syntax-calculus-limit-v1.jsonl
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5s --sub semantics \
--fixtures bench/fixtures/5s/semantics-calculus-limit-v1.jsonl
bench-5s-calculus-series: bootstrap ## 5S calculus-series π* (#000030 Phase 5)
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5s --sub syntax \
--fixtures bench/fixtures/5s/syntax-calculus-series-v1.jsonl
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5s --sub semantics \
--fixtures bench/fixtures/5s/semantics-calculus-series-v1.jsonl
bench-5s-linear-algebra: bootstrap ## 5S linear-algebra π* (#000030 Phase 6)
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5s --sub syntax \
--fixtures bench/fixtures/5s/syntax-linear-algebra-v1.jsonl
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5s --sub semantics \
--fixtures bench/fixtures/5s/semantics-linear-algebra-v1.jsonl
bench-5s-function-sampled: bootstrap ## 5S function-sampled π* (#000030 Phase 7; SymPy → time-series bridge)
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5s --sub syntax \
--fixtures bench/fixtures/5s/syntax-function-sampled-v1.jsonl
PYTHONUNBUFFERED=1 $(PY) -m bench.batteries.runner --battery 5s --sub semantics \
--fixtures bench/fixtures/5s/semantics-function-sampled-v1.jsonl
bench-real-shard: bootstrap ## #000026 Phase 2 — real-shard workload baseline (latency, audit, primary-source use)
PYTHONUNBUFFERED=1 $(PY) -m bench.scripts.real_shard_baseline \
--shards-dir $${ARBORIST_SHARDS_DIR:-$$HOME/.arborist/shards} \

View file

@ -22,16 +22,26 @@ Concrete π*'s shipped today (alphabetical):
thread-timeout + expand). Optional; gates on ``sympy``. Returns
an ``unevaluated:Integral(...)`` sentinel when no closed form
exists or the timeout fires.
- ``calculus-limit@v1`` symbolic limit (one/two-sided +
± / complex-infinity sentinels). Optional; gates on ``sympy``.
- ``calculus-series@v1`` truncated Taylor / Maclaurin series
(drops the ``O(x**n)`` remainder for finite-byte canonical).
Optional; gates on ``sympy``.
- ``claim-lattice@v1`` claim lines JSON parsed-claim list.
- ``code-py-ast@v1`` Python source canonical AST S-expression.
- ``function-sampled@v1`` symbolic expression + sample grid
quantized integer-vector signature in time-series-quantized@v1
format. Optional; gates on ``sympy``.
- ``linear-algebra@v1`` matrix RREF / det / eigenvalues / inverse
via ``{op, matrix}`` JSON. Optional; gates on ``sympy``.
- ``logic-kernel@v1`` propositional Boolean CNF (SQD §14.3).
- ``tabular-pinned@v1`` JSON-rows pinned-schema canonical bytes
(last reserved-stub graduated; closes the registry chapter).
- ``time-series-quantized@v1`` JSON sample array quantized
integer vector (SQD §13.5).
- ``wikitext-base@v1`` wraps :func:`arborist.wikitext.to_base`.
Last reserved stub:
- ``tabular-pinned@v1`` (raises :class:`NotImplementedError`).
The registry has no remaining reserved stubs.
Composition theory: see ``docs/pi-star-composition.md``.
"""
@ -63,8 +73,12 @@ from arborist.pi_star import algebra_symbolic_simplified # noqa: F401,E402
from arborist.pi_star import arithmetic # noqa: F401,E402
from arborist.pi_star import calculus_derivative # noqa: F401,E402
from arborist.pi_star import calculus_integral # noqa: F401,E402
from arborist.pi_star import calculus_limit # noqa: F401,E402
from arborist.pi_star import calculus_series # noqa: F401,E402
from arborist.pi_star import claim_lattice # noqa: F401,E402
from arborist.pi_star import code # noqa: F401,E402
from arborist.pi_star import function_sampled # noqa: F401,E402
from arborist.pi_star import linear_algebra # noqa: F401,E402
from arborist.pi_star import logic # noqa: F401,E402
from arborist.pi_star import tabular # noqa: F401,E402
from arborist.pi_star import text # noqa: F401,E402

View file

@ -0,0 +1,218 @@
"""``calculus-limit@v1`` π* — symbolic-limit canonicalizer.
Domain: ``calculus``. Computes ``lim_{x point} f(x)``, optionally
one-sided. Wraps ``sp.limit`` with a soft timeout so pathological
inputs cannot hang the caller indefinitely.
Why this matters
----------------
Ticket #000030 §2.2 Phase 4. Limit is the third calculus primitive
after derivative (Phase 2) and integral (Phase 3). Same JSON-shaped
input contract; canonicalizes through ``algebra-symbolic@v1`` for
finite-result cases. Infinite-result cases pin a stable spelling
(``+oo`` / ``-oo`` / ``zoo``) so two LLM-spellings of "infinity"
collapse.
Input shape (JSON, UTF-8)
-------------------------
::
{"f": "<expression-text>",
"x": "<variable-name>",
"point": "<sympify-able-point>",
"dir"?: "+" | "-" | "+-",
"timeout_seconds"?: <float>}
- ``f`` the function (parsed via :func:`sympy.sympify`).
- ``x`` the variable name.
- ``point`` the limit point. Sympify-friendly: ``"0"``, ``"oo"``,
``"-oo"``, ``"pi"`` all work.
- ``dir`` optional one-sided limit direction. Default is the
two-sided limit (``"+-"`` in SymPy parlance, or omitted from the
call). ``"+"`` for right-side only, ``"-"`` for left-side.
- ``timeout_seconds`` optional, default :data:`DEFAULT_TIMEOUT`.
Output canonical bytes
----------------------
Three paths:
1. **Finite result** ``sp.srepr(sp.expand(limit_result))`` UTF-8.
Same recipe as ``algebra-symbolic@v1`` so numerical limits
(``lim sin(x)/x 1``) compose with the rest of the math
substrate.
2. **Infinite result** pinned spelling: ``b"+oo"`` for ``+``,
``b"-oo"`` for ``-``, ``b"zoo"`` for SymPy's ``ComplexInfinity``.
These don't go through ``sp.expand`` since ``Infinity`` is not
an algebraic object.
3. **No closed form / timeout** bytes are
``b"unevaluated:" + sp.srepr(<Limit>)``. Mirrors
``calculus-integral@v1``'s sentinel discipline.
Optional dependency
-------------------
Same as ``algebra-symbolic@v1``: SymPy via the ``[math]`` extras.
Source: ticket #000030 §2.2 Phase 4 — landed 2026-05-09.
"""
from __future__ import annotations
import concurrent.futures
import json
from dataclasses import dataclass
from arborist.pi_star.protocol import PiStarError
from arborist.pi_star.registry import register
try:
import sympy as sp # type: ignore[import-not-found]
except ImportError: # pragma: no cover — optional extra
sp = None # type: ignore[assignment]
DEFAULT_TIMEOUT = 30.0
_SYMPY_REQUIRED_MSG = (
"calculus-limit@v1 requires sympy; install with "
"`pip install 'arborist[math]'`"
)
_VALID_DIRS = ("+", "-", "+-")
def _serialize_infinity(result) -> bytes | None:
"""Map SymPy infinities to pinned UTF-8 byte spellings, or None
if the result isn't an infinity case."""
if result is sp.oo or result == sp.oo:
return b"+oo"
if result is -sp.oo or result == -sp.oo:
return b"-oo"
if result is sp.zoo or result == sp.zoo:
return b"zoo"
return None
def _is_unevaluated(result) -> bool:
"""True when ``sp.limit`` returned an unevaluated form."""
return isinstance(result, sp.Limit)
def _sentinel(unevaluated) -> bytes:
return b"unevaluated:" + sp.srepr(unevaluated).encode("utf-8")
def _build_unevaluated(f_expr, x_sym, point_expr, dir_):
if dir_ in (None, "+-"):
return sp.Limit(f_expr, x_sym, point_expr)
return sp.Limit(f_expr, x_sym, point_expr, dir_)
@dataclass
class CalculusLimitV1:
name: str = "calculus-limit"
version: str = "v1"
domain: str = "calculus"
def canonicalize(self, raw: bytes) -> bytes:
if sp is None:
raise PiStarError(_SYMPY_REQUIRED_MSG)
if not isinstance(raw, (bytes, bytearray)):
raise PiStarError(
"calculus-limit@v1 expects bytes; got "
f"{type(raw).__name__}"
)
try:
text = raw.decode("utf-8", errors="surrogatepass")
except UnicodeDecodeError as exc: # pragma: no cover
raise PiStarError(f"input not valid UTF-8: {exc}") from exc
try:
obj = json.loads(text)
except json.JSONDecodeError as exc:
raise PiStarError(
f"calculus-limit@v1 input is not valid JSON: {exc}"
) from exc
if not isinstance(obj, dict):
raise PiStarError(
"calculus-limit@v1 input must be a JSON object"
)
for required in ("f", "x", "point"):
if required not in obj:
raise PiStarError(
f"calculus-limit@v1 missing required field {required!r}"
)
f_text = obj["f"]
x_name = obj["x"]
point_text = obj["point"]
dir_ = obj.get("dir")
if dir_ is not None and dir_ not in _VALID_DIRS:
raise PiStarError(
f"calculus-limit@v1 dir must be one of {_VALID_DIRS}; "
f"got {dir_!r}"
)
timeout_s = float(obj.get("timeout_seconds", DEFAULT_TIMEOUT))
if not isinstance(f_text, str) or not f_text.strip():
raise PiStarError("calculus-limit@v1 f must be a non-empty string")
if not isinstance(x_name, str) or not x_name.strip():
raise PiStarError("calculus-limit@v1 x must be a non-empty string")
try:
x_sym = sp.Symbol(x_name)
f_expr = sp.sympify(f_text, locals={x_name: x_sym})
# ``point`` may be "oo", "-oo", "pi", "0", etc.
point_expr = sp.sympify(point_text)
except (sp.SympifyError, SyntaxError, TypeError) as exc:
raise PiStarError(
f"calculus-limit@v1 cannot parse: {exc}"
) from exc
# SymPy's limit() takes the dir as a string; default
# two-sided. Use a thread + timeout for the same reason
# calculus-integral does.
def _compute():
if dir_ in (None, "+-"):
return sp.limit(f_expr, x_sym, point_expr)
return sp.limit(f_expr, x_sym, point_expr, dir_)
try:
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as ex:
future = ex.submit(_compute)
try:
result = future.result(timeout=timeout_s)
except concurrent.futures.TimeoutError:
return _sentinel(
_build_unevaluated(f_expr, x_sym, point_expr, dir_)
)
except (sp.SympifyError, ValueError, TypeError) as exc:
raise PiStarError(
f"calculus-limit@v1 evaluation failed: {exc}"
) from exc
# Infinity / complex-infinity: pinned spelling.
inf_bytes = _serialize_infinity(result)
if inf_bytes is not None:
return inf_bytes
# SymPy may also return an unevaluated Limit object on weird
# inputs even without timeout firing — fall back to sentinel.
if _is_unevaluated(result):
return _sentinel(result)
# Finite result — canonicalize through algebra-symbolic@v1's
# recipe (sp.expand + sp.srepr).
try:
expanded = sp.expand(result)
except Exception as exc: # pragma: no cover — sympy edge cases
raise PiStarError(
f"calculus-limit@v1 cannot expand result: {exc}"
) from exc
return sp.srepr(expanded).encode("utf-8")
if sp is not None:
register(CalculusLimitV1())

View file

@ -0,0 +1,171 @@
"""``calculus-series@v1`` π* — Taylor / Maclaurin series canonicalizer.
Domain: ``calculus``. Computes the truncated Taylor series of
``f`` around ``x = x0`` to ``n`` terms via ``sp.series``. The
``O(x**n)`` remainder is dropped that's what makes the series
finite-byte canonical.
Why this matters
----------------
Ticket #000030 §2.2 Phase 5. Series complete the calculus-primitive
quartet (derivative · integral · limit · series) on the SymPy
substrate. All four ride ``algebra-symbolic@v1``'s
``sp.expand + sp.srepr`` normalizer for the closed-form output, so
the four kernels compose cleanly.
Input shape (JSON, UTF-8)
-------------------------
::
{"f": "<expression-text>",
"x": "<variable-name>",
"x0": "<sympify-able-point>",
"n": <positive-int>,
"timeout_seconds"?: <float>}
- ``f`` the function to expand.
- ``x`` the variable.
- ``x0`` the expansion point. ``"0"`` gives a Maclaurin series.
- ``n`` number of terms (the ``n`` in SymPy's ``series(..., n=n)``).
Must be a positive integer.
- ``timeout_seconds`` optional, default :data:`DEFAULT_TIMEOUT`.
Output canonical bytes
----------------------
Two paths:
1. **Series with closed-form coefficients** the truncated
polynomial after ``.removeO()``, fed through
``sp.expand + sp.srepr``. Two equivalent series collapse.
2. **Timeout** bytes are
``b"unevaluated:" + sp.srepr(<original f, x0, n>)``. Mirrors
``calculus-integral@v1`` / ``calculus-limit@v1`` sentinel
discipline.
Optional dependency
-------------------
Same as ``algebra-symbolic@v1``: SymPy via the ``[math]`` extras.
Source: ticket #000030 §2.2 Phase 5 — landed 2026-05-09.
"""
from __future__ import annotations
import concurrent.futures
import json
from dataclasses import dataclass
from arborist.pi_star.protocol import PiStarError
from arborist.pi_star.registry import register
try:
import sympy as sp # type: ignore[import-not-found]
except ImportError: # pragma: no cover — optional extra
sp = None # type: ignore[assignment]
DEFAULT_TIMEOUT = 30.0
_SYMPY_REQUIRED_MSG = (
"calculus-series@v1 requires sympy; install with "
"`pip install 'arborist[math]'`"
)
def _sentinel(f_expr, x_sym, x0_expr, n: int) -> bytes:
# Build a stable sentinel from inputs (we don't have a SymPy
# "unevaluated series" class). Hash-able by srepr of the tuple.
payload = f"Series({sp.srepr(f_expr)}, {sp.srepr(x_sym)}, {sp.srepr(x0_expr)}, {n})"
return b"unevaluated:" + payload.encode("utf-8")
@dataclass
class CalculusSeriesV1:
name: str = "calculus-series"
version: str = "v1"
domain: str = "calculus"
def canonicalize(self, raw: bytes) -> bytes:
if sp is None:
raise PiStarError(_SYMPY_REQUIRED_MSG)
if not isinstance(raw, (bytes, bytearray)):
raise PiStarError(
"calculus-series@v1 expects bytes; got "
f"{type(raw).__name__}"
)
try:
text = raw.decode("utf-8", errors="surrogatepass")
except UnicodeDecodeError as exc: # pragma: no cover
raise PiStarError(f"input not valid UTF-8: {exc}") from exc
try:
obj = json.loads(text)
except json.JSONDecodeError as exc:
raise PiStarError(
f"calculus-series@v1 input is not valid JSON: {exc}"
) from exc
if not isinstance(obj, dict):
raise PiStarError(
"calculus-series@v1 input must be a JSON object"
)
for required in ("f", "x", "x0", "n"):
if required not in obj:
raise PiStarError(
f"calculus-series@v1 missing required field "
f"{required!r}"
)
f_text = obj["f"]
x_name = obj["x"]
x0_text = obj["x0"]
n_terms = obj["n"]
timeout_s = float(obj.get("timeout_seconds", DEFAULT_TIMEOUT))
if not isinstance(f_text, str) or not f_text.strip():
raise PiStarError("calculus-series@v1 f must be a non-empty string")
if not isinstance(x_name, str) or not x_name.strip():
raise PiStarError("calculus-series@v1 x must be a non-empty string")
# JSON booleans are subclasses of int in Python; reject explicitly.
if isinstance(n_terms, bool) or not isinstance(n_terms, int) or n_terms < 1:
raise PiStarError(
f"calculus-series@v1 n must be a positive integer; got "
f"{n_terms!r}"
)
try:
x_sym = sp.Symbol(x_name)
f_expr = sp.sympify(f_text, locals={x_name: x_sym})
x0_expr = sp.sympify(x0_text)
except (sp.SympifyError, SyntaxError, TypeError) as exc:
raise PiStarError(
f"calculus-series@v1 cannot parse: {exc}"
) from exc
def _compute():
return sp.series(f_expr, x_sym, x0_expr, n_terms).removeO()
try:
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as ex:
future = ex.submit(_compute)
try:
truncated = future.result(timeout=timeout_s)
except concurrent.futures.TimeoutError:
return _sentinel(f_expr, x_sym, x0_expr, n_terms)
except (sp.SympifyError, ValueError, TypeError) as exc:
raise PiStarError(
f"calculus-series@v1 evaluation failed: {exc}"
) from exc
try:
expanded = sp.expand(truncated)
except Exception as exc: # pragma: no cover
raise PiStarError(
f"calculus-series@v1 cannot expand result: {exc}"
) from exc
return sp.srepr(expanded).encode("utf-8")
if sp is not None:
register(CalculusSeriesV1())

View file

@ -0,0 +1,236 @@
"""``function-sampled@v1`` π* — symbolic function sampler.
Domain: ``function-sampled``. Bridges symbolic SymPy expressions
to the existing ``time-series-quantized@v1`` integer-vector
canonical form. Two functions that render identically (within
sample-grid tolerance) collapse to the same canonical bytes.
Why this matters
----------------
Ticket #000030 §2.2 Phase 7. This is what plotting CAN become in
π* terms: a function is identified by its quantized samples on a
declared grid. Different libraries / DPIs / palettes don't matter
the canonical bytes pin the value-at-each-x. The PNG render is
a downstream view of the same canonical evidence.
Input shape (JSON, UTF-8)
-------------------------
::
{
"f": "<expression-text>",
"x": "<variable-name>",
"x_min": <number>,
"x_max": <number>,
"n_samples": <positive int>,
"dv": <positive number>
}
- ``f`` expression to sample (SymPy parsable).
- ``x`` independent-variable name.
- ``x_min``, ``x_max`` sample-grid endpoints (inclusive).
- ``n_samples`` number of points on the grid (linspace).
- ``dv`` value-quantization step. Same role as
``time-series-quantized@v1`` ``dv``: each y-sample is rounded to
``round(y / dv)`` (banker's rounding).
Output canonical bytes
----------------------
Reuses ``time-series-quantized@v1`` exactly::
dt=<dt>;dv=<dv>;n=<n>;t0=<t0>:v0|v1|v2|...
Where ``dt = (x_max - x_min) / (n_samples - 1)``, ``t0 = 0`` (the
linspace index), and each ``v_i = round(f(x_i) / dv)``. This
makes the sampled signature byte-compatible with
``time-series-quantized@v1`` storage and inspection.
Equivalence classes preserved
-----------------------------
- Two expressions that evaluate to the same numeric values on the
same grid same bytes. ``sin(x)`` and ``2*sin(x)/2`` collapse
trivially; the harder cases (``sin(x)**2 + cos(x)**2`` ``1``)
don't go through SymPy `simplify` here, so they only collapse
if the sampler observes the same numeric values within the dv
tolerance which they will.
- Different but equivalent JSON whitespace / formatting: same.
Equivalence classes kept distinct
---------------------------------
- Different grid (x_min, x_max, n_samples) different canonical.
The sample-grid IS part of identity.
- Different ``dv`` (quantization tightness) different canonical.
- Different expression that diverges at any sample different.
Round-trip property
-------------------
Projective. The canonical text isn't valid input JSON; rerunning
on the canonical bytes raises :class:`PiStarError`.
Optional dependency
-------------------
SymPy via the ``[math]`` extras (lambdify needs it). NumPy is
required transitively but ships standalone via SymPy's lambdify
backend; tests use ``pytest.importorskip("sympy")``.
Source: ticket #000030 §2.2 Phase 7 — landed 2026-05-09.
"""
from __future__ import annotations
import json
import math
from dataclasses import dataclass
from arborist.pi_star.protocol import PiStarError
from arborist.pi_star.registry import register
try:
import sympy as sp # type: ignore[import-not-found]
except ImportError: # pragma: no cover — optional extra
sp = None # type: ignore[assignment]
_SYMPY_REQUIRED_MSG = (
"function-sampled@v1 requires sympy; install with "
"`pip install 'arborist[math]'`"
)
def _format_num(x) -> str:
"""Mirror time_series._num: int → 'N'; integer-valued float → 'N';
otherwise repr (shortest round-trip)."""
if isinstance(x, int) or (isinstance(x, float) and x.is_integer()):
return str(int(x))
return repr(x)
@dataclass
class FunctionSampledV1:
name: str = "function-sampled"
version: str = "v1"
domain: str = "function-sampled"
def canonicalize(self, raw: bytes) -> bytes:
if sp is None:
raise PiStarError(_SYMPY_REQUIRED_MSG)
if not isinstance(raw, (bytes, bytearray)):
raise PiStarError(
"function-sampled@v1 expects bytes; got "
f"{type(raw).__name__}"
)
try:
text = raw.decode("utf-8", errors="surrogatepass")
except UnicodeDecodeError as exc: # pragma: no cover
raise PiStarError(f"input not valid UTF-8: {exc}") from exc
try:
obj = json.loads(text)
except json.JSONDecodeError as exc:
raise PiStarError(
f"function-sampled@v1 input is not valid JSON: {exc}"
) from exc
if not isinstance(obj, dict):
raise PiStarError(
"function-sampled@v1 input must be a JSON object"
)
for required in ("f", "x", "x_min", "x_max", "n_samples", "dv"):
if required not in obj:
raise PiStarError(
f"function-sampled@v1 missing required field "
f"{required!r}"
)
f_text = obj["f"]
x_name = obj["x"]
x_min = obj["x_min"]
x_max = obj["x_max"]
n_samples = obj["n_samples"]
dv = obj["dv"]
if not isinstance(f_text, str) or not f_text.strip():
raise PiStarError("function-sampled@v1 f must be non-empty string")
if not isinstance(x_name, str) or not x_name.strip():
raise PiStarError("function-sampled@v1 x must be non-empty string")
if not isinstance(x_min, (int, float)) or isinstance(x_min, bool):
raise PiStarError("function-sampled@v1 x_min must be number")
if not isinstance(x_max, (int, float)) or isinstance(x_max, bool):
raise PiStarError("function-sampled@v1 x_max must be number")
if x_max <= x_min:
raise PiStarError(
f"function-sampled@v1 x_max must be > x_min; got "
f"x_min={x_min!r}, x_max={x_max!r}"
)
if isinstance(n_samples, bool) or not isinstance(n_samples, int) or n_samples < 2:
raise PiStarError(
"function-sampled@v1 n_samples must be int >= 2"
)
if isinstance(dv, bool) or not isinstance(dv, (int, float)) or dv <= 0:
raise PiStarError(
f"function-sampled@v1 dv must be positive number; got {dv!r}"
)
try:
x_sym = sp.Symbol(x_name)
f_expr = sp.sympify(f_text, locals={x_name: x_sym})
except (sp.SympifyError, SyntaxError, TypeError) as exc:
raise PiStarError(
f"function-sampled@v1 cannot parse f: {exc}"
) from exc
# Sample without numpy — keep dependency surface small. The
# output is byte-identical to time-series-quantized@v1's
# rounding (Python's round() is banker's-rounding, matching
# numpy's default).
try:
f_callable = sp.lambdify(x_sym, f_expr, "math")
except (TypeError, ValueError) as exc:
raise PiStarError(
f"function-sampled@v1 cannot lambdify f: {exc}"
) from exc
dt_value = (x_max - x_min) / (n_samples - 1)
values: list[int] = []
for i in range(n_samples):
xi = x_min + i * dt_value
try:
yi = f_callable(xi)
except (ValueError, ZeroDivisionError, OverflowError) as exc:
raise PiStarError(
f"function-sampled@v1 evaluation failed at x={xi!r}: "
f"{exc}"
) from exc
if isinstance(yi, complex):
if abs(yi.imag) > 1e-12:
raise PiStarError(
f"function-sampled@v1 got complex value at "
f"x={xi!r}: {yi!r}"
)
yi = yi.real
if not isinstance(yi, (int, float)):
raise PiStarError(
f"function-sampled@v1 non-numeric sample at x={xi!r}: "
f"{yi!r} (type {type(yi).__name__})"
)
if math.isnan(yi) or math.isinf(yi):
raise PiStarError(
f"function-sampled@v1 non-finite sample at x={xi!r}: "
f"{yi!r}"
)
values.append(int(round(yi / dv)))
# Serialize in time-series-quantized@v1 format so the bytes
# compose identically (function-sampled output IS a quantized
# series, just one derived from a closed-form expression
# rather than an array of samples).
dt_s = _format_num(dt_value)
dv_s = _format_num(dv)
n = len(values)
body = "|".join(str(v) for v in values)
out = f"dt={dt_s};dv={dv_s};n={n};t0=0:{body}"
return out.encode("utf-8")
if sp is not None:
register(FunctionSampledV1())

View file

@ -0,0 +1,284 @@
"""``linear-algebra@v1`` π* — matrix-operation canonicalizer.
Domain: ``linear-algebra``. One π* covers the whole linear-algebra
surface via an ``op`` field in the input JSON. Matrix entries are
folded through SymPy's exact-rational arithmetic so
``[[1, 2], [3, 4]]`` and ``[[1.0, 2.0], [3.0, 4.0]]`` collapse.
Why this matters
----------------
Ticket #000030 §2.2 Phase 6. Linear algebra rounds out the math
substrate alongside the calculus quartet. Same ``[math]`` extras
gate; same `srepr`-based canonical-bytes discipline as the rest
of the SymPy substrate.
Input shape (JSON, UTF-8)
-------------------------
::
{
"op": "rref" | "det" | "eigenvalues" | "inverse",
"matrix": [[<cell>, <cell>, ...], ...]
}
- ``op`` selects the operation. Each operation has its own canonical
output shape; see below.
- ``matrix`` is a row-major 2D list. Cells go through
:func:`sympy.sympify` so ``"1/2"``, ``1`` and ``1.0`` all work
and fold to the same rational (see ``arithmetic@v1`` discipline).
Canonical output by op
----------------------
``op = "rref"`` Reduced Row Echelon Form. Output:
rref;rows=<r>;cols=<c>:cell00|cell01|...||cell10|...
Cells are printed as ``num/den`` (rational lowest-terms) for
integer + rational entries; symbolic entries fall through
``sp.srepr``.
``op = "det"`` determinant. Output:
det:<num/den-or-srepr>
``op = "eigenvalues"`` eigenvalues with multiplicity. Output:
eigenvalues:<value-1>x<mult-1>|<value-2>x<mult-2>|...
Sorted by value's `srepr` for determinism (eigenvalues come back
as a dict from SymPy and would otherwise depend on insertion
order).
``op = "inverse"`` matrix inverse, same shape as ``rref``:
inverse;rows=<r>;cols=<c>:cell00|...
Equivalence classes preserved
-----------------------------
- Cell-formatting variations (``1`` ``1.0`` ``"1.0"``) collapse.
- Eigenvalue ordering is canonicalized (sorted lexically by
``srepr``); two equivalent eigen-decompositions canonicalize
identically.
Equivalence classes kept distinct
---------------------------------
- Different ``op`` for same matrix different canonical (the op
is part of identity).
- Different matrix entries different canonical.
- Different matrix shape (rows/cols) different canonical.
Errors
------
- Singular matrix on ``inverse`` :class:`PiStarError`.
- Non-square matrix on ``det`` / ``inverse`` / ``eigenvalues``
:class:`PiStarError`.
- Unknown ``op`` :class:`PiStarError`.
Optional dependency
-------------------
Same as ``algebra-symbolic@v1``: SymPy via the ``[math]`` extras.
Source: ticket #000030 §2.2 Phase 6 — landed 2026-05-09.
"""
from __future__ import annotations
import json
from dataclasses import dataclass
from arborist.pi_star.protocol import PiStarError
from arborist.pi_star.registry import register
try:
import sympy as sp # type: ignore[import-not-found]
except ImportError: # pragma: no cover — optional extra
sp = None # type: ignore[assignment]
_VALID_OPS = ("rref", "det", "eigenvalues", "inverse")
_SYMPY_REQUIRED_MSG = (
"linear-algebra@v1 requires sympy; install with "
"`pip install 'arborist[math]'`"
)
def _format_cell(cell) -> str:
"""Render a SymPy entry as canonical text. Rationals get
``num/den``; symbolic values fall through ``srepr``."""
if isinstance(cell, sp.Rational):
return f"{cell.p}/{cell.q}"
return sp.srepr(cell)
def _format_matrix(M) -> str:
rows = []
for i in range(M.rows):
cells = [_format_cell(M[i, j]) for j in range(M.cols)]
rows.append("|".join(cells))
return "||".join(rows)
@dataclass
class LinearAlgebraV1:
name: str = "linear-algebra"
version: str = "v1"
domain: str = "linear-algebra"
def canonicalize(self, raw: bytes) -> bytes:
if sp is None:
raise PiStarError(_SYMPY_REQUIRED_MSG)
if not isinstance(raw, (bytes, bytearray)):
raise PiStarError(
"linear-algebra@v1 expects bytes; got "
f"{type(raw).__name__}"
)
try:
text = raw.decode("utf-8", errors="surrogatepass")
except UnicodeDecodeError as exc: # pragma: no cover
raise PiStarError(f"input not valid UTF-8: {exc}") from exc
try:
obj = json.loads(text)
except json.JSONDecodeError as exc:
raise PiStarError(
f"linear-algebra@v1 input is not valid JSON: {exc}"
) from exc
if not isinstance(obj, dict):
raise PiStarError(
"linear-algebra@v1 input must be a JSON object"
)
for required in ("op", "matrix"):
if required not in obj:
raise PiStarError(
f"linear-algebra@v1 missing required field "
f"{required!r}"
)
op = obj["op"]
matrix_raw = obj["matrix"]
if op not in _VALID_OPS:
raise PiStarError(
f"linear-algebra@v1 op must be one of {_VALID_OPS}; "
f"got {op!r}"
)
if not isinstance(matrix_raw, list) or not matrix_raw:
raise PiStarError(
"linear-algebra@v1 matrix must be a non-empty 2D array"
)
# Sympify each cell. Floats are folded to exact Rational via
# Decimal(str(...)) so 1.0 ≡ 1 ≡ "1.0" all collapse to the
# same Rational(1, 1) — matching arithmetic@v1's discipline.
# Without this, sympy keeps floats as Float (separate type),
# and downstream det()/inverse() return Float-shaped bytes
# that don't match the int-input bytes.
from decimal import Decimal
from fractions import Fraction
sym_rows: list[list] = []
n_cols = None
for ri, row in enumerate(matrix_raw):
if not isinstance(row, list):
raise PiStarError(
f"matrix row[{ri}] must be a JSON array"
)
if n_cols is None:
n_cols = len(row)
elif len(row) != n_cols:
raise PiStarError(
f"matrix row[{ri}] has {len(row)} cells; "
f"row[0] has {n_cols} (jagged matrix)"
)
sym_row = []
for ci, c in enumerate(row):
try:
if isinstance(c, bool):
raise PiStarError(
f"matrix[{ri}][{ci}] is bool; not a number"
)
if isinstance(c, float):
# Float → exact Fraction → SymPy Rational. Avoids
# sympy.Rational(Decimal) signature drift across
# versions and matches arithmetic@v1's discipline.
f = Fraction(Decimal(str(c)))
sym_row.append(sp.Rational(f.numerator, f.denominator))
elif isinstance(c, int):
sym_row.append(sp.Integer(c))
else:
sym_row.append(sp.sympify(c, rational=True))
except (sp.SympifyError, SyntaxError, TypeError, ValueError) as exc:
raise PiStarError(
f"matrix[{ri}][{ci}] cannot sympify: {exc}"
) from exc
sym_rows.append(sym_row)
try:
M = sp.Matrix(sym_rows)
except (TypeError, ValueError) as exc:
raise PiStarError(
f"linear-algebra@v1 cannot build Matrix: {exc}"
) from exc
# Dispatch by op.
if op == "rref":
R, _pivots = M.rref()
body = (
f"rref;rows={R.rows};cols={R.cols}:"
f"{_format_matrix(R)}"
)
return body.encode("utf-8")
if op == "det":
if M.rows != M.cols:
raise PiStarError(
f"linear-algebra@v1 det requires square matrix; "
f"got {M.rows}x{M.cols}"
)
d = M.det()
return f"det:{_format_cell(d)}".encode("utf-8")
if op == "inverse":
if M.rows != M.cols:
raise PiStarError(
f"linear-algebra@v1 inverse requires square matrix; "
f"got {M.rows}x{M.cols}"
)
try:
Inv = M.inv()
except Exception as exc:
# SymPy raises NonInvertibleMatrixError on singular
# matrices; the import path drifts across versions
# (sympy.matrices.common in 1.13, sympy.matrices.exceptions
# in 1.14). Catch broadly + re-raise as PiStarError so
# we don't pin the import path.
raise PiStarError(
f"linear-algebra@v1 inverse failed: {exc}"
) from exc
body = (
f"inverse;rows={Inv.rows};cols={Inv.cols}:"
f"{_format_matrix(Inv)}"
)
return body.encode("utf-8")
if op == "eigenvalues":
if M.rows != M.cols:
raise PiStarError(
f"linear-algebra@v1 eigenvalues requires square matrix; "
f"got {M.rows}x{M.cols}"
)
evs = M.eigenvals() # dict {value: multiplicity}
# Sort by srepr of the value for determinism.
ordered = sorted(evs.items(), key=lambda kv: sp.srepr(kv[0]))
parts = [f"{_format_cell(v)}x{m}" for v, m in ordered]
return f"eigenvalues:{'|'.join(parts)}".encode("utf-8")
raise PiStarError( # pragma: no cover — _VALID_OPS exhausts
f"linear-algebra@v1 unknown op {op!r}"
)
if sp is not None:
register(LinearAlgebraV1())

View file

@ -1,17 +1,99 @@
"""``tabular-pinned@v1`` π* (stub).
"""``tabular-pinned@v1`` π* — declared-schema tabular canonicalizer.
Domain: ``tabular``. Planned semantics: declared schema (column order
+ types) + canonical row encoding (sorted by primary key, type-pinned
cells, normalized text).
Domain: ``tabular``. The last reserved-stub π* graduates: structured
2D data (rows × columns) gets first-class equivalence-class identity.
This closes the π* registry chapter every modality the substrate
paper reserved (text, claim_lattice, code, arithmetic, logic,
time-series, tabular, plus the math-substrate extras
algebra-symbolic, calculus-derivative) is now real.
Input format
------------
UTF-8 JSON object::
{
"schema": [
{"name": "<col_name>", "type": "str|int|rational|bool"},
...
],
"key_columns": ["<col_name>", ...],
"rows": [
[<cell>, <cell>, ...],
...
]
}
- ``schema`` declares column order + per-cell type. Types fold cell
values: ``int`` and ``rational`` go through SQD §14.1 rational
arithmetic (1, 1.0, "1.0" all collapse to ``1/1``). ``str`` keeps
the value verbatim; ``bool`` normalizes to ``true``/``false``.
- ``key_columns`` names the primary-key columns. Rows are sorted by
the (key_column_1, key_column_2, ...) tuple before serialization;
ties within key are stable.
- Empty ``rows`` is valid canonicalizes to an empty body.
Canonical output
----------------
UTF-8 text of shape::
schema=col1:type1,col2:type2,...
key=col1,col2,...
n=<row_count>
rows:
cell11|cell12|...
cell21|cell22|...
...
Type codes are the schema's declared types (``str`` / ``int`` /
``rational`` / ``bool``). Cells are joined with ``|``; rows are
joined with ``\n``. Header always present even on empty tables.
Equivalence classes preserved
-----------------------------
- Row order: rows are sorted by ``key_columns``; differently-ordered
inputs collapse.
- Numeric formatting: ``1`` ``1.0`` ``"1.0"`` for ``int`` / ``rational``
columns (same rational identity through arithmetic@v1).
- JSON whitespace / formatting: same logical content, same canonical.
- Boolean spelling: ``true``/``True``/``"true"`` all normalize for
``bool`` columns.
Equivalence classes kept distinct
---------------------------------
- Schema (column order, types, key_columns) is part of identity.
Two tables with different declared schema different canonical.
- Different cell values different canonical.
- Different ``key_columns`` different canonical (changes the sort).
- Header case: pinned exact. ``Name`` ``name``. PostgreSQL +
Excel both care about case; defaulting to lowercase-fold would
break operator expectations.
Round-trip property
-------------------
Projective. The canonical text form is not valid JSON input
re-canonicalizing the canonical bytes raises PiStarError.
Versioning
----------
``tabular-pinned@v1`` pins this serialization, sort policy, and
type-folding rules. Any change requires a new version.
"""
from __future__ import annotations
import json
from dataclasses import dataclass
from fractions import Fraction
from decimal import Decimal, InvalidOperation
from arborist.pi_star.protocol import PiStarError
from arborist.pi_star.registry import register
_VALID_TYPES = ("str", "int", "rational", "bool")
@dataclass
class TabularPinnedV1:
name: str = "tabular-pinned"
@ -19,9 +101,224 @@ class TabularPinnedV1:
domain: str = "tabular"
def canonicalize(self, raw: bytes) -> bytes:
raise NotImplementedError(
"tabular-pinned@v1 is a stub; implementation ticket pending."
if not isinstance(raw, (bytes, bytearray)):
raise PiStarError(
"tabular-pinned@v1 expects bytes; got "
f"{type(raw).__name__}"
)
try:
text = raw.decode("utf-8", errors="surrogatepass")
except UnicodeDecodeError as exc: # pragma: no cover
raise PiStarError(f"input not valid UTF-8: {exc}") from exc
try:
obj = json.loads(text)
except json.JSONDecodeError as exc:
raise PiStarError(
f"tabular-pinned@v1 input is not valid JSON: {exc}"
) from exc
if not isinstance(obj, dict):
raise PiStarError(
"tabular-pinned@v1 input must be a JSON object"
)
for required in ("schema", "key_columns", "rows"):
if required not in obj:
raise PiStarError(
f"tabular-pinned@v1 missing required field "
f"{required!r}"
)
schema = obj["schema"]
key_columns = obj["key_columns"]
rows = obj["rows"]
if not isinstance(schema, list) or not schema:
raise PiStarError(
"tabular-pinned@v1 schema must be a non-empty JSON array"
)
if not isinstance(key_columns, list):
raise PiStarError(
"tabular-pinned@v1 key_columns must be a JSON array"
)
if not isinstance(rows, list):
raise PiStarError(
"tabular-pinned@v1 rows must be a JSON array"
)
# Validate schema entries.
col_names: list[str] = []
col_types: list[str] = []
for i, entry in enumerate(schema):
if not isinstance(entry, dict):
raise PiStarError(
f"schema[{i}] must be a JSON object with name + type"
)
name = entry.get("name")
ctype = entry.get("type")
if not isinstance(name, str) or not name:
raise PiStarError(
f"schema[{i}].name must be a non-empty string"
)
if ctype not in _VALID_TYPES:
raise PiStarError(
f"schema[{i}].type must be one of "
f"{_VALID_TYPES}; got {ctype!r}"
)
col_names.append(name)
col_types.append(ctype)
# Validate key_columns reference real schema columns.
col_index = {n: i for i, n in enumerate(col_names)}
if len(set(col_names)) != len(col_names):
raise PiStarError(
"tabular-pinned@v1 schema column names must be unique"
)
key_indices: list[int] = []
for k in key_columns:
if not isinstance(k, str):
raise PiStarError("key_columns entries must be strings")
if k not in col_index:
raise PiStarError(
f"key_column {k!r} not in schema columns "
f"{col_names!r}"
)
key_indices.append(col_index[k])
# Validate + fold each row.
n_cols = len(col_names)
folded: list[list[str]] = []
for ri, row in enumerate(rows):
if not isinstance(row, list):
raise PiStarError(
f"rows[{ri}] must be a JSON array"
)
if len(row) != n_cols:
raise PiStarError(
f"rows[{ri}] has {len(row)} cells; schema declares "
f"{n_cols}"
)
folded_row: list[str] = []
for ci, cell in enumerate(row):
folded_row.append(_fold_cell(cell, col_types[ci], ri, ci))
folded.append(folded_row)
# Sort by key tuple (stable). Empty key_columns → preserve
# input order (some tables have no PK; the operator declares
# that explicitly).
if key_indices:
folded.sort(
key=lambda r: tuple(r[i] for i in key_indices)
)
# Serialize.
schema_str = ",".join(
f"{n}:{t}" for n, t in zip(col_names, col_types)
)
key_str = ",".join(key_columns)
body = "\n".join("|".join(r) for r in folded)
n = len(folded)
out = (
f"schema={schema_str}\n"
f"key={key_str}\n"
f"n={n}\n"
f"rows:\n"
f"{body}"
)
# Trailing newline only when rows non-empty so the empty-table
# canonical is exactly: "schema=...\nkey=...\nn=0\nrows:".
return out.encode("utf-8")
def _fold_cell(cell, col_type: str, ri: int, ci: int) -> str:
"""Fold one cell value into its canonical string form per type."""
if col_type == "str":
if not isinstance(cell, str):
raise PiStarError(
f"rows[{ri}][{ci}] expected str cell; got "
f"{type(cell).__name__}"
)
return cell
if col_type == "bool":
if isinstance(cell, bool):
return "true" if cell else "false"
if isinstance(cell, str):
normalized = cell.strip().lower()
if normalized in ("true", "1", "yes"):
return "true"
if normalized in ("false", "0", "no"):
return "false"
raise PiStarError(
f"rows[{ri}][{ci}] expected bool-shape cell; got {cell!r}"
)
if col_type == "int":
# Accept int, float that's integer-valued, or string that parses.
if isinstance(cell, bool):
# bool is a subtype of int in Python; reject to avoid
# silently treating True as 1.
raise PiStarError(
f"rows[{ri}][{ci}] expected int cell; got bool"
)
if isinstance(cell, int):
return str(cell)
if isinstance(cell, float):
if not cell.is_integer():
raise PiStarError(
f"rows[{ri}][{ci}] expected int cell; got "
f"non-integer float {cell!r}"
)
return str(int(cell))
if isinstance(cell, str):
try:
f = Fraction(Decimal(cell.strip()))
except (ValueError, InvalidOperation) as exc:
raise PiStarError(
f"rows[{ri}][{ci}] cannot parse int from {cell!r}"
) from exc
if f.denominator != 1:
raise PiStarError(
f"rows[{ri}][{ci}] expected int cell; got "
f"non-integer rational {cell!r}"
)
return str(f.numerator)
raise PiStarError(
f"rows[{ri}][{ci}] cannot fold {cell!r} as int"
)
if col_type == "rational":
# Fold to num/den lowest-terms form, sharing arithmetic@v1's
# contract (Decimal(str(...)) for floats avoids drift).
if isinstance(cell, bool):
raise PiStarError(
f"rows[{ri}][{ci}] expected rational cell; got bool"
)
try:
if isinstance(cell, int):
f = Fraction(cell, 1)
elif isinstance(cell, float):
f = Fraction(Decimal(str(cell)))
elif isinstance(cell, str):
stripped = cell.strip()
if "/" in stripped:
f = Fraction(stripped)
else:
f = Fraction(Decimal(stripped))
else:
raise PiStarError(
f"rows[{ri}][{ci}] cannot fold {cell!r} as rational"
)
except (ValueError, InvalidOperation, ZeroDivisionError) as exc:
raise PiStarError(
f"rows[{ri}][{ci}] cannot parse rational from {cell!r}: "
f"{exc}"
) from exc
return f"{f.numerator}/{f.denominator}"
raise PiStarError( # pragma: no cover — _VALID_TYPES exhausts above
f"rows[{ri}][{ci}] unknown type {col_type!r}"
)
register(TabularPinnedV1())

View file

@ -122,6 +122,13 @@ PHASE_1_CARRIERS = frozenset({
# Calculus-derivative shares this carrier (its output is itself an
# algebraic expression).
"symbolic_algebra",
# Calculus / linear-algebra / function-sampled / tabular — ticket
# #000030 Phases 4-7 + last-stub graduation. Each closes one more
# modality the substrate paper reserved.
"calculus",
"linear-algebra",
"function-sampled",
"tabular",
})

View file

@ -0,0 +1,13 @@
{"_meta": {"battery": "5s", "sub_battery": "semantics", "version": "v1", "task_count": 12, "notes": "calculus-limit@v1 semantics — same limit collapses regardless of f spelling; oo-direction-distinct."}}
{"id": "5s-sem-lim-001", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "calculus", "domain": "json_object", "pi_star_ref": "calculus-limit@v1", "input_a": "{\"f\": \"sin(x)/x\", \"x\": \"x\", \"point\": \"0\"}", "input_b": "{\"f\": \"sin(x)*1/x\", \"x\": \"x\", \"point\": \"0\"}", "expected_equivalent": true}
{"id": "5s-sem-lim-002", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "calculus", "domain": "json_object", "pi_star_ref": "calculus-limit@v1", "input_a": "{\"f\": \"2*sin(x)/x\", \"x\": \"x\", \"point\": \"0\"}", "input_b": "{\"f\": \"2*sin(x)/x\", \"x\": \"x\", \"point\": \"0\"}", "expected_equivalent": true}
{"id": "5s-sem-lim-003", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "calculus", "domain": "json_object", "pi_star_ref": "calculus-limit@v1", "input_a": "{\"f\": \"x\", \"x\": \"x\", \"point\": \"0\"}", "input_b": "{\"f\": \"x\", \"x\": \"x\", \"point\": \"1\"}", "expected_equivalent": false}
{"id": "5s-sem-lim-004", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "calculus", "domain": "json_object", "pi_star_ref": "calculus-limit@v1", "input_a": "{\"f\": \"sin(x)/x\", \"x\": \"x\", \"point\": \"0\"}", "input_b": "{\"f\": \"cos(x)/x\", \"x\": \"x\", \"point\": \"0\"}", "expected_equivalent": false}
{"id": "5s-sem-lim-005", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "calculus", "domain": "json_object", "pi_star_ref": "calculus-limit@v1", "input_a": "{\"f\": \"1/x\", \"x\": \"x\", \"point\": \"0\", \"dir\": \"+\"}", "input_b": "{\"f\": \"1/x\", \"x\": \"x\", \"point\": \"0\", \"dir\": \"+\"}", "expected_equivalent": true}
{"id": "5s-sem-lim-006", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "calculus", "domain": "json_object", "pi_star_ref": "calculus-limit@v1", "input_a": "{\"f\": \"1/x\", \"x\": \"x\", \"point\": \"0\", \"dir\": \"+\"}", "input_b": "{\"f\": \"1/x\", \"x\": \"x\", \"point\": \"0\", \"dir\": \"-\"}", "expected_equivalent": false}
{"id": "5s-sem-lim-007", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "calculus", "domain": "json_object", "pi_star_ref": "calculus-limit@v1", "input_a": "{\"f\": \"x**2\", \"x\": \"x\", \"point\": \"3\"}", "input_b": "{\"f\": \"x*x\", \"x\": \"x\", \"point\": \"3\"}", "expected_equivalent": true}
{"id": "5s-sem-lim-008", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "calculus", "domain": "json_object", "pi_star_ref": "calculus-limit@v1", "input_a": "{\"f\": \"x\", \"x\": \"x\", \"point\": \"oo\"}", "input_b": "{\"f\": \"x\", \"x\": \"x\", \"point\": \"-oo\"}", "expected_equivalent": false}
{"id": "5s-sem-lim-009", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "calculus", "domain": "json_object", "pi_star_ref": "calculus-limit@v1", "input_a": "{\"f\": \"0\", \"x\": \"x\", \"point\": \"0\"}", "input_b": "{\"f\": \"0\", \"x\": \"x\", \"point\": \"5\"}", "expected_equivalent": true}
{"id": "5s-sem-lim-010", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "calculus", "domain": "json_object", "pi_star_ref": "calculus-limit@v1", "input_a": "{\"f\": \"y**2\", \"x\": \"y\", \"point\": \"2\"}", "input_b": "{\"f\": \"x**2\", \"x\": \"x\", \"point\": \"2\"}", "expected_equivalent": true}
{"id": "5s-sem-lim-011", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "calculus", "domain": "json_object", "pi_star_ref": "calculus-limit@v1", "input_a": "{\"f\": \"sin(x)/x\", \"x\": \"x\", \"point\": \"0\"}", "input_b": "{\"f\": \"1/x\", \"x\": \"x\", \"point\": \"oo\"}", "expected_equivalent": false}
{"id": "5s-sem-lim-012", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "calculus", "domain": "json_object", "pi_star_ref": "calculus-limit@v1", "input_a": "{\"f\": \"log(x)\", \"x\": \"x\", \"point\": \"1\"}", "input_b": "{\"f\": \"0\", \"x\": \"x\", \"point\": \"5\"}", "expected_equivalent": true}

View file

@ -0,0 +1,13 @@
{"_meta": {"battery": "5s", "sub_battery": "semantics", "version": "v1", "task_count": 12, "notes": "calculus-series@v1 semantics — truncation identity; algebraic fold; n/x0/f distinct."}}
{"id": "5s-sem-ser-001", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "calculus", "domain": "json_object", "pi_star_ref": "calculus-series@v1", "input_a": "{\"f\": \"sin(x)\", \"x\": \"x\", \"x0\": \"0\", \"n\": 4}", "input_b": "{\"f\": \"sin(x)\", \"x\": \"x\", \"x0\": \"0\", \"n\": 4}", "expected_equivalent": true}
{"id": "5s-sem-ser-002", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "calculus", "domain": "json_object", "pi_star_ref": "calculus-series@v1", "input_a": "{\"f\": \"sin(x)\", \"x\": \"x\", \"x0\": \"0\", \"n\": 4}", "input_b": "{\"f\": \"sin(x)\", \"x\": \"x\", \"x0\": \"0\", \"n\": 6}", "expected_equivalent": false}
{"id": "5s-sem-ser-003", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "calculus", "domain": "json_object", "pi_star_ref": "calculus-series@v1", "input_a": "{\"f\": \"sin(x)\", \"x\": \"x\", \"x0\": \"0\", \"n\": 4}", "input_b": "{\"f\": \"cos(x)\", \"x\": \"x\", \"x0\": \"0\", \"n\": 4}", "expected_equivalent": false}
{"id": "5s-sem-ser-004", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "calculus", "domain": "json_object", "pi_star_ref": "calculus-series@v1", "input_a": "{\"f\": \"sin(x)\", \"x\": \"x\", \"x0\": \"0\", \"n\": 4}", "input_b": "{\"f\": \"sin(x)\", \"x\": \"x\", \"x0\": \"pi\", \"n\": 4}", "expected_equivalent": false}
{"id": "5s-sem-ser-005", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "calculus", "domain": "json_object", "pi_star_ref": "calculus-series@v1", "input_a": "{\"f\": \"2*x\", \"x\": \"x\", \"x0\": \"0\", \"n\": 3}", "input_b": "{\"f\": \"x+x\", \"x\": \"x\", \"x0\": \"0\", \"n\": 3}", "expected_equivalent": true}
{"id": "5s-sem-ser-006", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "calculus", "domain": "json_object", "pi_star_ref": "calculus-series@v1", "input_a": "{\"f\": \"x**3\", \"x\": \"x\", \"x0\": \"0\", \"n\": 5}", "input_b": "{\"f\": \"x**3\", \"x\": \"x\", \"x0\": \"0\", \"n\": 5}", "expected_equivalent": true}
{"id": "5s-sem-ser-007", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "calculus", "domain": "json_object", "pi_star_ref": "calculus-series@v1", "input_a": "{\"f\": \"x**3\", \"x\": \"x\", \"x0\": \"0\", \"n\": 3}", "input_b": "{\"f\": \"x**3\", \"x\": \"x\", \"x0\": \"0\", \"n\": 5}", "expected_equivalent": false}
{"id": "5s-sem-ser-008", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "calculus", "domain": "json_object", "pi_star_ref": "calculus-series@v1", "input_a": "{\"f\": \"sin(x)\", \"x\": \"x\", \"x0\": \"0\", \"n\": 4}", "input_b": "{\"f\": \"sin(y)\", \"x\": \"y\", \"x0\": \"0\", \"n\": 4}", "expected_equivalent": false}
{"id": "5s-sem-ser-009", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "calculus", "domain": "json_object", "pi_star_ref": "calculus-series@v1", "input_a": "{\"f\": \"exp(x)\", \"x\": \"x\", \"x0\": \"0\", \"n\": 3}", "input_b": "{\"f\": \"exp(2*x)\", \"x\": \"x\", \"x0\": \"0\", \"n\": 3}", "expected_equivalent": false}
{"id": "5s-sem-ser-010", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "calculus", "domain": "json_object", "pi_star_ref": "calculus-series@v1", "input_a": "{\"f\": \"5\", \"x\": \"x\", \"x0\": \"0\", \"n\": 3}", "input_b": "{\"f\": \"5\", \"x\": \"x\", \"x0\": \"0\", \"n\": 3}", "expected_equivalent": true}
{"id": "5s-sem-ser-011", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "calculus", "domain": "json_object", "pi_star_ref": "calculus-series@v1", "input_a": "{\"f\": \"0\", \"x\": \"x\", \"x0\": \"0\", \"n\": 3}", "input_b": "{\"f\": \"0\", \"x\": \"x\", \"x0\": \"5\", \"n\": 7}", "expected_equivalent": true}
{"id": "5s-sem-ser-012", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "calculus", "domain": "json_object", "pi_star_ref": "calculus-series@v1", "input_a": "{\"f\": \"1/(1-x)\", \"x\": \"x\", \"x0\": \"0\", \"n\": 3}", "input_b": "{\"f\": \"1/(1-x)\", \"x\": \"x\", \"x0\": \"1/2\", \"n\": 3}", "expected_equivalent": false}

View file

@ -0,0 +1,13 @@
{"_meta": {"battery": "5s", "sub_battery": "semantics", "version": "v1", "task_count": 12, "notes": "function-sampled@v1 semantics — same numeric profile collapses; grid/dv/f distinct."}}
{"id": "5s-sem-fs-001", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "function-sampled", "domain": "json_object", "pi_star_ref": "function-sampled@v1", "input_a": "{\"f\": \"sin(x)\", \"x\": \"x\", \"x_min\": 0, \"x_max\": 1, \"n_samples\": 5, \"dv\": 0.01}", "input_b": "{\"f\": \"2*sin(x)/2\", \"x\": \"x\", \"x_min\": 0, \"x_max\": 1, \"n_samples\": 5, \"dv\": 0.01}", "expected_equivalent": true}
{"id": "5s-sem-fs-002", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "function-sampled", "domain": "json_object", "pi_star_ref": "function-sampled@v1", "input_a": "{\"f\": \"x\", \"x\": \"x\", \"x_min\": 0, \"x_max\": 4, \"n_samples\": 5, \"dv\": 1}", "input_b": "{\"f\": \"x*1\", \"x\": \"x\", \"x_min\": 0, \"x_max\": 4, \"n_samples\": 5, \"dv\": 1}", "expected_equivalent": true}
{"id": "5s-sem-fs-003", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "function-sampled", "domain": "json_object", "pi_star_ref": "function-sampled@v1", "input_a": "{\"f\": \"x\", \"x\": \"x\", \"x_min\": 0, \"x_max\": 4, \"n_samples\": 5, \"dv\": 1}", "input_b": "{\"f\": \"x\", \"x\": \"x\", \"x_min\": 0, \"x_max\": 5, \"n_samples\": 5, \"dv\": 1}", "expected_equivalent": false}
{"id": "5s-sem-fs-004", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "function-sampled", "domain": "json_object", "pi_star_ref": "function-sampled@v1", "input_a": "{\"f\": \"x\", \"x\": \"x\", \"x_min\": 0, \"x_max\": 4, \"n_samples\": 5, \"dv\": 1}", "input_b": "{\"f\": \"x\", \"x\": \"x\", \"x_min\": 0, \"x_max\": 4, \"n_samples\": 9, \"dv\": 1}", "expected_equivalent": false}
{"id": "5s-sem-fs-005", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "function-sampled", "domain": "json_object", "pi_star_ref": "function-sampled@v1", "input_a": "{\"f\": \"x\", \"x\": \"x\", \"x_min\": 0, \"x_max\": 4, \"n_samples\": 5, \"dv\": 1}", "input_b": "{\"f\": \"x\", \"x\": \"x\", \"x_min\": 0, \"x_max\": 4, \"n_samples\": 5, \"dv\": 0.5}", "expected_equivalent": false}
{"id": "5s-sem-fs-006", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "function-sampled", "domain": "json_object", "pi_star_ref": "function-sampled@v1", "input_a": "{\"f\": \"x**2\", \"x\": \"x\", \"x_min\": 0, \"x_max\": 4, \"n_samples\": 5, \"dv\": 1}", "input_b": "{\"f\": \"x*x\", \"x\": \"x\", \"x_min\": 0, \"x_max\": 4, \"n_samples\": 5, \"dv\": 1}", "expected_equivalent": true}
{"id": "5s-sem-fs-007", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "function-sampled", "domain": "json_object", "pi_star_ref": "function-sampled@v1", "input_a": "{\"f\": \"x\", \"x\": \"x\", \"x_min\": 0, \"x_max\": 4, \"n_samples\": 5, \"dv\": 1}", "input_b": "{\"f\": \"x+1\", \"x\": \"x\", \"x_min\": 0, \"x_max\": 4, \"n_samples\": 5, \"dv\": 1}", "expected_equivalent": false}
{"id": "5s-sem-fs-008", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "function-sampled", "domain": "json_object", "pi_star_ref": "function-sampled@v1", "input_a": "{\"f\": \"y\", \"x\": \"y\", \"x_min\": 0, \"x_max\": 4, \"n_samples\": 5, \"dv\": 1}", "input_b": "{\"f\": \"x\", \"x\": \"x\", \"x_min\": 0, \"x_max\": 4, \"n_samples\": 5, \"dv\": 1}", "expected_equivalent": true}
{"id": "5s-sem-fs-009", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "function-sampled", "domain": "json_object", "pi_star_ref": "function-sampled@v1", "input_a": "{\"f\": \"x\", \"x\": \"x\", \"x_min\": 0, \"x_max\": 4, \"n_samples\": 5, \"dv\": 1}", "input_b": "{\"f\": \"x\", \"x\": \"x\", \"x_min\": -4, \"x_max\": 0, \"n_samples\": 5, \"dv\": 1}", "expected_equivalent": false}
{"id": "5s-sem-fs-010", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "function-sampled", "domain": "json_object", "pi_star_ref": "function-sampled@v1", "input_a": "{\"f\": \"0\", \"x\": \"x\", \"x_min\": 0, \"x_max\": 4, \"n_samples\": 5, \"dv\": 1}", "input_b": "{\"f\": \"0\", \"x\": \"x\", \"x_min\": 0, \"x_max\": 4, \"n_samples\": 5, \"dv\": 1}", "expected_equivalent": true}
{"id": "5s-sem-fs-011", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "function-sampled", "domain": "json_object", "pi_star_ref": "function-sampled@v1", "input_a": "{\"f\": \"0\", \"x\": \"x\", \"x_min\": 0, \"x_max\": 4, \"n_samples\": 5, \"dv\": 1}", "input_b": "{\"f\": \"1\", \"x\": \"x\", \"x_min\": 0, \"x_max\": 4, \"n_samples\": 5, \"dv\": 1}", "expected_equivalent": false}
{"id": "5s-sem-fs-012", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "function-sampled", "domain": "json_object", "pi_star_ref": "function-sampled@v1", "input_a": "{\"f\": \"sin(x)+sin(x)\", \"x\": \"x\", \"x_min\": 0, \"x_max\": 1, \"n_samples\": 11, \"dv\": 0.01}", "input_b": "{\"f\": \"2*sin(x)\", \"x\": \"x\", \"x_min\": 0, \"x_max\": 1, \"n_samples\": 11, \"dv\": 0.01}", "expected_equivalent": true}

View file

@ -0,0 +1,13 @@
{"_meta": {"battery": "5s", "sub_battery": "semantics", "version": "v1", "task_count": 12, "notes": "linear-algebra@v1 semantics — int/float/string cell fold; eigenvalue ordering; op-distinct."}}
{"id": "5s-sem-la-001", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "linear-algebra", "domain": "json_object", "pi_star_ref": "linear-algebra@v1", "input_a": "{\"op\": \"det\", \"matrix\": [[1, 2], [3, 4]]}", "input_b": "{\"op\": \"det\", \"matrix\": [[1.0, 2.0], [3.0, 4.0]]}", "expected_equivalent": true}
{"id": "5s-sem-la-002", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "linear-algebra", "domain": "json_object", "pi_star_ref": "linear-algebra@v1", "input_a": "{\"op\": \"det\", \"matrix\": [[1, 2], [3, 4]]}", "input_b": "{\"op\": \"det\", \"matrix\": [[\"1\", \"2\"], [\"3\", \"4\"]]}", "expected_equivalent": true}
{"id": "5s-sem-la-003", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "linear-algebra", "domain": "json_object", "pi_star_ref": "linear-algebra@v1", "input_a": "{\"op\": \"det\", \"matrix\": [[2, 0], [0, 2]]}", "input_b": "{\"op\": \"rref\", \"matrix\": [[2, 0], [0, 2]]}", "expected_equivalent": false}
{"id": "5s-sem-la-004", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "linear-algebra", "domain": "json_object", "pi_star_ref": "linear-algebra@v1", "input_a": "{\"op\": \"det\", \"matrix\": [[1, 2], [3, 4]]}", "input_b": "{\"op\": \"det\", \"matrix\": [[1, 2], [3, 5]]}", "expected_equivalent": false}
{"id": "5s-sem-la-005", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "linear-algebra", "domain": "json_object", "pi_star_ref": "linear-algebra@v1", "input_a": "{\"op\": \"det\", \"matrix\": [[1, 0], [0, 1]]}", "input_b": "{\"op\": \"det\", \"matrix\": [[\"1\", \"0\"], [\"0\", \"1.0\"]]}", "expected_equivalent": true}
{"id": "5s-sem-la-006", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "linear-algebra", "domain": "json_object", "pi_star_ref": "linear-algebra@v1", "input_a": "{\"op\": \"det\", \"matrix\": [[1, 0], [0, 1]]}", "input_b": "{\"op\": \"det\", \"matrix\": [[1, 0, 0], [0, 1, 0], [0, 0, 1]]}", "expected_equivalent": true}
{"id": "5s-sem-la-007", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "linear-algebra", "domain": "json_object", "pi_star_ref": "linear-algebra@v1", "input_a": "{\"op\": \"rref\", \"matrix\": [[2, 4], [1, 2]]}", "input_b": "{\"op\": \"rref\", \"matrix\": [[1, 2], [2, 4]]}", "expected_equivalent": true}
{"id": "5s-sem-la-008", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "linear-algebra", "domain": "json_object", "pi_star_ref": "linear-algebra@v1", "input_a": "{\"op\": \"eigenvalues\", \"matrix\": [[2, 0], [0, 3]]}", "input_b": "{\"op\": \"eigenvalues\", \"matrix\": [[3, 0], [0, 2]]}", "expected_equivalent": true}
{"id": "5s-sem-la-009", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "linear-algebra", "domain": "json_object", "pi_star_ref": "linear-algebra@v1", "input_a": "{\"op\": \"eigenvalues\", \"matrix\": [[1, 0], [0, 2]]}", "input_b": "{\"op\": \"eigenvalues\", \"matrix\": [[1, 0], [0, 3]]}", "expected_equivalent": false}
{"id": "5s-sem-la-010", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "linear-algebra", "domain": "json_object", "pi_star_ref": "linear-algebra@v1", "input_a": "{\"op\": \"inverse\", \"matrix\": [[1, 0], [0, 1]]}", "input_b": "{\"op\": \"inverse\", \"matrix\": [[1, 0], [0, 1]]}", "expected_equivalent": true}
{"id": "5s-sem-la-011", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "linear-algebra", "domain": "json_object", "pi_star_ref": "linear-algebra@v1", "input_a": "{\"op\": \"inverse\", \"matrix\": [[2, 0], [0, 2]]}", "input_b": "{\"op\": \"inverse\", \"matrix\": [[1, 0], [0, 1]]}", "expected_equivalent": false}
{"id": "5s-sem-la-012", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "linear-algebra", "domain": "json_object", "pi_star_ref": "linear-algebra@v1", "input_a": "{\"op\": \"det\", \"matrix\": [[\"1/2\", 0], [0, \"1/2\"]]}", "input_b": "{\"op\": \"det\", \"matrix\": [[0.5, 0], [0, 0.5]]}", "expected_equivalent": true}

View file

@ -0,0 +1,13 @@
{"_meta": {"battery": "5s", "sub_battery": "semantics", "version": "v1", "task_count": 12, "notes": "tabular-pinned@v1 semantics — row-order/cell-fold collapses; schema/key/case distinct."}}
{"id": "5s-sem-tab-001", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "tabular", "domain": "json_object", "pi_star_ref": "tabular-pinned@v1", "input_a": "{\"schema\": [{\"name\": \"k\", \"type\": \"int\"}], \"key_columns\": [\"k\"], \"rows\": [[2], [1]]}", "input_b": "{\"schema\": [{\"name\": \"k\", \"type\": \"int\"}], \"key_columns\": [\"k\"], \"rows\": [[1], [2]]}", "expected_equivalent": true}
{"id": "5s-sem-tab-002", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "tabular", "domain": "json_object", "pi_star_ref": "tabular-pinned@v1", "input_a": "{\"schema\": [{\"name\": \"v\", \"type\": \"int\"}], \"key_columns\": [], \"rows\": [[1]]}", "input_b": "{\"schema\": [{\"name\": \"v\", \"type\": \"int\"}], \"key_columns\": [], \"rows\": [[1.0]]}", "expected_equivalent": true}
{"id": "5s-sem-tab-003", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "tabular", "domain": "json_object", "pi_star_ref": "tabular-pinned@v1", "input_a": "{\"schema\": [{\"name\": \"v\", \"type\": \"int\"}], \"key_columns\": [], \"rows\": [[1]]}", "input_b": "{\"schema\": [{\"name\": \"v\", \"type\": \"int\"}], \"key_columns\": [], \"rows\": [[\"1.0\"]]}", "expected_equivalent": true}
{"id": "5s-sem-tab-004", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "tabular", "domain": "json_object", "pi_star_ref": "tabular-pinned@v1", "input_a": "{\"schema\": [{\"name\": \"r\", \"type\": \"rational\"}], \"key_columns\": [], \"rows\": [[0.5]]}", "input_b": "{\"schema\": [{\"name\": \"r\", \"type\": \"rational\"}], \"key_columns\": [], \"rows\": [[\"1/2\"]]}", "expected_equivalent": true}
{"id": "5s-sem-tab-005", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "tabular", "domain": "json_object", "pi_star_ref": "tabular-pinned@v1", "input_a": "{\"schema\": [{\"name\": \"f\", \"type\": \"bool\"}], \"key_columns\": [], \"rows\": [[true]]}", "input_b": "{\"schema\": [{\"name\": \"f\", \"type\": \"bool\"}], \"key_columns\": [], \"rows\": [[\"true\"]]}", "expected_equivalent": true}
{"id": "5s-sem-tab-006", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "tabular", "domain": "json_object", "pi_star_ref": "tabular-pinned@v1", "input_a": "{\"schema\": [{\"name\": \"Name\", \"type\": \"str\"}], \"key_columns\": [], \"rows\": [[\"x\"]]}", "input_b": "{\"schema\": [{\"name\": \"name\", \"type\": \"str\"}], \"key_columns\": [], \"rows\": [[\"x\"]]}", "expected_equivalent": false}
{"id": "5s-sem-tab-007", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "tabular", "domain": "json_object", "pi_star_ref": "tabular-pinned@v1", "input_a": "{\"schema\": [{\"name\": \"v\", \"type\": \"int\"}], \"key_columns\": [], \"rows\": [[1]]}", "input_b": "{\"schema\": [{\"name\": \"v\", \"type\": \"int\"}], \"key_columns\": [], \"rows\": [[2]]}", "expected_equivalent": false}
{"id": "5s-sem-tab-008", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "tabular", "domain": "json_object", "pi_star_ref": "tabular-pinned@v1", "input_a": "{\"schema\": [{\"name\": \"a\", \"type\": \"int\"}], \"key_columns\": [], \"rows\": [[1]]}", "input_b": "{\"schema\": [{\"name\": \"b\", \"type\": \"int\"}], \"key_columns\": [], \"rows\": [[1]]}", "expected_equivalent": false}
{"id": "5s-sem-tab-009", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "tabular", "domain": "json_object", "pi_star_ref": "tabular-pinned@v1", "input_a": "{\"schema\": [{\"name\": \"a\", \"type\": \"int\"}, {\"name\": \"b\", \"type\": \"int\"}], \"key_columns\": [\"a\"], \"rows\": [[2, 1], [1, 2]]}", "input_b": "{\"schema\": [{\"name\": \"a\", \"type\": \"int\"}, {\"name\": \"b\", \"type\": \"int\"}], \"key_columns\": [\"b\"], \"rows\": [[2, 1], [1, 2]]}", "expected_equivalent": false}
{"id": "5s-sem-tab-010", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "tabular", "domain": "json_object", "pi_star_ref": "tabular-pinned@v1", "input_a": "{\"schema\": [{\"name\": \"x\", \"type\": \"int\"}], \"key_columns\": [], \"rows\": []}", "input_b": "{\"schema\": [{\"name\": \"x\", \"type\": \"int\"}], \"key_columns\": [], \"rows\": []}", "expected_equivalent": true}
{"id": "5s-sem-tab-011", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "tabular", "domain": "json_object", "pi_star_ref": "tabular-pinned@v1", "input_a": "{\"schema\": [{\"name\": \"x\", \"type\": \"int\"}], \"key_columns\": [\"x\"], \"rows\": [[1]]}", "input_b": "{\"schema\": [{\"name\": \"x\", \"type\": \"int\"}], \"key_columns\": [\"x\"], \"rows\": [[1], [2]]}", "expected_equivalent": false}
{"id": "5s-sem-tab-012", "battery": "5s", "sub_battery": "semantics", "version": "v1", "carrier": "tabular", "domain": "json_object", "pi_star_ref": "tabular-pinned@v1", "input_a": "{\"schema\": [{\"name\": \"r\", \"type\": \"rational\"}], \"key_columns\": [], \"rows\": [[\"1/2\"]]}", "input_b": "{\"schema\": [{\"name\": \"r\", \"type\": \"rational\"}], \"key_columns\": [], \"rows\": [[\"2/4\"]]}", "expected_equivalent": true}

View file

@ -0,0 +1,11 @@
{"_meta": {"battery": "5s", "sub_battery": "syntax", "version": "v1", "task_count": 10, "notes": "calculus-limit@v1 syntax — sin(x)/x at 0; 1/x at 0±; e at oo."}}
{"id": "5s-syn-lim-001", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "calculus", "domain": "json_object", "pi_star_ref": "calculus-limit@v1", "input": "{\"f\": \"sin(x)/x\", \"x\": \"x\", \"point\": \"0\"}"}
{"id": "5s-syn-lim-002", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "calculus", "domain": "json_object", "pi_star_ref": "calculus-limit@v1", "input": "{\"f\": \"x**2 + 1\", \"x\": \"x\", \"point\": \"3\"}"}
{"id": "5s-syn-lim-003", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "calculus", "domain": "json_object", "pi_star_ref": "calculus-limit@v1", "input": "{\"f\": \"1/x\", \"x\": \"x\", \"point\": \"oo\"}"}
{"id": "5s-syn-lim-004", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "calculus", "domain": "json_object", "pi_star_ref": "calculus-limit@v1", "input": "{\"f\": \"1/x\", \"x\": \"x\", \"point\": \"0\", \"dir\": \"+\"}"}
{"id": "5s-syn-lim-005", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "calculus", "domain": "json_object", "pi_star_ref": "calculus-limit@v1", "input": "{\"f\": \"1/x\", \"x\": \"x\", \"point\": \"0\", \"dir\": \"-\"}"}
{"id": "5s-syn-lim-006", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "calculus", "domain": "json_object", "pi_star_ref": "calculus-limit@v1", "input": "{\"f\": \"exp(-x)\", \"x\": \"x\", \"point\": \"oo\"}"}
{"id": "5s-syn-lim-007", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "calculus", "domain": "json_object", "pi_star_ref": "calculus-limit@v1", "input": "{\"f\": \"log(x)\", \"x\": \"x\", \"point\": \"1\"}"}
{"id": "5s-syn-lim-008", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "calculus", "domain": "json_object", "pi_star_ref": "calculus-limit@v1", "input": "{\"f\": \"(1+1/x)**x\", \"x\": \"x\", \"point\": \"oo\"}"}
{"id": "5s-syn-lim-009", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "calculus", "domain": "json_object", "pi_star_ref": "calculus-limit@v1", "input": "{\"f\": \"tan(x)\", \"x\": \"x\", \"point\": \"0\"}"}
{"id": "5s-syn-lim-010", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "calculus", "domain": "json_object", "pi_star_ref": "calculus-limit@v1", "input": "{\"f\": \"x*sin(1/x)\", \"x\": \"x\", \"point\": \"0\"}"}

View file

@ -0,0 +1,11 @@
{"_meta": {"battery": "5s", "sub_battery": "syntax", "version": "v1", "task_count": 10, "notes": "calculus-series@v1 syntax — Maclaurin/Taylor truncation."}}
{"id": "5s-syn-ser-001", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "calculus", "domain": "json_object", "pi_star_ref": "calculus-series@v1", "input": "{\"f\": \"sin(x)\", \"x\": \"x\", \"x0\": \"0\", \"n\": 4}"}
{"id": "5s-syn-ser-002", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "calculus", "domain": "json_object", "pi_star_ref": "calculus-series@v1", "input": "{\"f\": \"cos(x)\", \"x\": \"x\", \"x0\": \"0\", \"n\": 4}"}
{"id": "5s-syn-ser-003", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "calculus", "domain": "json_object", "pi_star_ref": "calculus-series@v1", "input": "{\"f\": \"exp(x)\", \"x\": \"x\", \"x0\": \"0\", \"n\": 3}"}
{"id": "5s-syn-ser-004", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "calculus", "domain": "json_object", "pi_star_ref": "calculus-series@v1", "input": "{\"f\": \"1/(1-x)\", \"x\": \"x\", \"x0\": \"0\", \"n\": 4}"}
{"id": "5s-syn-ser-005", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "calculus", "domain": "json_object", "pi_star_ref": "calculus-series@v1", "input": "{\"f\": \"log(1+x)\", \"x\": \"x\", \"x0\": \"0\", \"n\": 4}"}
{"id": "5s-syn-ser-006", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "calculus", "domain": "json_object", "pi_star_ref": "calculus-series@v1", "input": "{\"f\": \"sin(x)\", \"x\": \"x\", \"x0\": \"pi\", \"n\": 3}"}
{"id": "5s-syn-ser-007", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "calculus", "domain": "json_object", "pi_star_ref": "calculus-series@v1", "input": "{\"f\": \"x**3 + 2*x\", \"x\": \"x\", \"x0\": \"0\", \"n\": 5}"}
{"id": "5s-syn-ser-008", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "calculus", "domain": "json_object", "pi_star_ref": "calculus-series@v1", "input": "{\"f\": \"1/(1+x**2)\", \"x\": \"x\", \"x0\": \"0\", \"n\": 4}"}
{"id": "5s-syn-ser-009", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "calculus", "domain": "json_object", "pi_star_ref": "calculus-series@v1", "input": "{\"f\": \"tan(x)\", \"x\": \"x\", \"x0\": \"0\", \"n\": 3}"}
{"id": "5s-syn-ser-010", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "calculus", "domain": "json_object", "pi_star_ref": "calculus-series@v1", "input": "{\"f\": \"sqrt(1+x)\", \"x\": \"x\", \"x0\": \"0\", \"n\": 3}"}

View file

@ -0,0 +1,11 @@
{"_meta": {"battery": "5s", "sub_battery": "syntax", "version": "v1", "task_count": 10, "notes": "function-sampled@v1 syntax — SymPy expr → quantized sample grid."}}
{"id": "5s-syn-fs-001", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "function-sampled", "domain": "json_object", "pi_star_ref": "function-sampled@v1", "input": "{\"f\": \"x\", \"x\": \"x\", \"x_min\": 0, \"x_max\": 4, \"n_samples\": 5, \"dv\": 1}"}
{"id": "5s-syn-fs-002", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "function-sampled", "domain": "json_object", "pi_star_ref": "function-sampled@v1", "input": "{\"f\": \"x**2\", \"x\": \"x\", \"x_min\": 0, \"x_max\": 4, \"n_samples\": 5, \"dv\": 1}"}
{"id": "5s-syn-fs-003", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "function-sampled", "domain": "json_object", "pi_star_ref": "function-sampled@v1", "input": "{\"f\": \"sin(x)\", \"x\": \"x\", \"x_min\": 0, \"x_max\": 1, \"n_samples\": 11, \"dv\": 0.01}"}
{"id": "5s-syn-fs-004", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "function-sampled", "domain": "json_object", "pi_star_ref": "function-sampled@v1", "input": "{\"f\": \"2*x + 1\", \"x\": \"x\", \"x_min\": -2, \"x_max\": 2, \"n_samples\": 5, \"dv\": 1}"}
{"id": "5s-syn-fs-005", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "function-sampled", "domain": "json_object", "pi_star_ref": "function-sampled@v1", "input": "{\"f\": \"exp(x)\", \"x\": \"x\", \"x_min\": 0, \"x_max\": 1, \"n_samples\": 11, \"dv\": 0.1}"}
{"id": "5s-syn-fs-006", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "function-sampled", "domain": "json_object", "pi_star_ref": "function-sampled@v1", "input": "{\"f\": \"cos(x)\", \"x\": \"x\", \"x_min\": 0, \"x_max\": 3.14159, \"n_samples\": 4, \"dv\": 0.1}"}
{"id": "5s-syn-fs-007", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "function-sampled", "domain": "json_object", "pi_star_ref": "function-sampled@v1", "input": "{\"f\": \"x**3\", \"x\": \"x\", \"x_min\": -1, \"x_max\": 1, \"n_samples\": 5, \"dv\": 0.1}"}
{"id": "5s-syn-fs-008", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "function-sampled", "domain": "json_object", "pi_star_ref": "function-sampled@v1", "input": "{\"f\": \"abs(x)\", \"x\": \"x\", \"x_min\": -1, \"x_max\": 1, \"n_samples\": 5, \"dv\": 0.1}"}
{"id": "5s-syn-fs-009", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "function-sampled", "domain": "json_object", "pi_star_ref": "function-sampled@v1", "input": "{\"f\": \"x\", \"x\": \"x\", \"x_min\": 0, \"x_max\": 10, \"n_samples\": 11, \"dv\": 1}"}
{"id": "5s-syn-fs-010", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "function-sampled", "domain": "json_object", "pi_star_ref": "function-sampled@v1", "input": "{\"f\": \"sin(x)*cos(x)\", \"x\": \"x\", \"x_min\": 0, \"x_max\": 1, \"n_samples\": 11, \"dv\": 0.01}"}

View file

@ -0,0 +1,11 @@
{"_meta": {"battery": "5s", "sub_battery": "syntax", "version": "v1", "task_count": 10, "notes": "linear-algebra@v1 syntax — det/rref/inverse/eigenvalues."}}
{"id": "5s-syn-la-001", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "linear-algebra", "domain": "json_object", "pi_star_ref": "linear-algebra@v1", "input": "{\"op\": \"det\", \"matrix\": [[1, 2], [3, 4]]}"}
{"id": "5s-syn-la-002", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "linear-algebra", "domain": "json_object", "pi_star_ref": "linear-algebra@v1", "input": "{\"op\": \"det\", \"matrix\": [[1, 0, 0], [0, 1, 0], [0, 0, 1]]}"}
{"id": "5s-syn-la-003", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "linear-algebra", "domain": "json_object", "pi_star_ref": "linear-algebra@v1", "input": "{\"op\": \"rref\", \"matrix\": [[2, 4], [1, 2]]}"}
{"id": "5s-syn-la-004", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "linear-algebra", "domain": "json_object", "pi_star_ref": "linear-algebra@v1", "input": "{\"op\": \"rref\", \"matrix\": [[1, 2, 3], [0, 1, 1]]}"}
{"id": "5s-syn-la-005", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "linear-algebra", "domain": "json_object", "pi_star_ref": "linear-algebra@v1", "input": "{\"op\": \"inverse\", \"matrix\": [[2, 0], [0, 2]]}"}
{"id": "5s-syn-la-006", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "linear-algebra", "domain": "json_object", "pi_star_ref": "linear-algebra@v1", "input": "{\"op\": \"inverse\", \"matrix\": [[1, 1], [0, 1]]}"}
{"id": "5s-syn-la-007", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "linear-algebra", "domain": "json_object", "pi_star_ref": "linear-algebra@v1", "input": "{\"op\": \"eigenvalues\", \"matrix\": [[2, 0], [0, 3]]}"}
{"id": "5s-syn-la-008", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "linear-algebra", "domain": "json_object", "pi_star_ref": "linear-algebra@v1", "input": "{\"op\": \"eigenvalues\", \"matrix\": [[1, 1], [0, 1]]}"}
{"id": "5s-syn-la-009", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "linear-algebra", "domain": "json_object", "pi_star_ref": "linear-algebra@v1", "input": "{\"op\": \"det\", \"matrix\": [[\"1/2\", \"1/3\"], [\"1\", \"1\"]]}"}
{"id": "5s-syn-la-010", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "linear-algebra", "domain": "json_object", "pi_star_ref": "linear-algebra@v1", "input": "{\"op\": \"rref\", \"matrix\": [[1, 2], [2, 4], [3, 6]]}"}

View file

@ -0,0 +1,11 @@
{"_meta": {"battery": "5s", "sub_battery": "syntax", "version": "v1", "task_count": 10, "notes": "tabular-pinned@v1 syntax — last reserved-stub π* graduates."}}
{"id": "5s-syn-tab-001", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "tabular", "domain": "json_object", "pi_star_ref": "tabular-pinned@v1", "input": "{\"schema\": [{\"name\": \"id\", \"type\": \"int\"}], \"key_columns\": [\"id\"], \"rows\": [[1], [2]]}"}
{"id": "5s-syn-tab-002", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "tabular", "domain": "json_object", "pi_star_ref": "tabular-pinned@v1", "input": "{\"schema\": [{\"name\": \"k\", \"type\": \"int\"}, {\"name\": \"v\", \"type\": \"str\"}], \"key_columns\": [\"k\"], \"rows\": [[1, \"a\"], [2, \"b\"]]}"}
{"id": "5s-syn-tab-003", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "tabular", "domain": "json_object", "pi_star_ref": "tabular-pinned@v1", "input": "{\"schema\": [{\"name\": \"r\", \"type\": \"rational\"}], \"key_columns\": [], \"rows\": [[0.5], [0.25]]}"}
{"id": "5s-syn-tab-004", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "tabular", "domain": "json_object", "pi_star_ref": "tabular-pinned@v1", "input": "{\"schema\": [{\"name\": \"f\", \"type\": \"bool\"}], \"key_columns\": [], \"rows\": [[true], [false]]}"}
{"id": "5s-syn-tab-005", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "tabular", "domain": "json_object", "pi_star_ref": "tabular-pinned@v1", "input": "{\"schema\": [{\"name\": \"id\", \"type\": \"int\"}], \"key_columns\": [\"id\"], \"rows\": []}"}
{"id": "5s-syn-tab-006", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "tabular", "domain": "json_object", "pi_star_ref": "tabular-pinned@v1", "input": "{\"schema\": [{\"name\": \"a\", \"type\": \"int\"}, {\"name\": \"b\", \"type\": \"int\"}], \"key_columns\": [\"a\", \"b\"], \"rows\": [[1, 2], [3, 4]]}"}
{"id": "5s-syn-tab-007", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "tabular", "domain": "json_object", "pi_star_ref": "tabular-pinned@v1", "input": "{\"schema\": [{\"name\": \"name\", \"type\": \"str\"}], \"key_columns\": [\"name\"], \"rows\": [[\"alice\"], [\"bob\"]]}"}
{"id": "5s-syn-tab-008", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "tabular", "domain": "json_object", "pi_star_ref": "tabular-pinned@v1", "input": "{\"schema\": [{\"name\": \"x\", \"type\": \"rational\"}], \"key_columns\": [\"x\"], \"rows\": [[\"1/3\"], [\"2/3\"]]}"}
{"id": "5s-syn-tab-009", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "tabular", "domain": "json_object", "pi_star_ref": "tabular-pinned@v1", "input": "{\"schema\": [{\"name\": \"flag\", \"type\": \"bool\"}, {\"name\": \"id\", \"type\": \"int\"}], \"key_columns\": [\"id\"], \"rows\": [[true, 1], [false, 2]]}"}
{"id": "5s-syn-tab-010", "battery": "5s", "sub_battery": "syntax", "version": "v1", "carrier": "tabular", "domain": "json_object", "pi_star_ref": "tabular-pinned@v1", "input": "{\"schema\": [{\"name\": \"id\", \"type\": \"int\"}], \"key_columns\": [\"id\"], \"rows\": [[100], [200], [300], [400]]}"}

View file

@ -11,20 +11,34 @@ the equivalence-class identity.
Registry overview
-----------------
Lookup is by ``name@version`` key. Six concrete π*'s + one stub
ship today:
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 (Phase 1a)
``claim-lattice@v1`` text Claim lines → JSON parsed-claim list (Phase 1a)
``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
``tabular-pinned@v1`` tabular reserved (stub)
========================== =========== ===========================================================
================================ ================= =====================================================
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
-------------------------

View file

@ -124,19 +124,38 @@ def test_claim_lattice_empty_input_returns_empty_array():
# --- stubs ---------------------------------------------------------
@pytest.mark.parametrize(
"key",
[
# code-py-ast@v1, logic-kernel@v1, arithmetic@v1, and
# time-series-quantized@v1 all graduated to real
# implementations. Only tabular-pinned@v1 remains a stub.
"tabular-pinned@v1",
],
)
def test_stubs_raise_not_implemented(key):
pi_star = get(key)
with pytest.raises(NotImplementedError):
pi_star.canonicalize(b"anything")
def test_no_stub_pi_stars_remain():
"""Closure-criterion guard: every reserved-stub π* has graduated.
History:
- 2026-05-07 (#000015): six concrete + four reserved stubs
(``code-py-ast``, ``logic-kernel``, ``time-series-quantized``,
``tabular-pinned``).
- 2026-05-08: arithmetic / logic-kernel / code-py-ast /
time-series-quantized graduated.
- 2026-05-09 (#000030 + tabular phase): algebra-symbolic /
calculus-derivative / -integral / -limit / -series /
linear-algebra / function-sampled / tabular-pinned all real.
Adding a new reserved stub re-opens this list; that's the
governance event this test pins."""
from arborist.pi_star import REGISTRY
for key, pi_star in REGISTRY.items():
# A stub raises NotImplementedError on any input; a real
# canonicalizer either succeeds or raises PiStarError on
# bad input. We probe with empty bytes — most real kernels
# raise PiStarError; none should raise NotImplementedError.
try:
pi_star.canonicalize(b"")
except NotImplementedError:
raise AssertionError(
f"π* {key!r} still raises NotImplementedError — "
f"reserved stub not yet graduated"
)
except Exception:
# Any other exception (PiStarError, ValueError, etc.)
# means the kernel is real.
pass
# --- time-series-quantized@v1 (graduated from stub) ------------------

View file

@ -0,0 +1,272 @@
"""Tests for #000030 Phase 4-7 π* graduations.
Phase 4 calculus-limit@v1
Phase 5 calculus-series@v1
Phase 6 linear-algebra@v1
Phase 7 function-sampled@v1
All four gate on SymPy via the [math] extra; tests skip cleanly
when sympy is absent (mirrors the algebra-symbolic / calculus-
derivative pattern).
"""
from __future__ import annotations
import pytest
from arborist.pi_star import PiStarError, get
sympy = pytest.importorskip("sympy")
# ===== calculus-limit@v1 (Phase 4) ========================================
def test_limit_sinx_over_x_at_zero_is_one():
ps = get("calculus-limit@v1")
out = ps.canonicalize(b'{"f":"sin(x)/x","x":"x","point":"0"}')
assert out == sympy.srepr(sympy.Integer(1)).encode("utf-8")
def test_limit_polynomial_at_finite_point():
ps = get("calculus-limit@v1")
out = ps.canonicalize(b'{"f":"x**2 + 1","x":"x","point":"3"}')
# 9 + 1 = 10
assert out == sympy.srepr(sympy.Integer(10)).encode("utf-8")
def test_limit_one_over_x_at_zero_plus_is_oo():
ps = get("calculus-limit@v1")
out = ps.canonicalize(b'{"f":"1/x","x":"x","point":"0","dir":"+"}')
assert out == b"+oo"
def test_limit_one_over_x_at_zero_minus_is_minus_oo():
ps = get("calculus-limit@v1")
out = ps.canonicalize(b'{"f":"1/x","x":"x","point":"0","dir":"-"}')
assert out == b"-oo"
def test_limit_at_infinity():
ps = get("calculus-limit@v1")
out = ps.canonicalize(b'{"f":"1/x","x":"x","point":"oo"}')
assert out == sympy.srepr(sympy.Integer(0)).encode("utf-8")
def test_limit_invalid_dir_raises():
ps = get("calculus-limit@v1")
with pytest.raises(PiStarError):
ps.canonicalize(b'{"f":"x","x":"x","point":"0","dir":"invalid"}')
def test_limit_missing_f_raises():
ps = get("calculus-limit@v1")
with pytest.raises(PiStarError):
ps.canonicalize(b'{"x":"x","point":"0"}')
# ===== calculus-series@v1 (Phase 5) =======================================
def test_series_sinx_maclaurin_n4():
"""Taylor series of sin(x) at x=0 to 4 terms is x - x³/6."""
ps = get("calculus-series@v1")
out = ps.canonicalize(b'{"f":"sin(x)","x":"x","x0":"0","n":4}')
# Should canonicalize to x - x³/6 via expand+srepr.
expected = sympy.srepr(sympy.expand(
sympy.Symbol("x") - sympy.Rational(1, 6) * sympy.Symbol("x") ** 3
)).encode("utf-8")
assert out == expected
def test_series_exp_maclaurin_n3():
"""exp(x) Taylor at 0, n=3 → 1 + x + x²/2."""
ps = get("calculus-series@v1")
out = ps.canonicalize(b'{"f":"exp(x)","x":"x","x0":"0","n":3}')
x = sympy.Symbol("x")
expected = sympy.srepr(sympy.expand(
1 + x + sympy.Rational(1, 2) * x ** 2
)).encode("utf-8")
assert out == expected
def test_series_n_must_be_positive():
ps = get("calculus-series@v1")
with pytest.raises(PiStarError):
ps.canonicalize(b'{"f":"sin(x)","x":"x","x0":"0","n":0}')
def test_series_n_must_be_int():
ps = get("calculus-series@v1")
with pytest.raises(PiStarError):
ps.canonicalize(b'{"f":"sin(x)","x":"x","x0":"0","n":3.5}')
def test_series_missing_field_raises():
ps = get("calculus-series@v1")
with pytest.raises(PiStarError):
ps.canonicalize(b'{"f":"sin(x)","x":"x","n":4}')
# ===== linear-algebra@v1 (Phase 6) ========================================
def test_linalg_det_2x2():
ps = get("linear-algebra@v1")
# det [[1,2],[3,4]] = 1*4 - 2*3 = -2
out = ps.canonicalize(b'{"op":"det","matrix":[[1,2],[3,4]]}')
assert out == b"det:-2/1"
def test_linalg_det_3x3():
ps = get("linear-algebra@v1")
# Identity 3x3 has det 1.
out = ps.canonicalize(
b'{"op":"det","matrix":[[1,0,0],[0,1,0],[0,0,1]]}'
)
assert out == b"det:1/1"
def test_linalg_rref_collapses_dependent_rows():
"""RREF of [[2,4],[1,2]] is [[1,2],[0,0]] — one pivot."""
ps = get("linear-algebra@v1")
out = ps.canonicalize(b'{"op":"rref","matrix":[[2,4],[1,2]]}')
assert out == b"rref;rows=2;cols=2:1/1|2/1||0/1|0/1"
def test_linalg_inverse_2x2():
ps = get("linear-algebra@v1")
# [[2,0],[0,2]]^-1 = [[1/2,0],[0,1/2]]
out = ps.canonicalize(b'{"op":"inverse","matrix":[[2,0],[0,2]]}')
assert out == b"inverse;rows=2;cols=2:1/2|0/1||0/1|1/2"
def test_linalg_eigenvalues_diagonal():
"""Diagonal matrix has its diagonal entries as eigenvalues."""
ps = get("linear-algebra@v1")
out = ps.canonicalize(
b'{"op":"eigenvalues","matrix":[[3,0],[0,2]]}'
)
# Sorted by srepr → 2 first then 3.
assert out == b"eigenvalues:2/1x1|3/1x1"
def test_linalg_inverse_singular_raises():
"""Singular matrix has no inverse."""
ps = get("linear-algebra@v1")
with pytest.raises(PiStarError):
ps.canonicalize(b'{"op":"inverse","matrix":[[1,1],[1,1]]}')
def test_linalg_det_non_square_raises():
ps = get("linear-algebra@v1")
with pytest.raises(PiStarError):
ps.canonicalize(b'{"op":"det","matrix":[[1,2,3],[4,5,6]]}')
def test_linalg_unknown_op_raises():
ps = get("linear-algebra@v1")
with pytest.raises(PiStarError):
ps.canonicalize(b'{"op":"transpose","matrix":[[1,2],[3,4]]}')
def test_linalg_jagged_matrix_raises():
ps = get("linear-algebra@v1")
with pytest.raises(PiStarError):
ps.canonicalize(b'{"op":"det","matrix":[[1,2],[3,4,5]]}')
def test_linalg_cell_format_folds():
"""1, 1.0, '1.0', '1/1' all sympify to the same SymPy Integer/Rational
so the canonical bytes for det should match."""
ps = get("linear-algebra@v1")
a = ps.canonicalize(b'{"op":"det","matrix":[[1,2],[3,4]]}')
b = ps.canonicalize(b'{"op":"det","matrix":[[1.0,2.0],[3.0,4.0]]}')
c = ps.canonicalize(b'{"op":"det","matrix":[["1","2"],["3","4"]]}')
assert a == b == c
# ===== function-sampled@v1 (Phase 7) ======================================
def test_function_sampled_basic_polynomial():
"""x² sampled at 0,1,2,3,4 with dv=1 → 0|1|4|9|16."""
ps = get("function-sampled@v1")
out = ps.canonicalize(
b'{"f":"x**2","x":"x","x_min":0,"x_max":4,"n_samples":5,"dv":1}'
)
assert out == b"dt=1;dv=1;n=5;t0=0:0|1|4|9|16"
def test_function_sampled_equivalent_expressions_collapse():
"""sin(x) and 2*sin(x)/2 are textually different but evaluate
identically. Same canonical bytes."""
ps = get("function-sampled@v1")
a = ps.canonicalize(
b'{"f":"sin(x)","x":"x","x_min":0,"x_max":1,"n_samples":11,"dv":0.01}'
)
b = ps.canonicalize(
b'{"f":"2*sin(x)/2","x":"x","x_min":0,"x_max":1,"n_samples":11,"dv":0.01}'
)
assert a == b
def test_function_sampled_different_grid_distinct():
"""Different sample grid → different canonical."""
ps = get("function-sampled@v1")
a = ps.canonicalize(
b'{"f":"x","x":"x","x_min":0,"x_max":4,"n_samples":5,"dv":1}'
)
b = ps.canonicalize(
b'{"f":"x","x":"x","x_min":0,"x_max":4,"n_samples":9,"dv":1}'
)
assert a != b
def test_function_sampled_output_format_byte_compatible_with_time_series():
"""Output starts with the same 'dt=...;dv=...;n=...;t0=...:' header
as time-series-quantized@v1 so storage paths can treat both
interchangeably."""
ps = get("function-sampled@v1")
out = ps.canonicalize(
b'{"f":"x","x":"x","x_min":0,"x_max":2,"n_samples":3,"dv":1}'
).decode("utf-8")
assert out.startswith("dt=")
assert ";dv=" in out
assert ";n=" in out
assert ";t0=0:" in out
def test_function_sampled_x_max_must_exceed_x_min():
ps = get("function-sampled@v1")
with pytest.raises(PiStarError):
ps.canonicalize(
b'{"f":"x","x":"x","x_min":1,"x_max":1,"n_samples":3,"dv":1}'
)
def test_function_sampled_n_samples_minimum_two():
ps = get("function-sampled@v1")
with pytest.raises(PiStarError):
ps.canonicalize(
b'{"f":"x","x":"x","x_min":0,"x_max":1,"n_samples":1,"dv":1}'
)
def test_function_sampled_dv_must_be_positive():
ps = get("function-sampled@v1")
with pytest.raises(PiStarError):
ps.canonicalize(
b'{"f":"x","x":"x","x_min":0,"x_max":1,"n_samples":3,"dv":0}'
)
def test_function_sampled_complex_value_raises():
"""sqrt(x) at negative x is complex; should raise rather than
silently drop the imaginary part."""
ps = get("function-sampled@v1")
with pytest.raises(PiStarError):
ps.canonicalize(
b'{"f":"sqrt(x)","x":"x","x_min":-1,"x_max":1,"n_samples":3,"dv":0.1}'
)

View file

@ -0,0 +1,233 @@
"""Tests for ``tabular-pinned@v1`` (the last reserved-stub π*).
Closes the registry chapter every modality the substrate paper
reserved (text, claim_lattice, code, arithmetic, logic, time-series,
tabular) is now real. Plus the math substrate extras (algebra-symbolic,
calculus-derivative/integral/limit/series, linear-algebra,
function-sampled).
"""
from __future__ import annotations
import pytest
from arborist.pi_star import PiStarError, get
# ----- basic round-trips -------------------------------------------------
def test_tabular_basic_int_table():
ps = get("tabular-pinned@v1")
out = ps.canonicalize(
b'{"schema":[{"name":"id","type":"int"}],"key_columns":["id"],'
b'"rows":[[1],[2],[3]]}'
)
assert out == b"schema=id:int\nkey=id\nn=3\nrows:\n1\n2\n3"
def test_tabular_empty_rows():
"""Empty body still emits header + n=0."""
ps = get("tabular-pinned@v1")
out = ps.canonicalize(
b'{"schema":[{"name":"id","type":"int"}],"key_columns":["id"],'
b'"rows":[]}'
)
assert out == b"schema=id:int\nkey=id\nn=0\nrows:\n"
def test_tabular_schema_only_no_key_columns():
"""key_columns=[] is valid: rows preserve input order."""
ps = get("tabular-pinned@v1")
out = ps.canonicalize(
b'{"schema":[{"name":"x","type":"str"}],"key_columns":[],'
b'"rows":[["b"],["a"],["c"]]}'
)
assert out == b"schema=x:str\nkey=\nn=3\nrows:\nb\na\nc"
# ----- equivalence-class collapse ----------------------------------------
def test_tabular_row_order_collapses_under_key_sort():
"""Same data, different row order → same canonical (key-sorted)."""
ps = get("tabular-pinned@v1")
a = ps.canonicalize(
b'{"schema":[{"name":"id","type":"int"},{"name":"name","type":"str"}],'
b'"key_columns":["id"],"rows":[[2,"b"],[1,"a"],[3,"c"]]}'
)
b = ps.canonicalize(
b'{"schema":[{"name":"id","type":"int"},{"name":"name","type":"str"}],'
b'"key_columns":["id"],"rows":[[1,"a"],[2,"b"],[3,"c"]]}'
)
assert a == b
def test_tabular_int_float_string_fold_equivalence():
"""1 ≡ 1.0 ≡ "1.0" for int columns; same canonical."""
ps = get("tabular-pinned@v1")
a = ps.canonicalize(
b'{"schema":[{"name":"v","type":"int"}],"key_columns":["v"],'
b'"rows":[[1]]}'
)
b = ps.canonicalize(
b'{"schema":[{"name":"v","type":"int"}],"key_columns":["v"],'
b'"rows":[[1.0]]}'
)
c = ps.canonicalize(
b'{"schema":[{"name":"v","type":"int"}],"key_columns":["v"],'
b'"rows":[["1.0"]]}'
)
assert a == b == c
def test_tabular_rational_fold():
"""0.5 → 1/2; "1/2" → 1/2; same canonical."""
ps = get("tabular-pinned@v1")
a = ps.canonicalize(
b'{"schema":[{"name":"r","type":"rational"}],"key_columns":[],'
b'"rows":[[0.5]]}'
)
b = ps.canonicalize(
b'{"schema":[{"name":"r","type":"rational"}],"key_columns":[],'
b'"rows":[["1/2"]]}'
)
assert a == b
assert b"1/2" in a
def test_tabular_bool_normalization():
"""true / True / "true" / 1 (when bool col) — fold to 'true'."""
ps = get("tabular-pinned@v1")
a = ps.canonicalize(
b'{"schema":[{"name":"flag","type":"bool"}],"key_columns":[],'
b'"rows":[[true]]}'
)
b = ps.canonicalize(
b'{"schema":[{"name":"flag","type":"bool"}],"key_columns":[],'
b'"rows":[["true"]]}'
)
c = ps.canonicalize(
b'{"schema":[{"name":"flag","type":"bool"}],"key_columns":[],'
b'"rows":[["yes"]]}'
)
assert a == b == c
assert b"true" in a
# ----- equivalence-class distinction -------------------------------------
def test_tabular_different_schema_distinct():
ps = get("tabular-pinned@v1")
a = ps.canonicalize(
b'{"schema":[{"name":"x","type":"int"}],"key_columns":[],'
b'"rows":[[1]]}'
)
b = ps.canonicalize(
b'{"schema":[{"name":"y","type":"int"}],"key_columns":[],'
b'"rows":[[1]]}'
)
assert a != b
def test_tabular_different_key_columns_distinct():
"""Same data, different declared key → different canonical (sort changes)."""
ps = get("tabular-pinned@v1")
a = ps.canonicalize(
b'{"schema":[{"name":"a","type":"int"},{"name":"b","type":"int"}],'
b'"key_columns":["a"],"rows":[[2,1],[1,2]]}'
)
b = ps.canonicalize(
b'{"schema":[{"name":"a","type":"int"},{"name":"b","type":"int"}],'
b'"key_columns":["b"],"rows":[[2,1],[1,2]]}'
)
assert a != b
def test_tabular_header_case_pinned():
"""Header case is part of identity; 'Name''name'."""
ps = get("tabular-pinned@v1")
a = ps.canonicalize(
b'{"schema":[{"name":"Name","type":"str"}],"key_columns":[],'
b'"rows":[["x"]]}'
)
b = ps.canonicalize(
b'{"schema":[{"name":"name","type":"str"}],"key_columns":[],'
b'"rows":[["x"]]}'
)
assert a != b
# ----- error paths -------------------------------------------------------
def test_tabular_missing_schema_field_raises():
ps = get("tabular-pinned@v1")
with pytest.raises(PiStarError):
ps.canonicalize(b'{"key_columns":[],"rows":[]}')
def test_tabular_unknown_type_raises():
ps = get("tabular-pinned@v1")
with pytest.raises(PiStarError):
ps.canonicalize(
b'{"schema":[{"name":"x","type":"datetime"}],'
b'"key_columns":[],"rows":[]}'
)
def test_tabular_key_column_not_in_schema_raises():
ps = get("tabular-pinned@v1")
with pytest.raises(PiStarError):
ps.canonicalize(
b'{"schema":[{"name":"a","type":"int"}],'
b'"key_columns":["b"],"rows":[[1]]}'
)
def test_tabular_duplicate_column_names_raises():
ps = get("tabular-pinned@v1")
with pytest.raises(PiStarError):
ps.canonicalize(
b'{"schema":[{"name":"a","type":"int"},{"name":"a","type":"str"}],'
b'"key_columns":[],"rows":[]}'
)
def test_tabular_row_arity_mismatch_raises():
ps = get("tabular-pinned@v1")
with pytest.raises(PiStarError):
ps.canonicalize(
b'{"schema":[{"name":"a","type":"int"},{"name":"b","type":"int"}],'
b'"key_columns":[],"rows":[[1]]}'
)
def test_tabular_int_with_non_integer_float_raises():
ps = get("tabular-pinned@v1")
with pytest.raises(PiStarError):
ps.canonicalize(
b'{"schema":[{"name":"v","type":"int"}],"key_columns":[],'
b'"rows":[[1.5]]}'
)
def test_tabular_bool_subtype_of_int_rejected_in_int_column():
"""Python booleans are int-subtypes; we reject so True doesn't
silently become 1 in an int column."""
ps = get("tabular-pinned@v1")
with pytest.raises(PiStarError):
ps.canonicalize(
b'{"schema":[{"name":"v","type":"int"}],"key_columns":[],'
b'"rows":[[true]]}'
)
# ----- registry presence -------------------------------------------------
def test_tabular_pinned_registered():
"""Sanity: the kernel is in the registry under the expected key."""
from arborist.pi_star import list_keys
assert "tabular-pinned@v1" in list_keys()