From 5f5f485477767648372de208eb50829cecc381f2 Mon Sep 17 00:00:00 2001 From: Mingli Yuan Date: Thu, 27 Aug 2026 22:15:54 +0800 Subject: [PATCH] research: enforce Brownian AMP generator semantics --- docs/RESEARCH_STATUS.md | 2 +- sonnet/README.md | 2 +- .../00-problem-frontier.md | 35 +- .../01-amp-generator-chart-gate.md | 138 +++++++ sonnet/brownian-scale-fibre/README.md | 47 ++- sonnet/brownian-scale-fibre/brownian_amp.py | 309 +++++++++++++++ .../brownian-scale-fibre/phase0_contract.py | 56 ++- .../phase1_amp_contract.py | 356 ++++++++++++++++++ .../test_brownian_amp_generator_gate.py | 175 +++++++++ .../test_brownian_scale_fibre_phase0.py | 14 +- tests/research/test_native_method_firewall.py | 126 +++++++ workstreams/native_method_firewall/README.md | 30 +- .../native_method_firewall.py | 209 ++++++++++ 13 files changed, 1453 insertions(+), 46 deletions(-) create mode 100644 sonnet/brownian-scale-fibre/01-amp-generator-chart-gate.md create mode 100644 sonnet/brownian-scale-fibre/brownian_amp.py create mode 100644 sonnet/brownian-scale-fibre/phase1_amp_contract.py create mode 100644 tests/research/test_brownian_amp_generator_gate.py diff --git a/docs/RESEARCH_STATUS.md b/docs/RESEARCH_STATUS.md index 24356a5a..b22bfa7a 100644 --- a/docs/RESEARCH_STATUS.md +++ b/docs/RESEARCH_STATUS.md @@ -159,7 +159,7 @@ Process Geometry solver or independent adoption. | [`am-conformal-chart-normal-forms`](../sonnet/am-conformal-chart-normal-forms/00-problem-frontier.md) | Phase 1 exact Riccati lift, Möbius covariance, scalar gauge, cubic no-go, and eight-axis cost accounting | run oracle-isolated bounded discovery (1B) before the pendulum atlas search; no economy theorem yet | | [`moving-am-observer`](../sonnet/moving-am-observer/README.md) | static-observer no-go followed by blind moving-frame recovery, held-out family selection, task-equivalence quotient, blind morphism discovery, grammar stability, and dimensionful Bellman covariance | broader non-affine/multi-family or stochastic pressure, now separated into its own Sonnet | | [`stochastic-feedback-trap-first-passage`](../sonnet/stochastic-feedback-trap-first-passage/README.md) | bounded grammar census, exact Itô task quotient, independent BVP/Monte Carlo first-passage comparison, and reset Bellman value/policy covariance complete | calibration is closed; new discovery or API incubation requires a new contract | -| [`brownian-scale-fibre`](../sonnet/brownian-scale-fibre/README.md) | S0/S1 initialized: blind centered finite-law scale balance, typed drift refusal, exact endpoint fibres, and concatenation pushforward | separate lattice point return, continuum neighbourhood recurrence, and singleton hitting before authorizing a Brownian/heat-kernel lowering | +| [`brownian-scale-fibre`](../sonnet/brownian-scale-fibre/README.md) | S0/S1 corrected to PRE-AMP; executable A/M/P gate finds a global positive-position obstruction and an exact ensemble A/M/integer-P adapter with infinite-closure and path residuals | freeze a task that resolves one declared residual before recurrence or Brownian/heat-kernel lowering resumes | | [`lonely-runner`](../sonnet/lonely-runner/README.md) | exact upstream pruning transfer and bounded K4/K5 contact mechanisms; Phase 15A global audit complete | lift-aware initial search on solved cases, then frozen K13 validation; `LRC(13)` remains open | | [`hidden-am-noether`](../sonnet/hidden-am-noether/README.md) | bounded static-observer route reached a structural no-go; the viable moving-observer continuation is tracked separately | do not reopen static conjugation without new grammar or task semantics | | [`pcr3bp-history-cost`](../sonnet/pcr3bp-history-cost/README.md) | Phase 0/1 history, scale-jet, topology, and coding audit complete; Phase 2 contract frozen | execute converged return/absorbing ensemble and twisted partition audit; no numerical result yet | diff --git a/sonnet/README.md b/sonnet/README.md index e37fc834..e379ec70 100644 --- a/sonnet/README.md +++ b/sonnet/README.md @@ -80,7 +80,7 @@ deferred until its next oracle/evidence gate is affordable. | [`hidden-am-noether/`](hidden-am-noether/) | static branch closed negatively | a frozen observer in the same product-affine group cannot reveal a missing stabilizer dimension | [static-observer no-go](hidden-am-noether/03-static-observer-no-go-and-schedule-split.md); continuation moved to `moving-am-observer/` | | [`moving-am-observer/`](moving-am-observer/) | affine deterministic phase closed | bounded blind observer discovery, task-equivalent minimum slice, and dimensionful Bellman covariance on the declared family | [study ledger](moving-am-observer/README.md); stochastic continuation moved to its own Sonnet | | [`stochastic-feedback-trap-first-passage/`](stochastic-feedback-trap-first-passage/) | current calibration closed | exact Itô task quotient plus independent first-passage and reset-Bellman covariance across 242 monotone charts | [Phase-4 results](stochastic-feedback-trap-first-passage/09-phase4-reset-bellman-results.md); further work requires a new Sonnet or governed extraction proposal | -| [`brownian-scale-fibre/`](brownian-scale-fibre/) | S0/S1 active | blind centered finite-law scale balance and exact endpoint-fibre concatenation; no continuum limit or heat-kernel lowering yet | [problem frontier](brownian-scale-fibre/00-problem-frontier.md); next separate recurrence observers, then certify the continuum lowering | +| [`brownian-scale-fibre/`](brownian-scale-fibre/) | A/M/P correction gate active | S0/S1 relabelled PRE-AMP; positive position chart fails globally, while the ensemble chart gives exact A shift, M scale, and integer-P replicas with closure/path residuals | [AMP chart gate](brownian-scale-fibre/01-amp-generator-chart-gate.md); recurrence and heat-kernel continuation paused | | [`pcr3bp-history-cost/`](pcr3bp-history-cost/) | Phases 0–1 complete; Phase 2 frozen | lifted topology and scale-jet reconstruction separate word, clock, deck, and hyperbolic costs; no Bellman/Huffman source is yet justified | [Phase-2 contract](pcr3bp-history-cost/02-return-partition-holonomy-contract.md); next run the frozen two-gate covariance and convergence gates | | [`am-conformal-chart-normal-forms/`](am-conformal-chart-normal-forms/) | T0/T1; Phase 1 mechanism calibrated | exact Riccati lift, Möbius covariance, scalar-gauge invariance, cubic no-go, and eight-axis Pareto accounting; no discovery or economy theorem | [Phase-1 results](am-conformal-chart-normal-forms/02-phase1-riccati-results.md); next freeze a blind low-height grammar and run bounded recovery | | [`effective-scale-carrier-ladder/`](effective-scale-carrier-ladder/) | T1 / NARROW | finite syntax decision eliminates a surreal runtime; symbolic height is a real C2 obstruction, but semantic evaluation and C3/C4 separation remain open | [results](effective-scale-carrier-ladder/01-results.md), [compiler](../workstreams/carrier_ladder/compiler/), and [commit--reveal audit](../workstreams/carrier_ladder/redteam/); next implement an effective normalized hyperiteration fragment, not a general surreal runtime | diff --git a/sonnet/brownian-scale-fibre/00-problem-frontier.md b/sonnet/brownian-scale-fibre/00-problem-frontier.md index 92f4731a..f214c714 100644 --- a/sonnet/brownian-scale-fibre/00-problem-frontier.md +++ b/sonnet/brownian-scale-fibre/00-problem-frontier.md @@ -1,10 +1,22 @@ # Problem frontier: recalculate Brownian motion from process fibres -Status: frozen S0/S1 contract for -[#158](https://github.com/mountain/process-geometry/issues/158), downstream of +Status: frozen `PRE-AMP` S0/S1 discrete control for +[#158](https://github.com/mountain/process-geometry/issues/158), corrected by +[#160](https://github.com/mountain/process-geometry/issues/160), downstream of [#140](https://github.com/mountain/process-geometry/issues/140) and the native-method firewall merged in -[#157](https://github.com/mountain/process-geometry/pull/157). +[#157](https://github.com/mountain/process-geometry/pull/157). Recurrence and +heat-kernel continuation is paused pending the A/M/P chart gate. + +## 0. Correction boundary + +The finite-history calculation below is mathematically valid, but it is not an +AMP-native calculation. Its executable grammar contains only `A-step`: +addition is active, while population size and probe scale are external +parameters. It contains neither finite M/P flows nor their infinitesimal +generators and brackets. The result is therefore retained as a discrete +control, not promoted by changing its vocabulary. The corrected AMP gate is +recorded in [`01-amp-generator-chart-gate.md`](01-amp-generator-chart-gate.md). ## 1. Primitive process before the continuum answer @@ -82,17 +94,19 @@ This phase certifies a necessary nontrivial scale and its local response coefficient. It does **not** yet prove convergence to a Gaussian law or a continuum path process. -## 3. Method firewall +## 3. PRE-AMP method firewall -S0/S1 have no allowed lowering witness. The native source contains only the +The discrete S0/S1 control has no allowed lowering witness. Its source contains only the finite increment law, exact centered response, chronological histories, endpoint fibres, and their composition. Classical local expansion, transform, continuum PDE, and simulation routes are declared separately in `phase0_contract.py` and cannot enter a native trace without a later -task-adequacy witness. +task-adequacy witness. Its method events are now required to name `A-step`, +and its contract explicitly says that M, P, and AMP brackets are absent. This is deliberately stronger than asking an agent not to use an oracle in -prose: the merged method tool rejects the corresponding event in a native lane. +prose: the method tool rejects both classical oracle entry and an AMP claim +whose native evidence omits any task-required generator. ## 4. Held-out and red-team cases @@ -109,15 +123,16 @@ prose: the merged method tool rejects the corresponding event in a native lane. ## 5. Relation to the larger programme -This is the first independent scale/fibre family after the Bessel-based draft +This is the first independent discrete scale/fibre control after the Bessel-based draft in PR #141. It pressures U1, U2, U3, U4, and Effective Analysis because the same construction connects raw histories, scale, measured fibres, a continuum lowering, and later physical response. At S0/S1 it remains T1 and leaves the Mathematical Core, Engineering Architecture, Theory Map, dependencies, and API unchanged. -Surreal numbers are not needed for this finite-height task. That is a negative -runtime result, not an argument against later AMP or higher-height uses. +Surreal numbers are not needed for this finite-height control or the first +exact integer-replica AMP adapter. That is a negative runtime result, not an +argument against later AMP closure or higher-height uses. ## 6. Claim ceiling diff --git a/sonnet/brownian-scale-fibre/01-amp-generator-chart-gate.md b/sonnet/brownian-scale-fibre/01-amp-generator-chart-gate.md new file mode 100644 index 00000000..00b0ae91 --- /dev/null +++ b/sonnet/brownian-scale-fibre/01-amp-generator-chart-gate.md @@ -0,0 +1,138 @@ +# Brownian A/M/P generator and chart gate + +Status: executable correction for +[#160](https://github.com/mountain/process-geometry/issues/160). This gate +precedes recurrence, continuum, and heat-kernel work. + +## 1. What changed + +The first Brownian phase derived a correct discrete scale balance and exact +endpoint fibres, but the computation had only an additive step process. +Population (N) and probe contraction (N^{-a}) were external parameters. +Calling that calculation AMP would therefore confuse an answer expressed with +three letters with a calculation generated by three processes. + +The corrected method contract requires every AMP grammar to declare all of + + +\[ +A_t(x)=x+t,\qquad +M_s(x)=e^s x,\qquad +P_r(x)=\exp(e^r\operatorname{Log}x), +\] + +their infinitesimal actions + +\[ +A=\partial_x,\qquad +M=x\partial_x,\qquad +P=x\log(x)\partial_x, +\] + +and the three relation witnesses + +\[ +[A,M]=A,\qquad [M,P]=M,\qquad +[A,P]=(1+\log x)\partial_x. +\] + +The last bracket is not in the constant span of (A,M,P). Full A/M/P is not +one three-dimensional Lie group. Its closure generates further +logarithmically weighted vector fields, represented at this gate by the +residual family + +\[ +V_{m,n}=x^m(\log x)^n\partial_x. +\] + +The software now rejects an AMP contract that omits a generator or relation, +hides the A/P escape, or claims a task without trace evidence for every +generator that task requires. + +## 2. Competing chart I: physical position + +The positive real chart is faithful to a physical position state and supports +the real logarithm used by P. M preserves positivity. A does not: the exact +symmetric history + +\[ +1\xrightarrow{A_{-1}}0\xrightarrow{A_{+1}}1 +\] + +leaves the common A/M/P domain at its first step. The executable audit returns +the first exit index, value, and offending increment as a typed obstruction. + +This is not a proof that no atlas can represent Brownian position. It is a +proof that the single real positive chart is not global, so any continuation +must submit branch/gluing semantics rather than silently applying Log across +zero. + +## 3. Competing chart II: positive ensemble observer + +For a finite law (mu), take the positive real observer + +\[ +Z_\mu(s)=\mathbb E_\mu[e^{sX}]. +\] + +The executable finite carrier stores exact atoms + +\[ +E_q(s)=e^{qs},\qquad E_pE_q=E_{p+q}, +\] + +with rational exponents and coefficients. It is exponential-polynomial-like; +it is not lowered to an ordinary polynomial, Taylor series, matrix, or Fourier +basis. The three process roles become + +\[ +Z_{X+t}=E_tZ_X,\qquad +Z_{\lambda X}(s)=Z_X(\lambda s),\qquad +Z_{X_1+\cdots+X_N}=Z_X^N. +\] + +Thus A shifts atom exponents, M rescales them, and the nonnegative-integer +slice of P composes independent replicas. For the symmetric unit law the +fifth replica is computed exactly as + +\[ +\left(\tfrac12E_{-1}+\tfrac12E_1\right)^5 +=\tfrac1{32}E_{-5}+\tfrac5{32}E_{-3} ++\tfrac5{16}E_{-1}+\tfrac5{16}E_1 ++\tfrac5{32}E_3+\tfrac1{32}E_5. +\] + +An independent comparison certifies coefficient-by-coefficient equality with +the PRE-AMP endpoint-fibre law at horizon five. + +## 4. What the ensemble chart does not solve + +Two residuals remain explicit. + +First, the continuous P flow (Z\mapsto Z^{e^r}) is pointwise meaningful for +positive real (Z), but a noninteger power generally exits the finite +exponential-atom carrier. The current exact implementation therefore claims +only the integer replica slice; it does not replace the missing closure by a +series expansion. + +Second, the endpoint observer identifies histories such as +((+1,-1)) and ((-1,+1)). They have the same terminal observer but different +running maxima and chronological order. The ensemble chart is exact for the +declared endpoint-law task, not for path-space tasks. + +## 5. Result and next authorization gate + +The first nontrivial result is a separation, not a Brownian theorem: + +| Candidate | Exact gain | Blocking residual | +| --- | --- | --- | +| positive position | direct A/M/P state semantics | A crosses zero; P needs branch/gluing data | +| positive ensemble observer | exact A shift, M scale, integer-P replicas; exact endpoint-law certificate | noninteger P leaves finite atoms; paths are forgotten | + +This is enough to correct the software method and expose a usable +polynomial-like calculation at the AMP level. It is not enough to authorize +recurrence, a continuum Brownian law, a heat kernel, a speedup claim, or +surreal arithmetic. The next task must be frozen around one residual—either a +branch-aware position atlas, a P-closed observer enlargement, or a +path-sensitive fibre—and must beat the declared discrete control on that same +task before the broader Brownian narrative resumes. diff --git a/sonnet/brownian-scale-fibre/README.md b/sonnet/brownian-scale-fibre/README.md index eae1b17c..e75d493d 100644 --- a/sonnet/brownian-scale-fibre/README.md +++ b/sonnet/brownian-scale-fibre/README.md @@ -1,7 +1,9 @@ # Sonnet — Brownian scale and endpoint fibres Research-local calibration for -[#158](https://github.com/mountain/process-geometry/issues/158). +[#158](https://github.com/mountain/process-geometry/issues/158), corrected by +the A/M/P method gate in +[#160](https://github.com/mountain/process-geometry/issues/160). This Sonnet recalculates the entrance to Brownian motion from raw increment histories. It does not begin with the continuum process, a Gaussian density, @@ -11,25 +13,36 @@ Read in order: 1. [`00-problem-frontier.md`](00-problem-frontier.md) — primitive histories, tasks, scale argument, held-outs, and claim ceiling; -2. [`phase0_contract.py`](phase0_contract.py) — frozen executable method lanes; -3. [`brownian_native.py`](brownian_native.py) — exact scale and endpoint-fibre - calculations; -4. [`test_brownian_scale_fibre_phase0.py`](../../tests/research/test_brownian_scale_fibre_phase0.py) - — independent certificates and source firewall. - -Current S0/S1 result: +2. [`phase0_contract.py`](phase0_contract.py) — frozen `PRE-AMP` discrete-control + lanes; +3. [`brownian_native.py`](brownian_native.py) — exact discrete scale and + endpoint-fibre controls; +4. [`01-amp-generator-chart-gate.md`](01-amp-generator-chart-gate.md) — the + corrected A/M/P grammar, competing charts, and result boundary; +5. [`phase1_amp_contract.py`](phase1_amp_contract.py) and + [`brownian_amp.py`](brownian_amp.py) — executable grammar audit and exact + exponential-observer adapter; +6. the two research tests in [`tests/research`](../../tests/research/) — + independent certificates and fail-closed method checks. + +Current corrected boundary: ```text -raw centered finite law -> active response order 2 -> scale balance a=1/2 -finite histories -> endpoint fibres -> exact concatenation pushforward -biased law -> typed centering-required refusal -continuum / heat-kernel lowering -> not yet authorized +PRE-AMP: centered finite law -> response order 2 -> scale balance a=1/2 +PRE-AMP: finite histories -> endpoint fibres -> exact pushforward +AMP position chart -> typed obstruction at zero / negative states +AMP ensemble chart -> exact A shift, M scale, integer-P replicas +full A/M/P closure -> infinite-dimensional residual retained +recurrence / continuum / heat-kernel -> paused and unauthorized ``` -The scale result is narrower than a central-limit theorem. The endpoint result -is narrower than path equivalence. S2 must next separate lattice point return, -continuum neighbourhood recurrence, and singleton hitting before S3 authorizes -a Brownian/heat-kernel lowering. +The earlier scale and endpoint results remain valid controls, but they are not +AMP results: only a discrete additive step was active. The corrected gate +shows that the positive position chart is not global for a symmetric process, +while the positive ensemble observer supports exact A/M and the integer +replica slice of P. Noninteger P generally leaves the finite atom family, and +the observer still forgets paths. S2 recurrence and S3 heat-kernel work remain +paused until one of those residuals is resolved for a frozen task. This task is the second independent representation family requested in [#140](https://github.com/mountain/process-geometry/issues/140). Draft @@ -39,4 +52,4 @@ while it remains unmerged. The completed stochastic feedback-trap Sonnet is an independent downstream Itô/first-passage calibration and is not duplicated here. -No Experimental or Public API pressure follows from S0/S1. +No Experimental or Public API pressure follows from this gate. diff --git a/sonnet/brownian-scale-fibre/brownian_amp.py b/sonnet/brownian-scale-fibre/brownian_amp.py new file mode 100644 index 00000000..38694b6a --- /dev/null +++ b/sonnet/brownian-scale-fibre/brownian_amp.py @@ -0,0 +1,309 @@ +"""Research-local AMP chart candidates for the Brownian correction (#160). + +The executable algebra below is deliberately finite and exact. Its atoms are +``E_q(s) = exp(q*s)`` with rational ``q`` and rational coefficients. It does +not lower them to Taylor coefficients, matrices, or Fourier modes. +""" + +from __future__ import annotations + +from dataclasses import dataclass +from fractions import Fraction +from typing import Iterable + + +class BrownianAMPDomainError(ValueError): + """An AMP chart or exact finite carrier does not cover the requested input.""" + + +class PositionChartDomainError(BrownianAMPDomainError): + """A history left the common positive domain of the A/M/P flows.""" + + def __init__( + self, + *, + step_index: int, + value: Fraction, + offending_increment: Fraction | None, + ) -> None: + self.step_index = step_index + self.value = value + self.offending_increment = offending_increment + super().__init__( + "positive-position-chart-obstruction: " + f"state {value} at step {step_index} is outside x > 0" + ) + + +def _require_fraction(value: object, name: str) -> Fraction: + if not isinstance(value, Fraction): + raise BrownianAMPDomainError(f"{name} must be an exact Fraction") + return value + + +@dataclass(frozen=True) +class PositivePositionAudit: + initial: Fraction + increments: tuple[Fraction, ...] + states: tuple[Fraction, ...] + common_domain: str + generator_ids: tuple[str, ...] + claim_boundary: str + + +def audit_positive_position_history( + initial: Fraction, + increments: Iterable[Fraction], +) -> PositivePositionAudit: + """Check whether an additive history stays in the real Log AMP chart. + + A is used to transport the state. Positivity is the common domain needed + for the real P flow ``x -> exp(exp(r) Log(x))``; M preserves that domain. + The function therefore audits the three-generator chart without pretending + that a symmetric Brownian history stays inside it. + """ + + current = _require_fraction(initial, "initial") + history = tuple(increments) + for increment in history: + _require_fraction(increment, "increment") + if current <= 0: + raise PositionChartDomainError( + step_index=0, + value=current, + offending_increment=None, + ) + + states = [current] + for step_index, increment in enumerate(history, start=1): + current += increment + if current <= 0: + raise PositionChartDomainError( + step_index=step_index, + value=current, + offending_increment=increment, + ) + states.append(current) + return PositivePositionAudit( + initial=initial, + increments=history, + states=tuple(states), + common_domain="x > 0 with the real Log branch", + generator_ids=("A", "M", "P"), + claim_boundary=( + "local chart audit only; it does not make the symmetric Brownian " + "position process positive" + ), + ) + + +@dataclass(frozen=True) +class ObserverProductCost: + atom_products: int + + +@dataclass(frozen=True) +class ReplicaPowerResult: + observer: "ExponentialObserver" + replicas: int + factor_compositions: int + atom_products: int + power_slice: str + residual: str + + +@dataclass(frozen=True) +class ExponentialObserver: + """A finite exact sum of exponential atoms ``E_q(s)``. + + ``terms`` stores ``(q, c_q)`` and denotes ``sum c_q E_q``. Multiplication + uses ``E_p E_q = E_(p+q)`` directly. This is polynomial-like in named + exponential atoms, but it is not an ordinary polynomial or a power series. + """ + + terms: tuple[tuple[Fraction, Fraction], ...] + + def __post_init__(self) -> None: + if not self.terms: + raise BrownianAMPDomainError("an observer must contain at least one atom") + exponents = tuple(exponent for exponent, _ in self.terms) + if exponents != tuple(sorted(exponents)) or len(set(exponents)) != len( + exponents + ): + raise BrownianAMPDomainError( + "observer exponents must be uniquely sorted" + ) + for exponent, coefficient in self.terms: + _require_fraction(exponent, "observer exponent") + _require_fraction(coefficient, "observer coefficient") + if coefficient == 0: + raise BrownianAMPDomainError( + "zero coefficients must be removed from the observer" + ) + + @classmethod + def from_atoms( + cls, + atoms: Iterable[tuple[Fraction, Fraction]], + ) -> "ExponentialObserver": + combined: dict[Fraction, Fraction] = {} + for exponent, coefficient in atoms: + exponent = _require_fraction(exponent, "observer exponent") + coefficient = _require_fraction(coefficient, "observer coefficient") + combined[exponent] = combined.get(exponent, Fraction(0)) + coefficient + terms = tuple( + (exponent, coefficient) + for exponent, coefficient in sorted(combined.items()) + if coefficient != 0 + ) + return cls(terms) + + @classmethod + def constant_one(cls) -> "ExponentialObserver": + return cls(((Fraction(0), Fraction(1)),)) + + @classmethod + def point_mass(cls, value: Fraction) -> "ExponentialObserver": + value = _require_fraction(value, "point-mass value") + return cls(((value, Fraction(1)),)) + + @classmethod + def from_finite_law( + cls, + support: Iterable[int], + weights: Iterable[Fraction], + ) -> "ExponentialObserver": + support = tuple(support) + weights = tuple(weights) + if len(support) != len(weights) or not support: + raise BrownianAMPDomainError("law support and weights must align") + if any(not isinstance(value, int) or isinstance(value, bool) for value in support): + raise BrownianAMPDomainError("law support must contain integers") + for weight in weights: + _require_fraction(weight, "law weight") + if any(weight <= 0 for weight in weights) or sum( + weights, start=Fraction(0) + ) != 1: + raise BrownianAMPDomainError( + "law weights must be positive exact fractions summing to one" + ) + return cls.from_atoms( + (Fraction(value), weight) for value, weight in zip(support, weights) + ) + + @property + def mass(self) -> Fraction: + return sum((coefficient for _, coefficient in self.terms), Fraction(0)) + + def coefficient(self, exponent: Fraction) -> Fraction: + exponent = _require_fraction(exponent, "coefficient exponent") + return dict(self.terms).get(exponent, Fraction(0)) + + def shift_state(self, translation: Fraction) -> "ExponentialObserver": + """Apply A: ``Z_(X+t) = E_t Z_X`` exactly on the atom exponents.""" + + translation = _require_fraction(translation, "translation") + return ExponentialObserver.from_atoms( + (exponent + translation, coefficient) + for exponent, coefficient in self.terms + ) + + def scale_state(self, scale: Fraction) -> "ExponentialObserver": + """Apply positive M: ``Z_(lambda X)(s) = Z_X(lambda s)``.""" + + scale = _require_fraction(scale, "scale") + if scale <= 0: + raise BrownianAMPDomainError("multiplicative scale must be positive") + return ExponentialObserver.from_atoms( + (scale * exponent, coefficient) + for exponent, coefficient in self.terms + ) + + def multiply( + self, + other: "ExponentialObserver", + ) -> tuple["ExponentialObserver", ObserverProductCost]: + """Compose independent laws using ``E_p E_q = E_(p+q)``.""" + + if not isinstance(other, ExponentialObserver): + raise BrownianAMPDomainError("observer multiplication needs two observers") + atoms = ( + (left_exp + right_exp, left_coeff * right_coeff) + for left_exp, left_coeff in self.terms + for right_exp, right_coeff in other.terms + ) + return ( + ExponentialObserver.from_atoms(atoms), + ObserverProductCost(atom_products=len(self.terms) * len(other.terms)), + ) + + def replica_power(self, replicas: int) -> ReplicaPowerResult: + """Apply the exact nonnegative-integer slice of the P action. + + Integer powers describe convolution replicas. The continuous P flow + ``Z -> Z^(exp(r))`` is well-defined pointwise for positive real ``Z``, + but a noninteger power generally leaves this finite atom carrier. That + closure failure is returned explicitly rather than hidden by a series. + """ + + if isinstance(replicas, bool) or not isinstance(replicas, int) or replicas < 0: + raise BrownianAMPDomainError( + "replica power requires a non-negative integer" + ) + result = ExponentialObserver.constant_one() + base = self + remaining = replicas + compositions = 0 + atom_products = 0 + while remaining: + if remaining & 1: + result, cost = result.multiply(base) + compositions += 1 + atom_products += cost.atom_products + remaining >>= 1 + if remaining: + base, cost = base.multiply(base) + compositions += 1 + atom_products += cost.atom_products + return ReplicaPowerResult( + observer=result, + replicas=replicas, + factor_compositions=compositions, + atom_products=atom_products, + power_slice="exact nonnegative-integer replica slice", + residual=( + "noninteger P flow generally exits the finite exponential-atom family" + ), + ) + + +@dataclass(frozen=True) +class PathInformationResidual: + left_history: tuple[Fraction, ...] + right_history: tuple[Fraction, ...] + shared_observer: ExponentialObserver + lost_observer: str + + +def expose_path_information_residual( + left_history: Iterable[Fraction], + right_history: Iterable[Fraction], +) -> PathInformationResidual: + """Exhibit two distinct histories collapsed by the endpoint observer.""" + + left = tuple(left_history) + right = tuple(right_history) + for increment in left + right: + _require_fraction(increment, "history increment") + if left == right: + raise BrownianAMPDomainError("histories must be distinct") + left_endpoint = sum(left, start=Fraction(0)) + right_endpoint = sum(right, start=Fraction(0)) + if left_endpoint != right_endpoint: + raise BrownianAMPDomainError("histories must share an endpoint") + return PathInformationResidual( + left_history=left, + right_history=right, + shared_observer=ExponentialObserver.point_mass(left_endpoint), + lost_observer="running maximum and chronological order", + ) diff --git a/sonnet/brownian-scale-fibre/phase0_contract.py b/sonnet/brownian-scale-fibre/phase0_contract.py index 9d2e9edb..993891c3 100644 --- a/sonnet/brownian-scale-fibre/phase0_contract.py +++ b/sonnet/brownian-scale-fibre/phase0_contract.py @@ -1,4 +1,4 @@ -"""Frozen S0/S1 method contract and executable audit for issue #158.""" +"""Frozen PRE-AMP discrete-control contract and audit for issue #158.""" from __future__ import annotations @@ -29,8 +29,46 @@ def _load(name: str, path: Path): native = _load("brownian_native", Path(__file__).with_name("brownian_native.py")) -METHOD_CONTRACT = firewall.MethodContract( - contract_id="brownian-scale-fibre-s0-s1", +DISCRETE_CONTROL_GRAMMAR = firewall.NativeGrammarProfile( + profile_id="brownian-finite-history-control", + family=firewall.NativeGrammarFamily.DECLARED, + required_generators=("A-step",), + generators=( + firewall.GeneratorWitness( + generator_id="A-step", + finite_action="append one declared lattice increment to a finite history", + infinitesimal_action="not-applicable: the control is a discrete process", + carrier="finite nearest-neighbour histories and endpoint fibres", + domain="bounded integer-lattice histories", + task_role="accumulate increments and push histories to endpoints", + residual="multiplicative and power generators are absent", + certificate="replay the finite chronological history", + ), + ), + legal_compositions=("chronological history concatenation",), + relations=( + firewall.GeneratorRelationWitness( + relation_id="A-step-A-step", + expression="append(u) after append(v) equals append(v,u)", + closure_status=firewall.ClosureStatus.TASK_SCOPED, + residual="order is forgotten by the endpoint quotient", + certificate="literal-history replay before endpoint pushforward", + ), + ), + closure_obligations=( + "retain literal history when the observer is path-sensitive", + ), + domain_and_branches=( + "integer-lattice control has no logarithm or real-power branch", + ), + claim_boundary=( + "PRE-AMP discrete control only; it cannot witness M, P, or AMP brackets" + ), +) + + +DISCRETE_CONTROL_CONTRACT = firewall.MethodContract( + contract_id="brownian-pre-amp-discrete-control-s0-s1", problem=( "derive the first fluctuation scale and endpoint-fibre composition " "from raw finite increment histories" @@ -57,6 +95,7 @@ def _load(name: str, path: Path): "degenerate-second-response", "outside-finite-law-grammar", ), + required_generators=("A-step",), ), firewall.TaskContract( task_id="endpoint-fibre", @@ -68,6 +107,7 @@ def _load(name: str, path: Path): accuracy="exact integer counts and rational probabilities", claim_mode=firewall.ClaimMode.EXACT_FINITE, failure_semantics=("invalid-history", "transition-budget-exhausted"), + required_generators=("A-step",), ), ), native_charts=( @@ -93,9 +133,11 @@ def _load(name: str, path: Path): "history concatenation", ), claim_boundary=( - "S0/S1 only: no continuum limit law, path-space limit, recurrence theorem, " - "physical decoder, efficiency theorem, or new stochastic calculus" + "PRE-AMP discrete S0/S1 control only: no AMP result, continuum limit law, " + "path-space limit, recurrence theorem, physical decoder, efficiency theorem, " + "or new stochastic calculus" ), + native_grammar=DISCRETE_CONTROL_GRAMMAR, allowed_lowerings=(), baselines=( firewall.BaselineSpec( @@ -142,7 +184,7 @@ def run_phase0() -> Phase0Result: if not concatenation_certified: # pragma: no cover - fail closed raise AssertionError("endpoint pushforward did not preserve concatenation") - trace = firewall.MethodTrace(METHOD_CONTRACT) + trace = firewall.MethodTrace(DISCRETE_CONTROL_CONTRACT) scale_event = trace.record( task_id="blind-scale", lane=firewall.MethodLane.NATIVE_DISCOVERY, @@ -150,6 +192,7 @@ def run_phase0() -> Phase0Result: action="derive centered response order and solve population/scale balance", input_semantics="raw symmetric unit increment law; no target exponent", output_semantics="exact scale-balance certificate", + generator_ids=("A-step",), cost=firewall.CostLedger( discovery_steps=scale.cost.exact_weighted_additions, live_state_units=scale.cost.law_atoms, @@ -162,6 +205,7 @@ def run_phase0() -> Phase0Result: action="push chronological histories to exact endpoint fibres", input_semantics="nearest-neighbour step grammar and finite horizon", output_semantics="endpoint counts and rational pushforward law", + generator_ids=("A-step",), cost=firewall.CostLedger( evaluation_steps=endpoint.cost.transition_updates, live_state_units=endpoint.cost.peak_live_fibres, diff --git a/sonnet/brownian-scale-fibre/phase1_amp_contract.py b/sonnet/brownian-scale-fibre/phase1_amp_contract.py new file mode 100644 index 00000000..22081f56 --- /dev/null +++ b/sonnet/brownian-scale-fibre/phase1_amp_contract.py @@ -0,0 +1,356 @@ +"""Executable Brownian AMP generator/chart gate for correction issue #160.""" + +from __future__ import annotations + +from dataclasses import dataclass +from fractions import Fraction +import importlib.util +from pathlib import Path +import sys + + +ROOT = Path(__file__).resolve().parents[2] + + +def _load(name: str, path: Path): + if name in sys.modules: + return sys.modules[name] + spec = importlib.util.spec_from_file_location(name, path) + assert spec and spec.loader + loaded = importlib.util.module_from_spec(spec) + sys.modules[name] = loaded + spec.loader.exec_module(loaded) + return loaded + + +firewall = _load( + "native_method_firewall", + ROOT / "workstreams/native_method_firewall/native_method_firewall.py", +) +discrete = _load("brownian_native", Path(__file__).with_name("brownian_native.py")) +amp = _load("brownian_amp", Path(__file__).with_name("brownian_amp.py")) + + +AMP_GRAMMAR = firewall.NativeGrammarProfile( + profile_id="brownian-positive-real-amp", + family=firewall.NativeGrammarFamily.AMP, + required_generators=("A", "M", "P"), + generators=( + firewall.GeneratorWitness( + generator_id="A", + finite_action="A_t(x)=x+t", + infinitesimal_action="A=d/dx", + carrier="positive real state or positive ensemble observer", + domain="translation must retain the chosen carrier domain", + task_role="state translation and chronological increment accumulation", + residual="a symmetric position history can hit zero or the negative half-line", + certificate="differentiate A_t at t=0 and replay additive composition", + ), + firewall.GeneratorWitness( + generator_id="M", + finite_action="M_s(x)=exp(s)*x", + infinitesimal_action="M=x*d/dx", + carrier="positive real state or exponential-observer exponents", + domain="positive multiplicative scale", + task_role="change physical or observer scale", + residual="zero is a fixed boundary not contained in the positive chart", + certificate="differentiate M_s at s=0 and replay positive-scale composition", + ), + firewall.GeneratorWitness( + generator_id="P", + finite_action="P_r(x)=exp(exp(r)*Log(x))", + infinitesimal_action="P=x*log(x)*d/dx", + carrier="positive real state or pointwise-positive ensemble observer", + domain="x>0 with a declared real Log branch", + task_role="replica power and scale-of-scale transport", + residual=( + "noninteger powers generally exit the finite exponential-atom carrier" + ), + certificate="differentiate P_r at r=0; integer replicas use exact products", + ), + ), + legal_compositions=( + "chronological composition of finite A/M/P flows on their common domain", + "M/P affine subgroup in Log coordinate", + "integer replica composition of independent ensemble observers", + ), + relations=( + firewall.GeneratorRelationWitness( + relation_id="A-M", + expression="[A,M]=A", + closure_status=firewall.ClosureStatus.CLOSES_DECLARED_SPAN, + residual="none inside the A/M subsystem", + certificate="differentiate vector-field coefficients directly", + ), + firewall.GeneratorRelationWitness( + relation_id="M-P", + expression="[M,P]=M", + closure_status=firewall.ClosureStatus.CLOSES_DECLARED_SPAN, + residual="none inside the M/P subsystem", + certificate="differentiate vector-field coefficients directly", + ), + firewall.GeneratorRelationWitness( + relation_id="A-P", + expression="[A,P]=(1+log(x))*d/dx", + closure_status=firewall.ClosureStatus.ESCAPES_DECLARED_SPAN, + residual="new log-weighted vector fields beyond span{A,M,P}", + certificate="compare the coefficient with constants times 1,x,x*log(x)", + ), + ), + closure_obligations=( + "retain the infinite family V_(m,n)=x^m*(log(x))^n*d/dx as a residual", + "do not describe full A/M/P as one three-dimensional Lie group", + ), + domain_and_branches=( + "position chart requires x>0 and fails at zero or negative states", + "ensemble observer Z(s)>0 for real s, but finite atoms are not closed under noninteger P", + ), + claim_boundary=( + "Brownian AMP chart gate only; no continuum Brownian law, recurrence, heat kernel, " + "or complexity advantage is certified" + ), +) + + +AMP_METHOD_CONTRACT = firewall.MethodContract( + contract_id="brownian-amp-generator-chart-gate", + problem=( + "test whether Brownian processes admit an A/M/P-native chart without hiding " + "domain, closure, or path-information residuals" + ), + primitive_processes=( + "finite A/M/P flows on a positive carrier", + "independent composition of finite exponential observers", + ), + tasks=( + firewall.TaskContract( + task_id="position-chart-domain", + observer="common real domain of the A/M/P position flows along a history", + deliverable="positive-chart witness or typed first-exit obstruction", + regime="finite exact rational position histories", + accuracy="exact order and sign comparison", + claim_mode=firewall.ClaimMode.EXACT_FINITE, + failure_semantics=("zero-crossing", "negative-state", "inexact-input"), + required_generators=("A", "M", "P"), + ), + firewall.TaskContract( + task_id="ensemble-amp-adapter", + observer="finite exact exponential observer Z(s)=E[exp(sX)]", + deliverable="exact A shift, M scale, integer-P replica, and endpoint-fibre certificate", + regime="finite integer laws, rational shifts/scales, integer replicas", + accuracy="exact rational atom coefficients", + claim_mode=firewall.ClaimMode.EXACT_SYMBOLIC, + failure_semantics=( + "nonpositive-scale", + "noninteger-replica", + "finite-carrier-closure-failure", + ), + required_generators=("A", "M", "P"), + ), + firewall.TaskContract( + task_id="path-information-residual", + observer="collision of distinct histories under the endpoint observer", + deliverable="explicit lost path observable", + regime="finite rational increment histories with a shared endpoint", + accuracy="exact history and endpoint equality", + claim_mode=firewall.ClaimMode.EXACT_FINITE, + failure_semantics=("different-endpoints", "identical-histories"), + required_generators=("A",), + ), + ), + native_charts=( + "positive position chart x>0 with real Log", + "positive ensemble observer Z(s) with finite exponential atoms E_q", + ), + retained_fibres=( + "first position-chart exit", + "noninteger-P finite-carrier closure residual", + "literal path information lost by the endpoint observer", + ), + native_function_family=( + "finite exact rational sums of exponential atoms E_q with E_p*E_q=E_(p+q)" + ), + native_composition=( + "finite A/M/P action plus exact independent-law product in the ensemble chart" + ), + native_operators=( + "state translation", + "positive state scaling", + "integer replica power", + "typed common-domain audit", + ), + claim_boundary=( + "chart admissibility and exact finite adapter identities only; the PRE-AMP " + "scale result is not promoted to an AMP Brownian theorem" + ), + native_grammar=AMP_GRAMMAR, + allowed_lowerings=(), + baselines=(), +).validate() + + +@dataclass(frozen=True) +class PositionObstructionCertificate: + step_index: int + value: Fraction + offending_increment: Fraction | None + typed_reason: str + + +@dataclass(frozen=True) +class EnsembleAMPCertificate: + base: object + shifted: object + scaled: object + replica: object + shift_inverse_certified: bool + scale_inverse_certified: bool + endpoint_fibre_certified: bool + noninteger_power_residual: str + + +@dataclass(frozen=True) +class AMPGateResult: + positive_history: object + position_obstruction: PositionObstructionCertificate + ensemble: EnsembleAMPCertificate + path_residual: object + trace: object + + +def run_amp_gate() -> AMPGateResult: + positive_history = amp.audit_positive_position_history( + Fraction(2), + (Fraction(1), Fraction(-1)), + ) + try: + amp.audit_positive_position_history( + Fraction(1), + (Fraction(-1), Fraction(1)), + ) + except amp.PositionChartDomainError as error: + obstruction = PositionObstructionCertificate( + step_index=error.step_index, + value=error.value, + offending_increment=error.offending_increment, + typed_reason=str(error), + ) + else: # pragma: no cover - the exact zero crossing must fail closed + raise AssertionError("symmetric history unexpectedly stayed in x > 0") + + law = discrete.FiniteIncrementLaw.symmetric_unit() + base = amp.ExponentialObserver.from_finite_law(law.support, law.weights) + translation = Fraction(2) + scale = Fraction(3) + shifted = base.shift_state(translation) + scaled = base.scale_state(scale) + replica = base.replica_power(5) + shift_inverse = shifted.shift_state(-translation) == base + scale_inverse = scaled.scale_state(Fraction(1, 3)) == base + + endpoint = discrete.endpoint_fibres(1, 5) + endpoints = {Fraction(point[0]) for point, _ in endpoint.counts} + observer_endpoints = {exponent for exponent, _ in replica.observer.terms} + endpoint_certified = endpoints == observer_endpoints and all( + replica.observer.coefficient(Fraction(point[0])) + == endpoint.probability(point) + for point, _ in endpoint.counts + ) + if not endpoint_certified: # pragma: no cover - exact identity must fail closed + raise AssertionError("replica power did not reproduce endpoint fibres") + ensemble = EnsembleAMPCertificate( + base=base, + shifted=shifted, + scaled=scaled, + replica=replica, + shift_inverse_certified=shift_inverse, + scale_inverse_certified=scale_inverse, + endpoint_fibre_certified=endpoint_certified, + noninteger_power_residual=replica.residual, + ) + + path_residual = amp.expose_path_information_residual( + (Fraction(1), Fraction(-1)), + (Fraction(-1), Fraction(1)), + ) + + trace = firewall.MethodTrace(AMP_METHOD_CONTRACT) + position_event = trace.record( + task_id="position-chart-domain", + lane=firewall.MethodLane.NATIVE_DISCOVERY, + mechanism=firewall.MethodMechanism.TASK_FIBRE, + action="audit the common positive domain along additive histories", + input_semantics="exact rational initial state and chronological A increments", + output_semantics="positive witness plus typed zero-crossing obstruction", + generator_ids=("A", "M", "P"), + cost=firewall.CostLedger( + discovery_steps=len(positive_history.increments) + obstruction.step_index, + live_state_units=1, + residual_units=1, + ), + ) + ensemble_event = trace.record( + task_id="ensemble-amp-adapter", + lane=firewall.MethodLane.NATIVE_EVALUATION, + mechanism=firewall.MethodMechanism.NATIVE_FUNCTION_FAMILY, + action="apply exact A shift, M scale, and integer P replica composition", + input_semantics="finite exact exponential observer with rational atoms", + output_semantics="exact transformed atoms and explicit noninteger-P residual", + generator_ids=("A", "M", "P"), + cost=firewall.CostLedger( + evaluation_steps=replica.atom_products, + live_state_units=len(replica.observer.terms), + residual_units=1, + ), + ) + certificate_event = trace.record( + task_id="ensemble-amp-adapter", + lane=firewall.MethodLane.CERTIFICATE, + mechanism=firewall.MethodMechanism.EXACT_FINITE_ENUMERATION, + action="compare replica atom coefficients with PRE-AMP endpoint fibres", + input_semantics="two independently constructed exact rational laws", + output_semantics="zero coefficient residual at every endpoint", + cost=firewall.CostLedger( + evaluation_steps=len(endpoint.counts), + stored_history_units=endpoint.support_size, + ), + ) + path_event = trace.record( + task_id="path-information-residual", + lane=firewall.MethodLane.NATIVE_EVALUATION, + mechanism=firewall.MethodMechanism.TASK_FIBRE, + action="compare two chronological histories under the endpoint observer", + input_semantics="distinct A histories with an equal aggregate", + output_semantics="shared observer and named lost path information", + generator_ids=("A",), + cost=firewall.CostLedger( + evaluation_steps=len(path_residual.left_history) + + len(path_residual.right_history), + residual_units=1, + ), + ) + trace.claim_native_result( + task_id="position-chart-domain", + statement="the real positive position AMP chart is not global for symmetric histories", + evidence_event_ids=(position_event.event_id,), + ) + trace.claim_native_result( + task_id="ensemble-amp-adapter", + statement=( + "A shift, M scale, and integer-P replicas are exact in the finite " + "ensemble carrier within its declared closure boundary" + ), + evidence_event_ids=(ensemble_event.event_id, certificate_event.event_id), + ) + trace.claim_native_result( + task_id="path-information-residual", + statement="the endpoint ensemble observer does not retain chronological paths", + evidence_event_ids=(path_event.event_id,), + ) + return AMPGateResult( + positive_history=positive_history, + position_obstruction=obstruction, + ensemble=ensemble, + path_residual=path_residual, + trace=trace, + ) diff --git a/tests/research/test_brownian_amp_generator_gate.py b/tests/research/test_brownian_amp_generator_gate.py new file mode 100644 index 00000000..ff4ad35b --- /dev/null +++ b/tests/research/test_brownian_amp_generator_gate.py @@ -0,0 +1,175 @@ +"""Brownian A/M/P generator and competing-chart gate for correction #160.""" + +from __future__ import annotations + +import ast +from fractions import Fraction +import importlib.util +import json +from pathlib import Path +import sys + +import pytest + + +MODULE_PATH = ( + Path(__file__).parents[2] + / "sonnet/brownian-scale-fibre/phase1_amp_contract.py" +) +SPEC = importlib.util.spec_from_file_location("brownian_phase1_amp_contract", MODULE_PATH) +assert SPEC and SPEC.loader +module = importlib.util.module_from_spec(SPEC) +sys.modules[SPEC.name] = module +SPEC.loader.exec_module(module) +amp = module.amp +firewall = module.firewall + + +def test_amp_contract_requires_three_generators_and_exposes_closure_escape() -> None: + grammar = module.AMP_METHOD_CONTRACT.native_grammar + assert grammar.family is firewall.NativeGrammarFamily.AMP + assert grammar.generator_ids == {"A", "M", "P"} + relations = {relation.relation_id: relation for relation in grammar.relations} + assert relations["A-M"].expression == "[A,M]=A" + assert relations["M-P"].expression == "[M,P]=M" + assert relations["A-P"].expression == "[A,P]=(1+log(x))*d/dx" + assert ( + relations["A-P"].closure_status + is firewall.ClosureStatus.ESCAPES_DECLARED_SPAN + ) + assert "infinite family" in grammar.closure_obligations[0] + + +def test_positive_position_chart_has_a_typed_zero_crossing_obstruction() -> None: + audit = amp.audit_positive_position_history( + Fraction(2), + (Fraction(1), Fraction(-1)), + ) + assert audit.states == (Fraction(2), Fraction(3), Fraction(2)) + assert audit.generator_ids == ("A", "M", "P") + + with pytest.raises( + amp.PositionChartDomainError, + match="positive-position-chart-obstruction", + ) as error: + amp.audit_positive_position_history( + Fraction(1), + (Fraction(-1), Fraction(1)), + ) + assert error.value.step_index == 1 + assert error.value.value == 0 + assert error.value.offending_increment == -1 + + +@pytest.mark.parametrize("initial", (Fraction(0), Fraction(-1))) +def test_positive_position_chart_rejects_nonpositive_initial_states( + initial: Fraction, +) -> None: + with pytest.raises(amp.PositionChartDomainError) as error: + amp.audit_positive_position_history(initial, ()) + assert error.value.step_index == 0 + + +def test_exponential_observer_realizes_exact_a_and_m_actions() -> None: + observer = amp.ExponentialObserver.from_finite_law( + (-1, 1), + (Fraction(1, 2), Fraction(1, 2)), + ) + assert observer.shift_state(Fraction(2)).terms == ( + (Fraction(1), Fraction(1, 2)), + (Fraction(3), Fraction(1, 2)), + ) + assert observer.scale_state(Fraction(3)).terms == ( + (Fraction(-3), Fraction(1, 2)), + (Fraction(3), Fraction(1, 2)), + ) + assert observer.shift_state(Fraction(2)).shift_state(Fraction(-2)) == observer + assert observer.scale_state(Fraction(3)).scale_state(Fraction(1, 3)) == observer + + +def test_integer_p_slice_reproduces_the_endpoint_fibre_law_exactly() -> None: + observer = amp.ExponentialObserver.from_finite_law( + (-1, 1), + (Fraction(1, 2), Fraction(1, 2)), + ) + replica = observer.replica_power(5) + assert replica.observer.terms == ( + (Fraction(-5), Fraction(1, 32)), + (Fraction(-3), Fraction(5, 32)), + (Fraction(-1), Fraction(5, 16)), + (Fraction(1), Fraction(5, 16)), + (Fraction(3), Fraction(5, 32)), + (Fraction(5), Fraction(1, 32)), + ) + assert replica.observer.mass == 1 + assert "noninteger P" in replica.residual + with pytest.raises(amp.BrownianAMPDomainError, match="non-negative integer"): + observer.replica_power(Fraction(1, 2)) + + +def test_endpoint_observer_preserves_an_explicit_path_residual() -> None: + residual = amp.expose_path_information_residual( + (Fraction(1), Fraction(-1)), + (Fraction(-1), Fraction(1)), + ) + assert residual.shared_observer == amp.ExponentialObserver.point_mass(Fraction(0)) + assert "running maximum" in residual.lost_observer + + +def test_amp_gate_is_exact_and_machine_auditable() -> None: + result = module.run_amp_gate() + report = json.loads(result.trace.to_json()) + assert result.position_obstruction.step_index == 1 + assert result.ensemble.shift_inverse_certified + assert result.ensemble.scale_inverse_certified + assert result.ensemble.endpoint_fibre_certified + assert report["contract"]["native_grammar"]["family"] == "amp" + assert report["contract"]["native_grammar"]["required_generators"] == [ + "A", + "M", + "P", + ] + assert len(report["native_claims"]) == 3 + ensemble_event = next( + event + for event in report["events"] + if event["task_id"] == "ensemble-amp-adapter" + and event["lane"] == "native-evaluation" + ) + assert ensemble_event["generator_ids"] == ["A", "M", "P"] + assert report["summary"]["cost_scalarization"] == "not-authorized" + + +def test_amp_claim_cannot_drop_p_even_if_the_contract_declares_it() -> None: + trace = firewall.MethodTrace(module.AMP_METHOD_CONTRACT) + event = trace.record( + task_id="ensemble-amp-adapter", + lane=firewall.MethodLane.NATIVE_EVALUATION, + mechanism=firewall.MethodMechanism.NATIVE_PROCESS, + action="incomplete two-generator adapter", + input_semantics="positive ensemble observer", + output_semantics="A/M result with no P witness", + generator_ids=("A", "M"), + ) + with pytest.raises(firewall.EvidenceLaneError, match="missing.*P"): + trace.claim_native_result( + task_id="ensemble-amp-adapter", + statement="this must not be accepted as an AMP result", + evidence_event_ids=(event.event_id,), + ) + + +def test_amp_engine_imports_no_matrix_series_or_transform_package() -> None: + source_path = MODULE_PATH.with_name("brownian_amp.py") + tree = ast.parse(source_path.read_text(encoding="utf-8")) + imported_roots = { + alias.name.split(".")[0] + for node in ast.walk(tree) + if isinstance(node, (ast.Import, ast.ImportFrom)) + for alias in node.names + } + assert not imported_roots & {"numpy", "scipy", "sympy"} + source = source_path.read_text(encoding="utf-8") + assert ".series(" not in source + assert ".fourier_transform(" not in source + assert ".as_matrix(" not in source diff --git a/tests/research/test_brownian_scale_fibre_phase0.py b/tests/research/test_brownian_scale_fibre_phase0.py index 912a9a83..c6aa7c5f 100644 --- a/tests/research/test_brownian_scale_fibre_phase0.py +++ b/tests/research/test_brownian_scale_fibre_phase0.py @@ -1,4 +1,4 @@ -"""S0/S1 Brownian scale and endpoint-fibre certificates for issue #158.""" +"""PRE-AMP discrete Brownian controls for issue #158 and correction #160.""" from __future__ import annotations @@ -23,9 +23,12 @@ firewall = module.firewall -def test_method_contract_has_no_s0_s1_lowering_escape_hatch() -> None: - contract = module.METHOD_CONTRACT - assert contract.contract_id == "brownian-scale-fibre-s0-s1" +def test_discrete_control_is_explicitly_not_an_amp_contract() -> None: + contract = module.DISCRETE_CONTROL_CONTRACT + assert contract.contract_id == "brownian-pre-amp-discrete-control-s0-s1" + assert contract.native_grammar.family is firewall.NativeGrammarFamily.DECLARED + assert contract.native_grammar.generator_ids == {"A-step"} + assert "PRE-AMP" in contract.native_grammar.claim_boundary assert contract.allowed_lowerings == () assert {task.task_id for task in contract.tasks} == { "blind-scale", @@ -169,7 +172,7 @@ def test_phase0_trace_is_machine_readable_and_lane_separated() -> None: def test_phase0_firewall_rejects_a_classical_discovery_oracle() -> None: - trace = firewall.MethodTrace(module.METHOD_CONTRACT) + trace = firewall.MethodTrace(module.DISCRETE_CONTROL_CONTRACT) with pytest.raises(firewall.PrematureLoweringError): trace.record( task_id="blind-scale", @@ -178,4 +181,5 @@ def test_phase0_firewall_rejects_a_classical_discovery_oracle() -> None: action="supply a target scale from a classical transform", input_semantics="raw increment law", output_semantics="answer-shaped exponent", + generator_ids=("A-step",), ) diff --git a/tests/research/test_native_method_firewall.py b/tests/research/test_native_method_firewall.py index 47fd9e2d..fe02f430 100644 --- a/tests/research/test_native_method_firewall.py +++ b/tests/research/test_native_method_firewall.py @@ -31,6 +31,7 @@ def contract(*, with_lowering: bool = False) -> object: accuracy="caller tolerance for the analytic tail", claim_mode=module.ClaimMode.CERTIFIED_APPROXIMATE, failure_semantics=("outside-domain", "budget-exhausted"), + required_generators=("Q",), ) lowering = () if with_lowering: @@ -62,6 +63,36 @@ def contract(*, with_lowering: bool = False) -> object: native_composition="chronological inverse-state recurrence", native_operators=("raise to degree", "divide by 1+t*q^d"), claim_boundary="one scalar task; no generic AMP solver or complexity theorem", + native_grammar=module.NativeGrammarProfile( + profile_id="inverse-recurrence-grammar", + family=module.NativeGrammarFamily.DECLARED, + required_generators=("Q",), + generators=( + module.GeneratorWitness( + generator_id="Q", + finite_action="one inverse-state recurrence update", + infinitesimal_action="not-applicable: finite declared recurrence", + carrier="inverse state q", + domain="q in [0, 1]", + task_role="evaluate the escape observer", + residual="tail after the finite recurrence budget", + certificate="replay the exact recurrence", + ), + ), + legal_compositions=("chronological recurrence composition",), + relations=( + module.GeneratorRelationWitness( + relation_id="Q-Q", + expression="Q after Q is chronological recurrence composition", + closure_status=module.ClosureStatus.TASK_SCOPED, + residual="the full history is not reconstructed", + certificate="direct two-step replay", + ), + ), + closure_obligations=("retain the recurrence tail residual",), + domain_and_branches=("real inverse-state chart q in [0, 1]",), + claim_boundary="declared recurrence grammar, not an AMP grammar", + ), allowed_lowerings=lowering, baselines=( module.BaselineSpec( @@ -88,15 +119,110 @@ def event_kwargs() -> dict[str, object]: "action": "evaluate the declared recurrence", "input_semantics": "inverse state and tolerance", "output_semantics": "escape value and tail residual", + "generator_ids": ("Q",), } +def amp_grammar(*, generator_ids: tuple[str, ...] = ("A", "M", "P")) -> object: + generators = tuple( + module.GeneratorWitness( + generator_id=generator_id, + finite_action=f"finite {generator_id} flow", + infinitesimal_action=f"infinitesimal {generator_id} action", + carrier="positive state chart", + domain="x > 0 with declared branch", + task_role=f"{generator_id} process role", + residual=f"{generator_id} domain residual", + certificate=f"differentiate the {generator_id} flow at zero", + ) + for generator_id in generator_ids + ) + relation_ids = ("A-M", "M-P", "A-P") if "P" in generator_ids else ("A-M",) + relations = tuple( + module.GeneratorRelationWitness( + relation_id=relation_id, + expression=f"bracket {relation_id}", + closure_status=( + module.ClosureStatus.ESCAPES_DECLARED_SPAN + if relation_id == "A-P" + else module.ClosureStatus.CLOSES_DECLARED_SPAN + ), + residual=f"{relation_id} residual", + certificate=f"direct coefficient differentiation for {relation_id}", + ) + for relation_id in relation_ids + ) + return module.NativeGrammarProfile( + profile_id="amp-positive-chart", + family=module.NativeGrammarFamily.AMP, + required_generators=generator_ids, + generators=generators, + legal_compositions=("chronological finite-flow composition",), + relations=relations, + closure_obligations=("retain brackets outside the declared span",), + domain_and_branches=("positive real chart with a fixed real logarithm",), + claim_boundary="local positive AMP chart only", + ) + + def test_incomplete_contract_fails_before_a_trace_exists() -> None: incomplete = replace(contract(), primitive_processes=()) with pytest.raises(module.MethodContractError, match="primitive_processes"): module.MethodTrace(incomplete) +def test_amp_contract_fails_when_one_generator_is_missing() -> None: + incomplete = replace(contract(), native_grammar=amp_grammar(generator_ids=("A", "M"))) + with pytest.raises(module.MethodContractError, match="exactly the A, M, and P"): + module.MethodTrace(incomplete) + + +def test_amp_contract_fails_when_ap_escape_is_hidden() -> None: + grammar = amp_grammar() + hidden = replace( + grammar.relations[-1], + closure_status=module.ClosureStatus.CLOSES_DECLARED_SPAN, + ) + incomplete = replace( + contract(), + native_grammar=replace( + grammar, + relations=grammar.relations[:-1] + (hidden,), + ), + ) + with pytest.raises(module.MethodContractError, match="must expose escape"): + module.MethodTrace(incomplete) + + +def test_amp_contract_fails_when_one_pair_relation_is_missing() -> None: + grammar = amp_grammar() + incomplete = replace( + contract(), + native_grammar=replace(grammar, relations=grammar.relations[:-1]), + ) + with pytest.raises(module.MethodContractError, match="requires exactly"): + module.MethodTrace(incomplete) + + +def test_amp_native_claim_requires_task_generator_coverage() -> None: + task = replace(contract().tasks[0], required_generators=("A", "M", "P")) + trace = module.MethodTrace( + replace(contract(), tasks=(task,), native_grammar=amp_grammar()) + ) + event = trace.record( + **{key: value for key, value in event_kwargs().items() if key != "generator_ids"}, + lane=module.MethodLane.NATIVE_EVALUATION, + mechanism=module.MethodMechanism.NATIVE_PROCESS, + generator_ids=("A", "M"), + ) + with pytest.raises(module.EvidenceLaneError, match="missing.*P"): + trace.claim_native_result( + task_id="escape-value", + statement="incomplete AMP result", + evidence_event_ids=(event.event_id,), + ) + + @pytest.mark.parametrize( "mechanism", sorted(module.CLASSICAL_MECHANISMS, key=lambda item: item.value), diff --git a/workstreams/native_method_firewall/README.md b/workstreams/native_method_firewall/README.md index e191cafb..054edb98 100644 --- a/workstreams/native_method_firewall/README.md +++ b/workstreams/native_method_firewall/README.md @@ -13,6 +13,18 @@ certificate exact or independent checks of a declared claim baseline conventional competing method and red team ``` +It now also makes the native process grammar executable. A +`NativeGrammarProfile` declares finite and infinitesimal generator actions, +their carrier and domain, legal compositions, relation witnesses, closure +obligations, branches, and residuals. Native trace events must name the +generators they actually use. A task can require a generator set, and a +native claim fails closed if its evidence does not cover that set. + +For `family="amp"` the profile must contain exactly `A`, `M`, and `P`, exactly +the `A-M`, `M-P`, and `A-P` relations, and an explicit witness that `A-P` +escapes the three-generator span. Thus an AMP label cannot be obtained from +an additive discrete process with passive scale and population parameters. + Taylor/power series, ordinary polynomial algebra, matrix linearization, Fourier/spectral methods, Koopman/Carleman lifting, generic CAS calls, and black-box numerics are not prohibited. They may appear freely in a declared @@ -37,7 +49,8 @@ Claim mode: Exact finite validation of the declared record, not proof of source honesty. Failure semantics: Incomplete contract, unknown mechanism/lane/task, premature lowering, - undeclared baseline, or evidence-lane mismatch all fail closed. + undeclared baseline, missing generator/relation/closure evidence, or + evidence-lane mismatch all fail closed. Cost: Multi-axis integer counts; scalarization is explicitly unauthorized. Baseline: @@ -55,9 +68,14 @@ This is a trace and contract checker, not a Python sandbox, theorem prover, source-code classifier, solver, or universal calculus. A dishonest caller can mislabel an algorithm as `native-process`; correctness still requires mathematical review, executable certificates, and independent baselines. The -typed mechanism vocabulary closes accidental aliasing, not adversarial code -execution. +typed mechanism and generator vocabularies close accidental aliasing, not +adversarial code execution. In particular, a string-valued generator witness +does not prove a bracket identity; it makes the identity an auditable +obligation that downstream certificates must test. -The Brownian scale/fibre Sonnet is the first intended downstream consumer. It -will declare Gaussian, heat-kernel, Fourier, and PDE machinery as lowering or -baseline evidence rather than supplying them to native scale discovery. +The Brownian scale/fibre Sonnet is the first downstream correction test. Its +merged S0/S1 calculation is now typed as a `PRE-AMP` discrete control with only +`A-step`. The next gate separately declares all A/M/P flows, their brackets, +the positive-position branch obstruction, and an exact finite ensemble +adapter. Gaussian, heat-kernel, Fourier, and PDE machinery remains outside +native discovery. diff --git a/workstreams/native_method_firewall/native_method_firewall.py b/workstreams/native_method_firewall/native_method_firewall.py index bdeef61d..d8891b03 100644 --- a/workstreams/native_method_firewall/native_method_firewall.py +++ b/workstreams/native_method_firewall/native_method_firewall.py @@ -74,6 +74,23 @@ class ClaimMode(str, Enum): SEARCH_ONLY = "search-only" +class NativeGrammarFamily(str, Enum): + """The process grammar whose generators make a result native.""" + + DECLARED = "declared" + AMP = "amp" + + +class ClosureStatus(str, Enum): + CLOSES_DECLARED_SPAN = "closes-declared-span" + ESCAPES_DECLARED_SPAN = "escapes-declared-span" + TASK_SCOPED = "task-scoped" + + +AMP_GENERATOR_IDS = frozenset({"A", "M", "P"}) +AMP_RELATION_IDS = frozenset({"A-M", "M-P", "A-P"}) + + def _require_text(value: str, field_name: str) -> None: if not isinstance(value, str) or not value.strip(): raise MethodContractError(f"{field_name} must be non-empty text") @@ -103,12 +120,17 @@ class TaskContract: accuracy: str claim_mode: ClaimMode failure_semantics: tuple[str, ...] + required_generators: tuple[str, ...] = () def validate(self) -> None: for name in ("task_id", "observer", "deliverable", "regime", "accuracy"): _require_text(getattr(self, name), f"task.{name}") _enum_value(self.claim_mode, ClaimMode, "claim mode") _require_texts(self.failure_semantics, "task.failure_semantics") + if self.required_generators: + _require_texts(self.required_generators, "task.required_generators") + if len(set(self.required_generators)) != len(self.required_generators): + raise MethodContractError("task required generator ids must be unique") def as_dict(self) -> dict[str, object]: return { @@ -119,6 +141,137 @@ def as_dict(self) -> dict[str, object]: "accuracy": self.accuracy, "claim_mode": ClaimMode(self.claim_mode).value, "failure_semantics": list(self.failure_semantics), + "required_generators": list(self.required_generators), + } + + +@dataclass(frozen=True) +class GeneratorWitness: + generator_id: str + finite_action: str + infinitesimal_action: str + carrier: str + domain: str + task_role: str + residual: str + certificate: str + + def validate(self) -> None: + for name in self.__dataclass_fields__: + _require_text(getattr(self, name), f"generator.{name}") + + def as_dict(self) -> dict[str, str]: + return {name: getattr(self, name) for name in self.__dataclass_fields__} + + +@dataclass(frozen=True) +class GeneratorRelationWitness: + relation_id: str + expression: str + closure_status: ClosureStatus + residual: str + certificate: str + + def validate(self) -> None: + for name in ("relation_id", "expression", "residual", "certificate"): + _require_text(getattr(self, name), f"relation.{name}") + _enum_value(self.closure_status, ClosureStatus, "closure status") + + def as_dict(self) -> dict[str, str]: + return { + "relation_id": self.relation_id, + "expression": self.expression, + "closure_status": ClosureStatus(self.closure_status).value, + "residual": self.residual, + "certificate": self.certificate, + } + + +@dataclass(frozen=True) +class NativeGrammarProfile: + profile_id: str + family: NativeGrammarFamily + required_generators: tuple[str, ...] + generators: tuple[GeneratorWitness, ...] + legal_compositions: tuple[str, ...] + relations: tuple[GeneratorRelationWitness, ...] + closure_obligations: tuple[str, ...] + domain_and_branches: tuple[str, ...] + claim_boundary: str + + def validate(self) -> None: + _require_text(self.profile_id, "grammar.profile_id") + family = _enum_value(self.family, NativeGrammarFamily, "grammar family") + _require_texts(self.required_generators, "grammar.required_generators") + _require_texts(self.legal_compositions, "grammar.legal_compositions") + _require_texts(self.closure_obligations, "grammar.closure_obligations") + _require_texts(self.domain_and_branches, "grammar.domain_and_branches") + _require_text(self.claim_boundary, "grammar.claim_boundary") + + if len(set(self.required_generators)) != len(self.required_generators): + raise MethodContractError("grammar required generator ids must be unique") + if not self.generators: + raise MethodContractError("grammar.generators must not be empty") + for generator in self.generators: + generator.validate() + generator_ids = tuple(item.generator_id for item in self.generators) + if len(set(generator_ids)) != len(generator_ids): + raise MethodContractError("generator witness ids must be unique") + if set(generator_ids) != set(self.required_generators): + raise MethodContractError( + "generator witnesses must exactly cover required_generators" + ) + + if not self.relations: + raise MethodContractError("grammar.relations must not be empty") + for relation in self.relations: + relation.validate() + relation_ids = tuple(item.relation_id for item in self.relations) + if len(set(relation_ids)) != len(relation_ids): + raise MethodContractError("generator relation ids must be unique") + + if family is NativeGrammarFamily.AMP: + if set(self.required_generators) != AMP_GENERATOR_IDS: + raise MethodContractError( + "AMP grammar requires exactly the A, M, and P generators" + ) + if set(relation_ids) != AMP_RELATION_IDS: + raise MethodContractError( + "AMP grammar requires exactly A-M, M-P, and A-P relations" + ) + relation_map = {item.relation_id: item for item in self.relations} + for relation_id in ("A-M", "M-P"): + if ( + ClosureStatus(relation_map[relation_id].closure_status) + is not ClosureStatus.CLOSES_DECLARED_SPAN + ): + raise MethodContractError( + f"AMP {relation_id} relation must close the declared span" + ) + if ( + ClosureStatus(relation_map["A-P"].closure_status) + is not ClosureStatus.ESCAPES_DECLARED_SPAN + ): + raise MethodContractError( + "AMP A-P relation must expose escape from the three-generator span" + ) + + @property + def generator_ids(self) -> frozenset[str]: + return frozenset(self.required_generators) + + def as_dict(self) -> dict[str, object]: + self.validate() + return { + "profile_id": self.profile_id, + "family": NativeGrammarFamily(self.family).value, + "required_generators": list(self.required_generators), + "generators": [item.as_dict() for item in self.generators], + "legal_compositions": list(self.legal_compositions), + "relations": [item.as_dict() for item in self.relations], + "closure_obligations": list(self.closure_obligations), + "domain_and_branches": list(self.domain_and_branches), + "claim_boundary": self.claim_boundary, } @@ -268,6 +421,7 @@ class MethodContract: native_composition: str native_operators: tuple[str, ...] claim_boundary: str + native_grammar: NativeGrammarProfile forbidden_premature_lowerings: frozenset[MethodMechanism] = field( default_factory=lambda: CLASSICAL_MECHANISMS ) @@ -287,6 +441,7 @@ def validate(self) -> "MethodContract": _require_texts(self.native_charts, "native_charts") _require_texts(self.retained_fibres, "retained_fibres") _require_texts(self.native_operators, "native_operators") + self.native_grammar.validate() if not self.tasks: raise MethodContractError("tasks must not be empty") for task in self.tasks: @@ -295,6 +450,14 @@ def validate(self) -> "MethodContract": if len(set(task_ids)) != len(task_ids): raise MethodContractError("task ids must be unique") known_tasks = frozenset(task_ids) + grammar_generators = self.native_grammar.generator_ids + for task in self.tasks: + unknown_generators = set(task.required_generators) - grammar_generators + if unknown_generators: + raise MethodContractError( + f"task {task.task_id!r} has unknown required generators: " + f"{sorted(unknown_generators)!r}" + ) forbidden = { _enum_value(mechanism, MethodMechanism, "mechanism") @@ -336,6 +499,12 @@ def lowering(self, witness_id: str) -> LoweringWitness: return witness raise PrematureLoweringError(f"undeclared lowering witness: {witness_id!r}") + def task(self, task_id: str) -> TaskContract: + for task in self.tasks: + if task.task_id == task_id: + return task + raise MethodContractError(f"unknown task: {task_id!r}") + def baseline_for( self, task_id: str, mechanism: MethodMechanism, baseline_id: str ) -> BaselineSpec: @@ -365,6 +534,7 @@ def as_dict(self) -> dict[str, object]: "native_composition": self.native_composition, "native_operators": list(self.native_operators), "claim_boundary": self.claim_boundary, + "native_grammar": self.native_grammar.as_dict(), "forbidden_premature_lowerings": sorted( MethodMechanism(mechanism).value for mechanism in self.forbidden_premature_lowerings @@ -386,6 +556,7 @@ class MethodEvent: input_semantics: str output_semantics: str cost: CostLedger + generator_ids: tuple[str, ...] = () lowering_id: str | None = None baseline_id: str | None = None @@ -399,6 +570,7 @@ def as_dict(self) -> dict[str, object]: "input_semantics": self.input_semantics, "output_semantics": self.output_semantics, "cost": self.cost.as_dict(), + "generator_ids": list(self.generator_ids), "lowering_id": self.lowering_id, "baseline_id": self.baseline_id, } @@ -446,6 +618,7 @@ def record( input_semantics: str, output_semantics: str, cost: CostLedger = CostLedger(), + generator_ids: tuple[str, ...] = (), lowering_id: str | None = None, baseline_id: str | None = None, ) -> MethodEvent: @@ -464,6 +637,26 @@ def record( MethodLane.NATIVE_DISCOVERY, MethodLane.NATIVE_EVALUATION, } + if lane in native_lanes: + _require_texts(generator_ids, "event.generator_ids") + if len(set(generator_ids)) != len(generator_ids): + raise MethodContractError("event generator ids must be unique") + unknown_generators = ( + set(generator_ids) - self.contract.native_grammar.generator_ids + ) + if unknown_generators: + raise MethodContractError( + f"event has unknown generators: {sorted(unknown_generators)!r}" + ) + elif generator_ids: + _require_texts(generator_ids, "event.generator_ids") + unknown_generators = ( + set(generator_ids) - self.contract.native_grammar.generator_ids + ) + if unknown_generators: + raise MethodContractError( + f"event has unknown generators: {sorted(unknown_generators)!r}" + ) if lane in native_lanes and mechanism in CLASSICAL_MECHANISMS: if lowering_id is None: raise PrematureLoweringError( @@ -509,6 +702,7 @@ def record( input_semantics=input_semantics, output_semantics=output_semantics, cost=cost, + generator_ids=tuple(generator_ids), lowering_id=lowering_id, baseline_id=baseline_id, ) @@ -556,6 +750,21 @@ def claim_native_result( "certificate-only evidence cannot establish a native result" ) + required_generators = set(self.contract.task(task_id).required_generators) + witnessed_generators = { + generator_id + for event in selected + if event.lane + in {MethodLane.NATIVE_DISCOVERY, MethodLane.NATIVE_EVALUATION} + for generator_id in event.generator_ids + } + missing_generators = required_generators - witnessed_generators + if missing_generators: + raise EvidenceLaneError( + "native claim is missing required generator evidence: " + f"{sorted(missing_generators)!r}" + ) + claim = NativeClaim( claim_id=len(self._claims), task_id=task_id,