core/docs/decisions/ADR-0140-subtract-and-additive-group-closure.md
Shay 622919019d
feat(ADR-0140): subtract as inverse translator + additive group closure (#215)
Extends generate/math_versor_arithmetic.py with one new function:

    def subtract(addend: float) -> np.ndarray:
        return translator(-float(addend))

Single-line delegate to translator(); no new algebra.

Adds tests/test_arithmetic_subtract_and_group.py covering all nine
ADR-0140 acceptance families:

  Families 1-6 (ADR-0139 families applied to subtract):
    1. Embedding well-formedness — null cone preserved for subtract cases
    2. Translator-of-negative well-formedness — versor_condition < 1e-6
    3. Closure — sandwich result stays on null cone
    4. Arithmetic correctness — decoded value == a − b within 1e-9
    5. Replay determinism — byte-identical across runs
    6. Composability — subtract(c) ∘ subtract(b) decodes to a − b − c

  New group-property families (structural verification of ADR-0139 claim):
    7. Inverse composition — T_{-b} * T_b = identity (max residual: 0.000e+00)
    8. Round-trip closure — versor_apply(T_{-b}, versor_apply(T_b, X)) → (a, u)
    9a. Sum composition — T_a * T_b = T_{a+b} (max residual: 0.000e+00)
    9b. Commutativity — T_a * T_b byte-equals T_b * T_a (all 10 cases)

All 96 tests pass. Group residuals are exactly 0.0 in float64.
The additive subgroup of Cl(4,1) translators along e1 is abelian and
closed; ADR-0139's algebraic claim holds at the group level.
2026-05-24 08:34:35 -07:00

11 KiB
Raw Permalink Blame History

ADR-0140 — subtract as Inverse Translator + Additive Group Closure

Status: Draft Date: 2026-05-24 Author: CORE agents Parent: ADR-0139 Engine target: CGA cognitive engine (algebra/versor.py, algebra/cga.py)


Context

ADR-0139 proved one operation — add — can be represented as a closed unit versor in Cl(4,1) with all residuals exactly 0.0 in float64. The construction T_t = 1 - 0.5·(t·n_inf) produces an exactly-closed translator because (t·n_inf)² = 0 algebraically before any float arithmetic occurs.

That spike proved the algebraic substrate can host one operation. It did not yet prove anything about the structure the operations should form. Subtract is the smallest follow-on that:

  1. Demonstrates the family generalizes — subtract is the same construction with a negated addend, so it should inherit the exact-closure property for free.
  2. Surfaces the additive group structure. Add + subtract together form an abelian group on the e1 axis. The structural identities (inverse, identity, associativity, commutativity) are the actual thing being decoded — not just "two operations work."

The thesis (thesis-decoding-not-generating) is sharper here: the engine isn't being given subtract as a new capability; it's being shown that the additive group was already there in the algebra, and CORE is decoding the relationships that already hold between the operations.

This ADR makes that decoding visible by testing the group axioms directly.


Decision

Construction

subtract(addend) is implemented as translator(-addend). No new algebra; the existing translator() from ADR-0139 is reused with a negated argument.

def subtract(addend: float) -> np.ndarray:
    return translator(-float(addend))

Group-property tests

Beyond the six assertion families inherited from ADR-0139, this ADR introduces three new families that test the additive group structure:

  • Family 7 — Inverse composition. T_{-b} · T_b = identity. Specifically, the geometric product of translator(-b) and translator(b) equals the scalar 1 (component 0 = 1, all others 0) within machine epsilon.

  • Family 8 — Round-trip closure. versor_apply(T_{-b}, versor_apply(T_b, X)) = X. An additive shift followed by its inverse recovers the original embedded quantity byte-equal at the chosen tolerance.

  • Family 9 — Commutativity of translators. T_a · T_b = T_b · T_a = T_{a+b}. Additive translations commute and compose into a single translator by the sum. This is the abelian property of the group; if it fails, the algebra is decoding something other than scalar addition.


Acceptance

A single test module — tests/test_arithmetic_subtract_and_group.py — passes with the following assertions on a small fixed set of (a, b) pairs.

Inherited from ADR-0139 (applied to subtract)

The same six families ADR-0139 used for add, applied to subtract:

  1. Embedding well-formedness (re-verified on subtract cases)
  2. Translator-of-negative well-formedness — versor_condition(subtract(b)) < 1e-6
  3. Closure under sandwich — cga_inner(R, R) < 1e-5
  4. Arithmetic correctness — decode_quantity(R, u) == (a b, u) within 1e-9
  5. Replay determinism — byte-identical across runs
  6. Composability — subtract(c) ∘ subtract(b) decodes to a b c

New group-property families

  1. Inverse composition. For each b in the test set: geometric_product(translator(-b), translator(b)) equals the scalar versor [1, 0, 0, ..., 0] within 1e-9 component-wise.

  2. Round-trip closure. For each (a, b) in the test set: versor_apply(translator(-b), versor_apply(translator(b), embed_quantity(a, "u"))) decodes to (a, "u") with error < 1e-9. Includes the case b = 0 (degenerate — should be identity in the algebra).

  3. Commutativity / composition into sum. For each (a, b):

    • geometric_product(translator(a), translator(b)) equals translator(a + b) component-wise within 1e-9.
    • geometric_product(translator(a), translator(b)) equals geometric_product(translator(b), translator(a)) byte-equal.

Fixed test cases

Subtract cases (a, b):
  (0, 0), (5, 0), (0, 5),
  (10, 3), (3, 10),
  (1.5, 0.5), (0.25, 0.75),
  (-5, 3), (5, -3),
  (-2, -3), (100, 1)

Group cases (a, b) for families 7-9:
  (0, 0), (1, 0), (0, 1),
  (1, 1), (-1, 1), (3, 4),
  (0.5, 0.5), (-2.5, 2.5),
  (100, 1), (1, 100)

Non-goals

Out of scope for this ADR (every item below is for a follow-on):

  • No multiply, divide, or any non-additive operation. multiply is ADR-0141 territory — the dilator construction is structurally different and concentrates the next risk.
  • No MathProblemGraph consumer. No PropositionGraph construction. No CognitiveTurnPipeline integration.
  • No GSM8K case routed.
  • No pack changes.
  • No proof of associativity beyond what binary-composition tests implicitly cover. (Three-element associativity T_a · (T_b · T_c) = (T_a · T_b) · T_c would be a clean addition but is redundant given commutativity + closure into the sum.)
  • No "inverse element" exposed as a separate primitive. subtract(b) is the inverse of add(b); the engine does not need a named "inverse" function until ADR-0143 (compare) or later.

Engine B (math_solver.py, candidate-graph parser, S.x corridor) remains unchanged. The 3/50 GSM8K admission set is preserved.


Rationale

Why test the group axioms here rather than later?

The thesis says the engine decodes what is already there. The additive group on the real line is already there — it's a mathematical fact independent of CORE. If translator() faithfully decodes addition, then the group axioms must hold automatically. Testing them isn't "adding a feature"; it's verifying that what we think we decoded is what we actually decoded.

If the inverse composition test (family 7) fails, the construction is not decoding addition — it's decoding something that looks like addition on small cases but doesn't form a group. That would invalidate ADR-0139 retroactively and pause the lift program.

If commutativity (family 9) fails, the algebra is not decoding scalar addition — it's decoding some non-abelian operation, which means scalar arithmetic can't be lifted onto this construction as we assumed.

So families 7-9 are not nice-to-haves. They are the structural verification that ADR-0139's algebraic claim is actually true at the group level, not just at the point-pair level.

Why subtract, not multiply, as the next step?

Multiply is structurally different — it's a dilator, not a translator, and the dilator construction in CGA (D_s = cosh(α/2) + sinh(α/2)·(n_o ∧ n_inf)) sits on a different versor manifold. The closure properties have to be re-derived. That's the next big risk; doing subtract first locks down the additive subgroup so multiply has a clean foundation to extend from.

Subtract is also the smallest possible follow-on — same construction, same module, three new test families. If subtract's spike fails, we catch the inverse-element failure with a one-line change rather than a multi-module multiply implementation.

Why no MathProblemGraph wiring yet?

Same reason as ADR-0139: the substrate must be proven before integration. We don't yet know whether multiply (the next risk) closes; if it doesn't, the integration plan changes shape. Wiring add and subtract into MathProblemGraph before multiply is tested would couple two unrelated unknowns.


Risks

Materially smaller than ADR-0139 because most of the load-bearing algebra is already discharged:

  • The inverse-composition test (family 7) may not hit exact zero. In ADR-0139, T_t · reverse(T_t) = 1 was exact because of an algebraic cancellation B² = 0. The composition T_{-b} · T_b is a different product (reverse is not the same as negate). The expected residual is bounded by (geometric_product cancellation precision) at float64. If it lands between 1e-9 and 1e-6, the test passes the versor-condition threshold but suggests the algebra isn't exactly the additive group. Worth measuring honestly.

  • Commutativity is non-trivial at the multivector level. Two bivectors don't generally commute. translator(a) · translator(b) multiplied out involves cross-terms; whether those cancel depends on the structure of B_a = a·e1·n_inf and B_b = b·e1·n_inf. They do (because both bivectors live in the same 2D subspace spanned by e14 and e15, where the algebra reduces to a commuting plane). But this is the kind of property that's true by structure, not by accident — and family 9 is exactly the test that confirms it.

  • b = 0 edge case. translator(0) should be the scalar 1 exactly. The construction 1 - 0.5 · (0 · n_inf) simplifies to 1 symbolically, and float arithmetic should reach the same result, but family 8's b = 0 case verifies it explicitly.


Replay & invariants

Same invariants as ADR-0139:

  • versor_condition(T) < 1e-6 for all constructed translators (now including negative addends).
  • Null inputs to versor_apply stay null.
  • No new normalization is introduced.
  • Float64 end-to-end where precision matters.
  • Determinism: same (a, b) → identical multivector bytes across runs.

New cross-cutting invariant introduced by this ADR (worth pinning in the test module): the additive subgroup of Cl(4,1) translators along e1 is abelian and closed under composition. Families 7-9 are the CI-enforced statement of this invariant.


Sequencing for follow-on

Only if every assertion in this ADR passes:

  1. ADR-0141: multiply as dilator. Concentrates the next risk.
  2. ADR-0142: Rate as bivector; apply_rate as combined translator-dilator.
  3. ADR-0143: compare_* at the proposition layer, not the versor layer.
  4. ADR-0144: PropositionGraph from MathProblemGraph.
  5. ADR-0145: One GSM8K case routed end-to-end through Engine A.

If any assertion fails — particularly family 7 (inverse) or family 9 (commutativity) — ADR-0139's algebraic claim is invalidated retroactively. The lift program pauses until the failure mode is documented and a revised construction is proposed.


Decision summary

Extend generate/math_versor_arithmetic.py with one new function (subtract, a one-line delegate to translator(-b)). Add one test module verifying the six ADR-0139 acceptance families against subtract, plus three new families that test the additive group structure (inverse, round-trip, commutativity).

Acceptance is binary: every test passes, or the ADR is withdrawn and ADR-0139's claim is re-examined.