feat(nc): piecewise-affine arrival curves (v0.9.3 NC tightness #1)#204
Merged
feat(nc): piecewise-affine arrival curves (v0.9.3 NC tightness #1)#204
Conversation
Adds `PiecewiseAffineArrivalCurve` alongside the existing single-bucket `ArrivalCurve` to encode T-SPEC-style multi-leaky-bucket constraints `α(t) = min_i (σ_i + ρ_i · t)`. The form captures both short-horizon burst (small σ, high ρ) and long-horizon sustained behaviour (large σ, low ρ) in a single curve — typically halves bounds on real ADAS / TSN traffic where the binding bucket changes across operating regimes. Math kernel: the four classical operators extend per-bucket. Backlog and delay take the min over per-bucket bounds (the binding bucket determines the bound at any operating point); output_bound preserves the piecewise structure with each bucket inflated independently (Le Boudec & Thiran 1.4.3 applied per-bucket); residual_service lowers conservatively to a single rate-latency curve via `R - max ρ_i` and the worst per-bucket burst-drain time. Compatibility: `From<ArrivalCurve>` round-trips the single-bucket case (peak-capped form lowers to a 2-bucket PWA `(σ, ρ), (0, peak)`); wctt.rs consumers continue to use the single-bucket `ArrivalCurve` for v0.9.3 (no test bound changes); `MinPlus.lean` (the load-bearing single-bucket Lean spec, 4 sorrys still on the affine form) is untouched. A `MinPlusPwa.lean` skeleton is filed out-of-tree (not imported by `Proofs.lean`) with theorem statements for the per-bucket generalisations, all sorry-tagged for v1.0.0. 14 unit tests under `curves::piecewise::tests`: empty-bucket reject, single-bucket=affine numeric agreement, σ-ascending canonical sort, duplicate dedup, at(t) min across buckets, at(0) causality, delay min each-regime-dominates pattern, all-buckets-stable requirement, output per-bucket inflation, residual `R - max ρ` with worst σ-drain, unservable on saturating bucket, From<ArrivalCurve> round-trips for both affine and peak-capped, backlog min across buckets. Per the post-v0.9.2 reviewer's NC top-5 #1 — highest-leverage tightness item. REQ-NETWORK-013 in artifacts/requirements.yaml; TEST-NC-PIECEWISE in verification.yaml; rivet validate PASS. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
PiecewiseAffineArrivalCurvealongside the existing single-bucketArrivalCurveto encode T-SPEC-style multi-leaky-bucket arrivalsα(t) = min_i (σ_i + ρ_i · t)— captures short-horizon burst + long-horizon sustained behaviour in one curve.output_boundpreserves the piecewise structure with each bucket inflated independently (Le Boudec & Thiran 1.4.3 per-bucket),residual_servicelowers conservatively to a single rate-latency viaR - max ρ_iand the worst per-bucket burst-drain time.From<ArrivalCurve>round-trips the single-bucket case (peak-capped form lowers to a 2-bucket PWA(σ, ρ), (0, peak)).curves::piecewise::tests— covering canonicalisation, causality, min-of-affines readouts, per-regime dominance for delay, conservative residual, single-bucket round-trip, peak-cap round-trip, etc.proofs/Proofs/Network/MinPlusPwa.leanwith theorem statements for the per-bucket generalisations, allsorry -- TODO(v1.0.0).MinPlus.lean(single-bucket spec, 4 remaining sorrys on the affine form) is unchanged — single-bucket is the special case[(σ, ρ)]of the new form, and existing wctt consumers stay onArrivalCurve.File deltas
crates/spar-network/src/curves.rs+611 lines (newpub mod piecewisewithPiecewiseAffineArrivalCurve,PwaError, four operators,From<ArrivalCurve>, 14 unit tests)crates/spar-network/src/lib.rs+1 (re-exportPiecewiseAffineArrivalCurve,PwaError)proofs/Proofs/Network/MinPlusPwa.lean+290 lines new (skeleton, not imported byProofs.lean)artifacts/requirements.yamlREQ-NETWORK-013artifacts/verification.yamlTEST-NC-PIECEWISE → satisfies REQ-NETWORK-013Out of scope
MinPlus.leansorrys are not discharged (separate v1.0 work).Deviations
The reviewer's English description for
delay_boundandbacklog_boundsays "max over buckets", but the test description in the same task spec says "bound is the min" and the whole pitch is "halves bounds." I implemented the min semantics (the tight closed form) — taking max would always give bounds at least as loose as single-bucket. Documented in the operator docstrings; thedelay_bound_min_across_buckets_each_dominates_a_regimetest directly assertsresult == min(single_bucket_b1, single_bucket_b2).residual_servicefollows the spec literally withR - max ρ_i(the conservative single-bucket-residual lowering) — that's the right call there because the residual must be sound under every individual bucket interpretation, and we're forced to a single rate-latency output.Test plan
cargo build --workspace— cleancargo test --workspace— clean (no regressions; spar-network goes from 77 → 91 lib tests, +14 piecewise)cargo test -p spar-network -- piecewise— 14/14 passcargo clippy --workspace --all-targets -- -D warnings— cleancargo fmt --all -- --check— clean (in the worktree; pre-existing fmt drift onstandard_properties.rsandspar-trace-topologyexists on main and is unrelated)rivet validate— PASS (99 warnings, same as baseline)🤖 Generated with Claude Code