Skip to content

Commit 9a1a908

Browse files
theory: EchoApprox deeper rung — separated zero-collapse + axis-1 shadow (#74)
## Summary Picks up two of three §7 deferrals from PR #70 in `/tmp/echo-types-exploration/axis2-approximate.md`. Refs not Closes because §7 is multi-rung and one rung (B-component + tolerance-budget retract round-trip) stays deferred pending a `Tolerance` interface design call (see below). ## Rungs landed ### Rung A — separated zero-collapse (§7 #7) - `Separated : Set` — separation predicate on the pseudo-metric: `∀ b₁ b₂ → dist b₁ b₂ ≤ zero → b₁ ≡ b₂`. Kept out of the `PseudoMetric` record; supplied as an explicit hypothesis at the lemma site (pseudo-metrics in general only guarantee `dist y y ≡ zero`, not the converse — making them a *metric proper* is an optional refinement the record deliberately does not bake in). - `echo-approx-zero-collapses-strict : Separated → EchoR zero f y → Echo f y` formalises the §4 "Approximate → strict (only when separated, at ε = 0)" statement. ### Rung B — axis-1 shadow agreement (§7 #8) - `echo-shadow-A` — A-component projection (axis-1 shadow of an approximate echo). - `echo-shadow-iso-{to,from}` — pins the §7 #8 obligation explicitly (definitional both ways: `EchoR ε f y` IS the existential `Σ A (λ x → dist (f x) y ≤ ε)`). - `echo-strict→approx-shadow-A` — cross-axis cohere: the A-component of `echo-strict→approx` agrees with the strict A-component on the nose (`refl`). - `echo-strict→approx-collapse-shadow-A` — round-trip under separation: `zero-collapses-strict ∘ strict→approx ≡ id` on the A-component, definitionally. ## DESIGN-DECISION STOP — Rung C deferred The full B-component + tolerance-budget retract round-trip (`echo-approx-comp-retract-from-to` and friends) requires a `+`-left-identity axiom on `Tolerance` (`zero + ε ≡ ε`, plus right-identity for the symmetric variants). ### Current shape of `Tolerance` ```agda record Tolerance ℓ : Set (suc ℓ) where field Tol : Set ℓ zero : Tol _+_ : Tol → Tol → Tol _≤_ : Tol → Tol → Set ℓ ≤-refl : ∀ {ε} → ε ≤ ε ≤-trans : ∀ {ε₁ ε₂ ε₃} → ε₁ ≤ ε₂ → ε₂ ≤ ε₃ → ε₁ ≤ ε₃ +-mono-≤ : ∀ {a b c d} → a ≤ b → c ≤ d → (a + c) ≤ (b + d) ``` No identity laws on `+`. Minimal interface, lets us state `echo-approx-compose`; that's all it currently does. ### What needs to be added For the symmetric retract round-trip: ```agda +-identityˡ : ∀ {ε} → (zero + ε) ≡ ε +-identityʳ : ∀ {ε} → (ε + zero) ≡ ε ``` (`≡`, not `≤` — the round-trip is a definitional retract on the tolerance budget, so we need a propositional equality, not just mutual `≤`.) ### Two options **(a)** Extend `Tolerance` with the two identity-law fields. - Pro: minimal scaffolding; one type to thread through. - Con: ripples to every `Tolerance` instance — currently just `EchoApproxInstance.trivialTolerance` (trivial discharge), but consumers may grow. Makes `Tolerance` strictly stronger than the "ordered commutative-monoid-flavoured" framing in §3 of the axis-2 design note. **(b)** Add a separate `BalancedTolerance` record on top of `Tolerance` carrying just the identity laws. - Pro: additive; no interface breakage; layered/opt-in. Matches the design pattern of `PseudoMetric` (which is *separately* refined to a metric by adding `Separated`, not by widening the base record). Keeps the §3 "minimal moving parts" promise. - Con: an extra record name to thread through retract-round-trip lemmas. ### Recommendation **(b).** The current `Tolerance` is deliberately the minimal interface that lets us state `echo-approx-compose` (triangle + additive monotonicity, nothing else). Identity laws are needed only by the retract round-trip, which is a downstream refinement — keeping them out of the core interface preserves the "minimal moving parts" design from §3 of the axis-2 design note. The analogue is `PseudoMetric` vs. (`PseudoMetric` + `Separated`): the refinement lives outside the base record. Rung C is deferred pending owner decision. ## Rung D — also deferred The Lipschitz generalisation `L_g ≠ 1` would need scalar multiplication on `Tolerance` — a third interface call (whether to add it to the core record, or layer a `ScalableTolerance` refinement). Deferred alongside Rung C; same recommendation shape applies. ## Hygiene All new headlines pinned via the per-lemma proof-of-life pattern from PR #71: `EchoApproxInstance.agda` adds top-level identifiers at the trivial-pseudometric-on-`⊤` instance, `Smoke.agda` enumerates them in its `using` clause. ## Verification ``` $ agda -i proofs/agda proofs/agda/All.agda # exit 0 $ agda -i proofs/agda proofs/agda/Smoke.agda # exit 0 ``` `--safe --without-K` throughout. No postulates. No escape pragmas. No funext. ## Refs - PR #70 (retract direction) - `/tmp/echo-types-exploration/axis2-approximate.md` §7 Refs not Closes — §7 obligations 7 and 8 are landed, but obligation 6's "full retract round-trip" (Rung C) and the Lipschitz generalisation remain. ## Test plan - [x] `agda proofs/agda/All.agda` exits 0 - [x] `agda proofs/agda/Smoke.agda` exits 0 - [x] No postulates / escape pragmas / funext introduced - [x] All new headlines pinned in `Smoke.agda` via `EchoApproxInstance` - [ ] Owner decides Rung C `Tolerance` interface question (a vs. b) - [ ] After decision: implement chosen option, land full retract round-trip 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 888dee0 commit 9a1a908

4 files changed

Lines changed: 160 additions & 6 deletions

File tree

docs/echo-types/composition.md

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,9 +172,30 @@ at `b := f x`, `ε₁ := zero`, `ε₂ := ε`), and
172172
`echo-approx-comp-retract-A` (A-component round-trip preserves the
173173
witness up to `refl`). The B-component round-trip and the
174174
tolerance-budget round-trip need a `+`-left-identity axiom on
175-
`Tolerance` (`zero + ε ≡ ε`) — not in the current record. The full
176-
retract proof and the Lipschitz generalisation (`L_g ≠ 1`) are
177-
deferred to subsequent rungs.
175+
`Tolerance` (`zero + ε ≡ ε`) — not in the current record; a
176+
design call on whether to extend the `Tolerance` interface or layer
177+
a separate `BalancedTolerance` record on top is deferred to the
178+
caller.
179+
180+
Second slice landed alongside (axis-2 design note §7 obligations
181+
7 and 8): `Separated` (separation predicate on the pseudo-metric:
182+
`dist b₁ b₂ ≤ zero → b₁ ≡ b₂`),
183+
`echo-approx-zero-collapses-strict` (under separation, every
184+
zero-tolerance approximate echo IS a strict echo with the same
185+
A-witness — the §4 "Approximate → strict, only when separated, at
186+
ε = 0" statement made formal), and the axis-1 shadow lemmas
187+
`echo-shadow-A`, `echo-shadow-iso-{to,from}`,
188+
`echo-strict→approx-shadow-A`,
189+
`echo-strict→approx-collapse-shadow-A`. The last two pin the
190+
axis-1 / axis-2 cross-classification: the A-component (the axis-1
191+
"shadow" of the approximate echo) is preserved on the nose by
192+
`echo-strict→approx` and round-trips definitionally through the
193+
zero-collapse under separation.
194+
195+
The Lipschitz generalisation (`L_g ≠ 1`) and the full B-component
196+
+ tolerance-budget retract round-trip remain deferred — both
197+
require new structure on `Tolerance` (multiplication for the
198+
former, `+`-identity for the latter).
178199

179200
### Q4. Associativity — landed
180201

proofs/agda/EchoApprox.agda

Lines changed: 122 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,20 @@
2626
-- preserves the A-witness up to `refl`,
2727
-- witnessing the retraction direction
2828
-- definitionally
29+
-- * Separated -- separation predicate on a pseudo-metric:
30+
-- `dist b₁ b₂ ≤ zero → b₁ ≡ b₂`
31+
-- * echo-approx-zero-collapses-strict -- §7 #7: under separation, an
32+
-- ε≡zero approximate echo IS a
33+
-- strict echo
34+
-- * echo-shadow-A -- §7 #8 axis-1 shadow: the underlying
35+
-- A-witness of an approximate echo;
36+
-- `echo-strict→approx` agrees with the
37+
-- strict A-witness on the nose
38+
-- * echo-shadow-iso-to / -from -- §7 #8 trivial repackaging of `EchoR`
39+
-- as an existential `Σ A (λ x → dist
40+
-- (f x) y ≤ ε)` (definitional both ways)
41+
-- * echo-strict→approx-shadow-A -- the A-component of `echo-strict→approx`
42+
-- equals the strict A-component (refl)
2943
--
3044
-- The non-expansiveness side condition on the outer leg is the
3145
-- minimal hypothesis under which tolerances accumulate additively;
@@ -41,9 +55,13 @@
4155
-- the canonical-split forward section, and an A-component round-trip
4256
-- witness. The B-component round-trip and the full tolerance round-trip
4357
-- need a `+`-left-identity axiom on `Tolerance` (`zero + ε ≡ ε`, not
44-
-- currently in the record); §7 obligations 7 (zero-collapse under
45-
-- separation) and 8 (axis-1 shadow agreement) are deferred to
46-
-- subsequent rungs.
58+
-- currently in the record); see the `Tolerance` design-decision note
59+
-- in the PR body for two options (interface extension vs. additive
60+
-- `BalancedTolerance` record). §7 obligations 7 (separated zero-collapse)
61+
-- and 8 (axis-1 shadow agreement) are now landed below. Rung C
62+
-- (full B-component + tolerance round-trip) is deferred pending the
63+
-- design call. Rung D (Lipschitz `L_g ≠ 1`) is deferred — it requires
64+
-- multiplication on `Tolerance`, another interface call.
4765

4866
module EchoApprox where
4967

@@ -261,3 +279,104 @@ module Approx
261279
(echo-approx-comp-retract-to f g e))
262280
≡ proj₁ e
263281
echo-approx-comp-retract-A f g g-nonexp (x , _) = refl
282+
283+
----------------------------------------------------------------------
284+
-- §7 obligation 7: separated zero-collapse.
285+
--
286+
-- A pseudo-metric is *separated* when zero distance implies
287+
-- propositional equality on the carrier. Pseudo-metrics in general
288+
-- only guarantee `dist y y ≡ zero`; the converse (a metric proper)
289+
-- is an extra hypothesis the `PseudoMetric` record deliberately
290+
-- does not bake in. Callers who need the converse supply a
291+
-- `Separated` witness explicitly at the lemma site.
292+
--
293+
-- Under that hypothesis, the strict-vs-approximate gap closes at
294+
-- ε = zero: any zero-tolerance approximate echo IS a strict echo,
295+
-- with the same A-witness on the nose. This realises §7 #7 of the
296+
-- axis-2 design note and the §4 "Approximate → strict (only when
297+
-- separated, at ε = 0)" statement.
298+
--
299+
-- Without separation the converse fails by design — multiple `x`s
300+
-- may share zero distance to `y` without `f x ≡ y` on the nose.
301+
-- That is the point of an approximate echo.
302+
----------------------------------------------------------------------
303+
304+
Separated : Set (b ⊔ ℓ)
305+
Separated = b₁ b₂ dist b₁ b₂ ≤ zero b₁ ≡ b₂
306+
307+
echo-approx-zero-collapses-strict :
308+
Separated
309+
{f : A B} {y : B} EchoR zero f y Echo f y
310+
echo-approx-zero-collapses-strict sep {f = f} {y = y} (x , dfx≤0) =
311+
x , sep (f x) y dfx≤0
312+
313+
----------------------------------------------------------------------
314+
-- §7 obligation 8: axis-1 shadow agreement.
315+
--
316+
-- The "shadow" of an approximate echo is its underlying A-witness —
317+
-- the projection that forgets the metric-bound proof. Two flavours:
318+
--
319+
-- * `echo-shadow-A` — extracts the A-witness from an
320+
-- approximate echo (definitional,
321+
-- the existing `proj₁`).
322+
--
323+
-- * `echo-shadow-iso-{to,from}` — the trivial repackaging of
324+
-- `EchoR ε f y` as the existential
325+
-- `Σ A (λ x → dist (f x) y ≤ ε)`.
326+
-- Both directions are `id` because
327+
-- the two shapes are definitionally
328+
-- equal; the iso lemma here pins
329+
-- the §7 #8 obligation explicitly.
330+
--
331+
-- * `echo-strict→approx-shadow-A` — axis-1 / axis-2 cross-check:
332+
-- `echo-strict→approx` preserves
333+
-- the A-component on the nose
334+
-- (`refl`). This is the
335+
-- definitional version of "the
336+
-- strict→approx inclusion and the
337+
-- A-shadow projection cohere"
338+
-- from the user-prompt framing.
339+
--
340+
-- Together these say: the A-component is a genuine axis-1 invariant
341+
-- of approximate echoes — every move in the axis-2 calculus that
342+
-- keeps the A-witness fixed (intro, strict→approx, relax,
343+
-- canonical-split retract section) preserves the axis-1 shadow
344+
-- definitionally.
345+
----------------------------------------------------------------------
346+
347+
echo-shadow-A :
348+
: Tol} {f : A B} {y : B} EchoR ε f y A
349+
echo-shadow-A = proj₁
350+
351+
-- Forward: an approximate echo IS an existential with metric bound.
352+
-- Definitionally `id`; the lemma pins the axis-1 shadow obligation.
353+
echo-shadow-iso-to :
354+
: Tol} {f : A B} {y : B}
355+
EchoR ε f y Σ A (λ x dist (f x) y ≤ ε)
356+
echo-shadow-iso-to e = e
357+
358+
echo-shadow-iso-from :
359+
: Tol} {f : A B} {y : B}
360+
Σ A (λ x dist (f x) y ≤ ε) EchoR ε f y
361+
echo-shadow-iso-from e = e
362+
363+
-- A-component of `echo-strict→approx` agrees with the strict
364+
-- A-component on the nose. The transport in `echo-strict→approx`
365+
-- only touches the bound proof, never the A-witness.
366+
echo-strict→approx-shadow-A :
367+
{f : A B} {y : B} (e : Echo f y)
368+
echo-shadow-A (echo-strict→approx e) ≡ proj₁ e
369+
echo-strict→approx-shadow-A (x , _) = refl
370+
371+
-- Round-trip: under separation, `echo-approx-zero-collapses-strict`
372+
-- and `echo-strict→approx` are mutually A-inverse at ε = zero,
373+
-- definitionally on the A-component. This closes the §4 statement
374+
-- "Approximate → strict (only when separated, at ε = 0)" with a
375+
-- definitional witness on the axis-1 shadow.
376+
echo-strict→approx-collapse-shadow-A :
377+
(sep : Separated)
378+
{f : A B} {y : B} (e : Echo f y)
379+
proj₁ (echo-approx-zero-collapses-strict sep
380+
(echo-strict→approx e))
381+
≡ proj₁ e
382+
echo-strict→approx-collapse-shadow-A sep (x , _) = refl

proofs/agda/EchoApproxInstance.agda

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,10 @@ approx-compose = echo-approx-compose
9696
approx-comp-sound = echo-approx-comp-sound
9797
approx-comp-retract-to = echo-approx-comp-retract-to
9898
approx-comp-retract-A = echo-approx-comp-retract-A
99+
approx-Separated = Separated
100+
approx-zero-collapses-strict = echo-approx-zero-collapses-strict
101+
approx-shadow-A = echo-shadow-A
102+
approx-shadow-iso-to = echo-shadow-iso-to
103+
approx-shadow-iso-from = echo-shadow-iso-from
104+
approx-strict→approx-shadow-A = echo-strict→approx-shadow-A
105+
approx-strict→approx-collapse-shadow-A = echo-strict→approx-collapse-shadow-A

proofs/agda/Smoke.agda

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,13 @@ open import EchoApproxInstance using
9090
; approx-comp-sound
9191
; approx-comp-retract-to
9292
; approx-comp-retract-A
93+
; approx-Separated
94+
; approx-zero-collapses-strict
95+
; approx-shadow-A
96+
; approx-shadow-iso-to
97+
; approx-shadow-iso-from
98+
; approx-strict→approx-shadow-A
99+
; approx-strict→approx-collapse-shadow-A
93100
)
94101

95102
open import EchoIndexed using

0 commit comments

Comments
 (0)