Skip to content

Commit 44f5efd

Browse files
authored
Merge pull request #111 from fsprojects/run118-work-100f9fbb5ed40f8e
[lean-squad] Deadzone_mono_dz_pos proved + correspondence tests (run118)
2 parents c1ca507 + c4a2c89 commit 44f5efd

5 files changed

Lines changed: 595 additions & 3 deletions

File tree

formal-verification/CORRESPONDENCE.md

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,43 @@
33
🔬 *Generated by Lean Squad automated formal verification.*
44

55
## Last Updated
6-
- **Date**: 2026-05-10 08:49 UTC
7-
- **Commit**: `01186ce1cebbe8c3483a07970c0e6b0b3b760191`
6+
- **Date**: 2026-05-25 UTC
7+
- **Commit**: `c1ca507bb3`
88

9-
### Run 114 Review Notes
9+
### Run 117 Review Notes
10+
11+
Task 6 (Correspondence Review): updated for runs 115–116.
12+
13+
**New theorems in `PID.lean`** (runs 115–116, now 36 theorems, 0 sorry):
14+
- `pidIntegralIterate_neg_error_mono`: iterated integration is non-decreasing (negative error
15+
direction) — if `e₁ ≤ e₂` then `pidIntegralIterate n e₁ ≤ pidIntegralIterate n e₂`.
16+
- `pidIntegralIterate_neg_saturates`: iterated negative-error integration saturates at `iMin`
17+
in finite steps.
18+
19+
**New theorems in `Expo.lean`** (runs 115–116, now 27 theorems, 0 sorry):
20+
- `expo_mono_val`: `expoRat` is non-decreasing in the value argument — the RC expo curve
21+
preserves input ordering.
22+
- `expo_mono_e_pos_v`: for positive `v` (in constraint range), increasing `e` lowers the
23+
output — higher expo = more curved/gentle near center.
24+
- `expo_mono_e_neg_v`: symmetric: for negative `v`, increasing `e` raises the output.
25+
- Additional private helper lemmas (`constrain_mono`, `constrainRat_of_in_range`, etc.)
26+
supporting these proofs.
27+
28+
**New theorems in `Deadzone.lean`** (run 117, now 14 theorems + 7 private helpers, 0 sorry):
29+
- `deadzone_mono_val`: `deadzone` is non-decreasing in the input `v` — a larger stick
30+
deflection always produces a ≥ output, preserving direction across deadzone boundaries.
31+
Proof by case analysis (v1 inside/outside × v2 inside/outside).
32+
33+
**New theorems in `ExpoDeadzone.lean`** (run 117, now 12 theorems, 0 sorry):
34+
- `expodz_mono_val`: the full expo-deadzone pipeline is non-decreasing in `v` — follows
35+
directly from `deadzone_mono_val``expo_mono_val`.
36+
37+
Coverage: **44 Lean files**, **~820 named theorem/lemma references**, 0 `sorry`.
38+
39+
No source drift detected: `src/lib/mathlib/math/Functions.hpp` and `src/lib/pid/PID.cpp`
40+
unchanged since run 114.
41+
42+
> 🔬 Added by Lean Squad run 117.
1043
1144
Task 6 (Correspondence Review) and Task 7 (Proof Utility Critique) update.
1245

formal-verification/lean/FVSquad/Deadzone.lean

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,202 @@ theorem deadzone_odd (x dz : Rat) (hdz : 0 ≤ dz) :
298298
rw [deadzone_in_dz x dz hin, deadzone_in_dz (-x) dz (by rwa [Rat.abs_neg]),
299299
Rat.neg_zero]
300300

301+
/-! ## Monotonicity -/
302+
303+
-- Private helpers used only by `deadzone_mono_val`
304+
private theorem dz_le_abs (v : Rat) : v ≤ v.abs := by
305+
by_cases h : 0 ≤ v
306+
· rw [Rat.abs_of_nonneg h]; exact @Rat.le_refl v
307+
· exact Rat.le_trans (Rat.le_of_lt (Rat.not_le.mp h)) Rat.abs_nonneg
308+
309+
private theorem dz_neg_abs_le (v : Rat) : -v.abs ≤ v := by
310+
by_cases h : 0 ≤ v
311+
· rw [Rat.abs_of_nonneg h]; exact Rat.le_trans (Rat.neg_le_iff.mpr h) h
312+
· have hv : v < 0 := Rat.not_le.mp h
313+
rw [Rat.abs_of_nonpos (Rat.le_of_lt hv), Rat.neg_neg]; exact @Rat.le_refl v
314+
315+
private theorem dz_abs_le_left (v dz : Rat) (h : v.abs ≤ dz) : -dz ≤ v :=
316+
Rat.le_trans (Rat.neg_le_neg h) (dz_neg_abs_le v)
317+
318+
private theorem dz_one_sub_pos (dz : Rat) (h : dz < 1) : (0 : Rat) < 1 - dz := by
319+
have := Rat.add_lt_add_right (a := dz) (b := 1) (c := -dz) |>.mpr h
320+
rwa [Rat.add_neg_cancel, ← Rat.sub_eq_add_neg] at this
321+
322+
private theorem dz_div_le (a b c : Rat) (h : a ≤ b) (hc : 0 < c) : a / c ≤ b / c := by
323+
rw [Rat.div_def, Rat.div_def]
324+
exact Rat.mul_le_mul_of_nonneg_right h (Rat.le_of_lt (Rat.inv_pos.mpr hc))
325+
326+
private theorem dz_sub_le (a b k : Rat) (h : a ≤ b) : a - k ≤ b - k := by
327+
rw [Rat.sub_eq_add_neg, Rat.sub_eq_add_neg]; exact (Rat.add_le_add_right (c := -k)).mpr h
328+
329+
private theorem dz_abs_out_neg (x dz : Rat) (hxneg : x < 0) (hxabs : x.abs > dz) : x < -dz := by
330+
rw [Rat.abs_of_nonpos (Rat.le_of_lt hxneg)] at hxabs
331+
have h := Rat.neg_lt_neg hxabs; rwa [Rat.neg_neg] at h
332+
333+
/-- **Monotonicity in the input value**: `deadzone` is non-decreasing in `v`.
334+
335+
If `v₁ ≤ v₂` then `deadzone v₁ dz ≤ deadzone v₂ dz`.
336+
337+
This is a key correctness property for RC stick processing: a larger stick
338+
deflection always produces a larger (or equal) output, preserving the
339+
direction of input changes across the deadzone boundary. -/
340+
theorem deadzone_mono_val (v1 v2 dz : Rat) (hv : v1 ≤ v2) (hdz0 : 0 ≤ dz) (hdz1 : dz < 1) :
341+
deadzone v1 dz ≤ deadzone v2 dz := by
342+
have h1mdz : (0 : Rat) < 1 - dz := dz_one_sub_pos dz hdz1
343+
by_cases h1 : v1.abs > dz
344+
· -- v1 outside deadzone
345+
by_cases h2 : v2.abs > dz
346+
· -- Both outside: monotone on each branch, or neg→pos gives neg<0<pos
347+
by_cases hv1pos : (0 : Rat) ≤ v1
348+
· -- Both positive (v1 ≤ v2, v1 ≥ 0 → v2 ≥ 0)
349+
have hv2pos : (0 : Rat) ≤ v2 := Rat.le_trans hv1pos hv
350+
rw [deadzone_pos_eq v1 dz (by rwa [Rat.abs_of_nonneg hv1pos] at h1) hdz0,
351+
deadzone_pos_eq v2 dz (by rwa [Rat.abs_of_nonneg hv2pos] at h2) hdz0]
352+
exact dz_div_le _ _ _ (dz_sub_le v1 v2 dz hv) h1mdz
353+
· -- v1 negative
354+
have hv1neg : v1 < 0 := Rat.not_le.mp hv1pos
355+
have hv1dz : v1 < -dz := dz_abs_out_neg v1 dz hv1neg h1
356+
by_cases hv2pos : (0 : Rat) ≤ v2
357+
· -- v1 negative, v2 non-negative
358+
have hv2dz : dz < v2 := by rwa [Rat.abs_of_nonneg hv2pos] at h2
359+
exact Rat.le_of_lt (Std.lt_trans (deadzone_neg v1 dz hv1dz hdz0 hdz1)
360+
(deadzone_pos v2 dz hv2dz hdz0 hdz1))
361+
· -- Both negative
362+
have hv2neg : v2 < 0 := Rat.not_le.mp hv2pos
363+
have hv2dz : v2 < -dz := dz_abs_out_neg v2 dz hv2neg h2
364+
rw [deadzone_neg_eq v1 dz hv1dz hdz0, deadzone_neg_eq v2 dz hv2dz hdz0]
365+
exact dz_div_le _ _ _ ((Rat.add_le_add_right (c := dz)).mpr hv) h1mdz
366+
· -- v1 outside, v2 inside: v1 must be negative (since v1 ≤ v2 ≤ dz < |v1|)
367+
have hin2 : v2.abs ≤ dz := Rat.not_lt.mp h2
368+
have hv1neg : v1 < 0 := by
369+
apply Classical.byContradiction; intro hv1pos
370+
have hv1nn : 0 ≤ v1 := Rat.not_lt.mp hv1pos
371+
rw [Rat.abs_of_nonneg hv1nn] at h1
372+
exact absurd (Rat.le_trans hv (Rat.le_trans (dz_le_abs v2) hin2)) (Rat.not_le.mpr h1)
373+
have hv1dz : v1 < -dz := dz_abs_out_neg v1 dz hv1neg h1
374+
rw [deadzone_in_dz v2 dz hin2]
375+
exact Rat.le_of_lt (deadzone_neg v1 dz hv1dz hdz0 hdz1)
376+
· -- v1 inside deadzone
377+
have hin1 : v1.abs ≤ dz := Rat.not_lt.mp h1
378+
rw [deadzone_in_dz v1 dz hin1]
379+
by_cases h2 : v2.abs > dz
380+
· -- v2 outside: v2 ≥ v1 ≥ -dz, so v2 must be positive (cannot be < -dz)
381+
have hv2dz : dz < v2 := by
382+
apply Classical.byContradiction; intro hv2neg_dz
383+
have hv2nn : ¬(0 ≤ v2) := fun hv2nn =>
384+
absurd (by rwa [Rat.abs_of_nonneg hv2nn] at h2) hv2neg_dz
385+
exact absurd (Rat.le_trans (dz_abs_le_left v1 dz hin1) hv)
386+
(Rat.not_le.mpr (dz_abs_out_neg v2 dz (Rat.not_le.mp hv2nn) h2))
387+
exact Rat.le_of_lt (deadzone_pos v2 dz hv2dz hdz0 hdz1)
388+
· -- Both inside: 0 ≤ 0
389+
rw [deadzone_in_dz v2 dz (Rat.not_lt.mp h2)]; exact Rat.le_refl
390+
391+
/-! ## No-deadzone identity (general) -/
392+
393+
/-- The negative case of the no-deadzone identity: when `dz = 0`, `deadzone x 0 = x` for `x < 0`. -/
394+
theorem deadzone_no_dz_neg (x : Rat) (hx : x < 0) : deadzone x 0 = x := by
395+
rw [deadzone_neg_eq x 0 (by rw [Rat.neg_zero]; exact hx) (Rat.le_refl)]
396+
rw [Rat.add_zero, Rat.sub_eq_add_neg, Rat.neg_zero, Rat.add_zero,
397+
Rat.div_def, Rat.inv_eq_of_mul_eq_one (Rat.mul_one 1), Rat.mul_one]
398+
399+
/-- **Identity with no deadzone** (all cases): when `dz = 0`, `deadzone x 0 = x` for all `x`.
400+
401+
Combining `deadzone_no_dz_pos`, `deadzone_no_dz_neg`, and the zero case (which is in the
402+
deadzone since `|0| = 0 = dz`), we get the full identity. -/
403+
theorem deadzone_no_dz (x : Rat) : deadzone x 0 = x := by
404+
by_cases h1 : 0 < x
405+
· exact deadzone_no_dz_pos x h1
406+
· by_cases h2 : x = 0
407+
· subst h2
408+
simp [deadzone]
409+
· exact deadzone_no_dz_neg x (Rat.lt_of_le_of_ne (Rat.not_lt.mp h1) h2)
410+
411+
/-! ## Monotonicity in the deadzone width -/
412+
413+
-- Private helpers for `deadzone_mono_dz_pos`
414+
415+
/-- Inverse anti-monotonicity: larger positive denominators give smaller inverses. -/
416+
private theorem dz_inv_anti (a b : Rat) (hba : b ≤ a) (ha : 0 < a) (hb : 0 < b) : a⁻¹ ≤ b⁻¹ := by
417+
have ha_ne : a ≠ 0 := Rat.ne_of_gt ha
418+
have hb_ne : b ≠ 0 := Rat.ne_of_gt hb
419+
rw [Rat.le_iff_sub_nonneg]
420+
have key : b⁻¹ - a⁻¹ = (a - b) * (a⁻¹ * b⁻¹) := by
421+
simp only [Rat.sub_eq_add_neg, Rat.add_mul, Rat.neg_mul]
422+
rw [show a * (a⁻¹ * b⁻¹) = b⁻¹ by
423+
rw [← Rat.mul_assoc a a⁻¹ b⁻¹, Rat.mul_inv_cancel a ha_ne, Rat.one_mul]]
424+
rw [show b * (a⁻¹ * b⁻¹) = a⁻¹ by
425+
rw [Rat.mul_comm a⁻¹ b⁻¹, ← Rat.mul_assoc b b⁻¹ a⁻¹,
426+
Rat.mul_inv_cancel b hb_ne, Rat.one_mul]]
427+
rw [key]
428+
exact Rat.mul_nonneg ((Rat.le_iff_sub_nonneg b a).mp hba)
429+
(Rat.mul_nonneg (Rat.le_of_lt (Rat.inv_pos.mpr ha)) (Rat.le_of_lt (Rat.inv_pos.mpr hb)))
430+
431+
/-- Rewrite `(x - dz) / (1 - dz)` as `1 - (1 - x) / (1 - dz)`. -/
432+
private theorem dz_div_rewrite (x dz : Rat) (h1mdz : 0 < 1 - dz) :
433+
(x - dz) / (1 - dz) = 1 - (1 - x) / (1 - dz) := by
434+
rw [Rat.div_def, Rat.div_def]
435+
have h1mdz_ne : (1 - dz) ≠ 0 := Rat.ne_of_gt h1mdz
436+
have hn : (1 - dz) - (1 - x) = x - dz := by
437+
simp only [Rat.sub_eq_add_neg, Rat.neg_add, Rat.neg_neg]
438+
rw [Rat.add_assoc, ← Rat.add_assoc (-dz) (-1) x, Rat.add_comm (-dz) (-1),
439+
Rat.add_assoc (-1) (-dz) x, ← Rat.add_assoc 1 (-1) (-dz + x),
440+
show (1:Rat) + -1 = 0 from Rat.add_neg_cancel 1, Rat.zero_add, Rat.add_comm (-dz) x]
441+
rw [show (x - dz) * (1 - dz)⁻¹ = ((1 - dz) - (1 - x)) * (1 - dz)⁻¹ from by rw [hn]]
442+
rw [show ((1 - dz) - (1 - x)) * (1 - dz)⁻¹ = (1 - dz) * (1 - dz)⁻¹ - (1 - x) * (1 - dz)⁻¹
443+
from by simp only [Rat.sub_eq_add_neg, Rat.add_mul, Rat.neg_mul]]
444+
rw [Rat.mul_inv_cancel _ h1mdz_ne]
445+
446+
/-- `c - b ≤ c - a` when `a ≤ b`. -/
447+
private theorem dz_sub_le_sub_l (a b c : Rat) (h : a ≤ b) : c - b ≤ c - a := by
448+
rw [Rat.le_iff_sub_nonneg (c - b) (c - a)]
449+
have key : (c - a) - (c - b) = b - a := by
450+
simp only [Rat.sub_eq_add_neg, Rat.neg_add, Rat.neg_neg]
451+
rw [Rat.add_assoc c (-a) (-c + b), ← Rat.add_assoc (-a) (-c) b, Rat.add_comm (-a) (-c),
452+
Rat.add_assoc (-c) (-a) b, ← Rat.add_assoc c (-c) (-a + b),
453+
show c + -c = (0:Rat) from Rat.add_neg_cancel c, Rat.zero_add, Rat.add_comm (-a) b]
454+
rw [key]; exact (Rat.le_iff_sub_nonneg a b).mp h
455+
456+
/-- **Monotonicity in `dz`** (positive branch): for a fixed input `x`,
457+
increasing the deadzone width `dz` weakly decreases the output.
458+
459+
Statement: `dz₁ ≤ dz₂ < x ≤ 1`, `0 ≤ dz₁`, `dz₂ < 1` →
460+
`deadzone x dz₂ ≤ deadzone x dz₁`.
461+
462+
Proof: rewrite `(x - dz) / (1 - dz) = 1 - (1 - x) / (1 - dz)`. Since `dz₁ ≤ dz₂`,
463+
we have `1 - dz₂ ≤ 1 - dz₁`, so `(1 - dz₁)⁻¹ ≤ (1 - dz₂)⁻¹` by inverse anti-monotonicity.
464+
Multiplying by `1 - x ≥ 0` gives `(1 - x)/(1 - dz₁) ≤ (1 - x)/(1 - dz₂)`, hence the
465+
subtraction from 1 reverses the inequality. -/
466+
theorem deadzone_mono_dz_pos (x dz1 dz2 : Rat)
467+
(hx : 0 < x) (hx1 : x ≤ 1)
468+
(h12 : dz1 ≤ dz2) (hdz0 : 0 ≤ dz1) (hdz1 : dz2 < 1) (hout : dz2 < x) :
469+
deadzone x dz2 ≤ deadzone x dz1 := by
470+
have hout1 : dz1 < x := Std.lt_of_le_of_lt h12 hout
471+
rw [deadzone_pos_eq x dz1 hout1 hdz0,
472+
deadzone_pos_eq x dz2 hout (Rat.le_trans hdz0 h12)]
473+
have hd1 : (0:Rat) < 1 - dz1 := dz_one_sub_pos dz1 (Std.lt_of_le_of_lt h12 hdz1)
474+
have hd2 : (0:Rat) < 1 - dz2 := dz_one_sub_pos dz2 hdz1
475+
-- Rewrite both sides: (x - dz) / (1 - dz) = 1 - (1 - x) / (1 - dz)
476+
rw [dz_div_rewrite x dz1 hd1, dz_div_rewrite x dz2 hd2]
477+
-- Goal: 1 - (1 - x) / (1 - dz2) ≤ 1 - (1 - x) / (1 - dz1)
478+
apply dz_sub_le_sub_l
479+
-- Goal: (1 - x) / (1 - dz1) ≤ (1 - x) / (1 - dz2)
480+
rw [Rat.div_def, Rat.div_def]
481+
have h1mx : (0:Rat) ≤ 1 - x := (Rat.le_iff_sub_nonneg x 1).mp hx1
482+
-- 1 - dz2 ≤ 1 - dz1 (because dz1 ≤ dz2)
483+
have hdz12 : (1:Rat) - dz2 ≤ 1 - dz1 := by
484+
rw [Rat.le_iff_sub_nonneg]
485+
have key : (1 - dz1) - (1 - dz2) = dz2 - dz1 := by
486+
simp only [Rat.sub_eq_add_neg, Rat.neg_add, Rat.neg_neg]
487+
rw [Rat.add_assoc 1 (-dz1) (-1 + dz2), ← Rat.add_assoc (-dz1) (-1) dz2,
488+
Rat.add_comm (-dz1) (-1), Rat.add_assoc (-1) (-dz1) dz2,
489+
← Rat.add_assoc 1 (-1) (-dz1 + dz2),
490+
show (1:Rat) + -1 = 0 from Rat.add_neg_cancel 1,
491+
Rat.zero_add, Rat.add_comm (-dz1) dz2]
492+
rw [key]; exact (Rat.le_iff_sub_nonneg dz1 dz2).mp h12
493+
-- (1 - dz1)⁻¹ ≤ (1 - dz2)⁻¹ by inverse anti-monotonicity
494+
have hinv : (1 - dz1)⁻¹ ≤ (1 - dz2)⁻¹ := dz_inv_anti (1 - dz1) (1 - dz2) hdz12 hd1 hd2
495+
exact Rat.mul_le_mul_of_nonneg_left hinv h1mx
496+
301497
/-! ## Summary of proved properties
302498
303499
| Theorem | Statement | Status |
@@ -310,11 +506,15 @@ theorem deadzone_odd (x dz : Rat) (hdz : 0 ≤ dz) :
310506
| `deadzone_pos` | `x > dz ≥ 0, dz < 1 → deadzone x dz > 0` | ✅ Proved |
311507
| `deadzone_neg` | `x < -dz ≤ 0, dz < 1 → deadzone x dz < 0` | ✅ Proved |
312508
| `deadzone_no_dz_pos` | `deadzone x 0 = x` (identity, positive case) | ✅ Proved |
509+
| `deadzone_no_dz_neg` | `deadzone x 0 = x` (identity, negative case) | ✅ Proved |
510+
| `deadzone_no_dz` | `deadzone x 0 = x` (identity, all inputs) | ✅ Proved |
313511
| `deadzone_at_max` | `deadzone 1 dz = 1` (for `dz < 1`) | ✅ Proved |
314512
| `deadzone_at_min` | `deadzone (-1) dz = -1` (for `dz < 1`) | ✅ Proved |
315513
| `deadzone_le_one` | `x ≤ 1, 0 ≤ dz < 1 → deadzone x dz ≤ 1` | ✅ Proved |
316514
| `deadzone_ge_neg_one` | `-1 ≤ x, 0 ≤ dz < 1 → -1 ≤ deadzone x dz` | ✅ Proved |
317515
| `deadzone_odd` | `dz ≥ 0 → deadzone (-x) dz = -(deadzone x dz)` | ✅ Proved |
516+
| `deadzone_mono_val` | `v₁ ≤ v₂ → deadzone v₁ dz ≤ deadzone v₂ dz` | ✅ Proved |
517+
| `deadzone_mono_dz_pos` | `dz₁ ≤ dz₂ < x ≤ 1 → deadzone x dz₂ ≤ deadzone x dz₁` | ✅ Proved |
318518
-/
319519

320520
end PX4.Deadzone

formal-verification/lean/FVSquad/ExpoDeadzone.lean

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ float expo_deadzone(float value, float e, float dz) {
3535
| `expodz_cubic` | ✅ | e=1: deadzone output cubed |
3636
| `expodz_no_dz` | ✅ | dz=0: reduces to pure expo |
3737
| `expodz_odd` | ✅ | Odd symmetry: `expodz(-v, e, dz) = -expodz(v, e, dz)` |
38+
| `expodz_mono_val` | ✅ | Monotone in `v`: `v₁ ≤ v₂ → expodz v₁ e dz ≤ expodz v₂ e dz` |
3839
3940
## Modelling Notes
4041
@@ -183,4 +184,22 @@ theorem expodz_odd (v e dz : Rat) (hdz : 0 ≤ dz) :
183184
simp only [expodz]
184185
rw [deadzone_odd v dz hdz, expo_odd (deadzone v dz) e]
185186

187+
/-!
188+
## Monotonicity
189+
-/
190+
191+
/-- **Monotonicity in the input value**: `expodz` is non-decreasing in `v`.
192+
193+
If `v₁ ≤ v₂` then `expodz v₁ e dz ≤ expodz v₂ e dz`.
194+
195+
This follows by composing the two monotonicity theorems:
196+
- `deadzone_mono_val`: deadzone is non-decreasing in `v`
197+
- `expo_mono_val`: `expoRat` is non-decreasing in its value argument
198+
199+
Together they ensure the full RC input pipeline preserves input ordering. -/
200+
theorem expodz_mono_val (v1 v2 e dz : Rat) (hv : v1 ≤ v2) (hdz0 : 0 ≤ dz) (hdz1 : dz < 1) :
201+
expodz v1 e dz ≤ expodz v2 e dz := by
202+
simp only [expodz]
203+
exact expo_mono_val _ _ e (deadzone_mono_val v1 v2 dz hv hdz0 hdz1)
204+
186205
end PX4.ExpoDeadzone
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Deadzone Correspondence Tests
2+
3+
🔬 *Lean Squad automated formal verification*
4+
5+
This directory contains **Route B correspondence tests** for the `deadzone` function,
6+
validating that the Lean 4 rational model in
7+
`formal-verification/lean/FVSquad/Deadzone.lean` matches the C++ template
8+
implementation in `src/lib/mathlib/math/Functions.hpp`.
9+
10+
## Running the tests
11+
12+
```bash
13+
python3 check_correspondence.py
14+
```
15+
16+
Exit code 0 means all cases passed; non-zero means at least one mismatch.
17+
18+
## What is tested
19+
20+
| Test | Description |
21+
|------|-------------|
22+
| Zero deadzone | `deadzone(x, 0) = x` for all `x` |
23+
| Inside deadzone | `\|x\| ≤ dz → deadzone(x, dz) = 0` |
24+
| Positive branch | `x > dz ≥ 0 → deadzone(x, dz) = (x − dz) / (1 − dz)` |
25+
| Negative branch | `x < −dz → deadzone(x, dz) = (x + dz) / (1 − dz)` |
26+
| Boundary x=1 | `deadzone(1, dz) = 1` |
27+
| Boundary x=−1 | `deadzone(−1, dz) = −1` |
28+
| Odd symmetry | `deadzone(−x, dz) = −deadzone(x, dz)` |
29+
| Monotone in value | `v₁ ≤ v₂ → deadzone(v₁, dz) ≤ deadzone(v₂, dz)` |
30+
| Monotone in dz | `dz₁ ≤ dz₂ < x → deadzone(x, dz₂) ≤ deadzone(x, dz₁)` |
31+
| Dense grid | All `(x, dz)` in `{-1, -0.9, …, 1} × {0, 0.1, …, 0.9}` |
32+
| Rational fractions | Inputs like `1/3`, `2/7`, `3/11`, etc. |
33+
| Output range | `-1 ≤ deadzone(x, dz) ≤ 1` |
34+
35+
## Domain assumptions
36+
37+
The C++ code clamps `value` to `[−1, 1]` and `dz` to `[0, 0.99]` before
38+
computing. The Lean model abstracts away this clamping (it is a pre-condition
39+
of the model). All test inputs are chosen within these bounds, so the C++
40+
`constrain` calls are identity and the two sides compute the same thing.
41+
42+
## Correspondence theorem
43+
44+
For inputs in domain `x ∈ [−1, 1]`, `dz ∈ [0, 1)`:
45+
46+
```
47+
C++: out = (x − sign(x)·dz) / (1 − dz) if |x| > dz, else 0
48+
Lean: if x > dz then (x − dz) / (1 − dz)
49+
elif x < −dz then (x + dz) / (1 − dz)
50+
else 0
51+
```
52+
53+
These are equal because `sign(x) = 1` for `x ≥ 0` and `sign(x) = −1` for
54+
`x < 0`, matching the Lean branching on `x > dz` vs `x < −dz`.

0 commit comments

Comments
 (0)