feat(zipf): power-law tail + Heaps fit; veto Chao2 saturation gate - #22
Conversation
core/zipf.py: discrete power-law MLE (Clauset et al.) on seeds-per-edge counts, capped at m seeds; min-KS xmin scan, Vuong vs geometric, tail-fraction guard -> TailLaw. Local Hurwitz zeta. Heaps' law fit on the coverage timeline. Wired: EdgeTracker.zipf_estimate() (memoized on owner-count version) and heaps_estimate(); run summary, --report "Zipf Tail", stats file "zipf". Saturation gate no longer engages while the tail is Zipf and Heaps beta >= 0.05: Chao2 undercounts power-law tails. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UbKUguEPpHswuuUbhCXSuT
Reviewer's GuideThis PR introduces a scipy-free, deterministic Zipf-tail and Heaps-growth analysis, wires memoized estimates into tracker, reporting, and stats paths, and uses the validated growth signal to veto Chao2 saturation gating only after Chao2 reaches saturation; extensive statistical, adversarial, integration, and output tests cover the implementation. Sequence diagram for memoized tracker estimatessequenceDiagram
participant Caller
participant EdgeTracker
participant ZipfModule as ZipfModule
Caller->>EdgeTracker: zipf_estimate()
alt cached (_owner_version, seed_count)
EdgeTracker-->>Caller: ZipfFit
else cache miss
EdgeTracker->>ZipfModule: fit_zipf(owner_counts, xmax=seed_count)
ZipfModule-->>EdgeTracker: ZipfFit
EdgeTracker-->>Caller: ZipfFit
end
Caller->>EdgeTracker: heaps_estimate()
EdgeTracker->>ZipfModule: fit_heaps(coverage_execs, coverage_edges)
ZipfModule-->>EdgeTracker: HeapsFit
EdgeTracker-->>Caller: HeapsFit
Flow diagram for Zipf tail and Heaps fittingflowchart LR
A["Seeds per edge counts"] --> B["Aggregate positive counts"]
B --> C["Scan xmin candidates <= 32"]
C --> D["Fit alpha with vectorized MLE"]
D --> E["Select minimum KS fit"]
E --> F["Vuong test vs geometric"]
F --> G["Tail fraction guard"]
G --> H["TailLaw verdict"]
I["Coverage timeline"] --> J["Fit D(N) = k * N^beta"]
J --> K["HeapsFit"]
Flow diagram for the Zipf-aware saturation gateflowchart TD
A["Chao2 saturation >= 0.99"] -->|No| B["Do not engage saturation gate"]
A -->|Yes| C["zipf_estimate().law == POWER_LAW"]
C -->|No| D["Apply normal saturation gate"]
C -->|Yes| E["heaps_estimate()"]
E --> F["beta >= 0.05 and r2 >= 0.9"]
F -->|Yes| G["Veto saturation gate"]
F -->|No| D
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Unresolved critical and moderate issues affect bounded-tail correctness, cache validity, fit cost, and Heaps reporting.
Review effort: Lite
Findings: 1
Open (1)
What changed in this PR
Adds Zipf tail and Heaps-law analysis, wiring results into tracking, reports, stats, and saturation gating.
Changes:
- Implements deterministic power-law and Heaps fitting.
- Adds caching and saturation-gate integration.
- Updates tests, reports, statistics, documentation, and architecture diagrams.
| File | Review summary |
|---|---|
tests/test_zipf.py |
Adds estimator and adversarial tests. |
tests/test_zipf_wiring.py |
Adds integration and output tests. |
src/fuzzer_tool/services/stats.py |
Adds stats output; two moderate issues remain (1 vote each) regarding cost gating and independent Heaps output. |
src/fuzzer_tool/services/seed_picker.py |
Adds the Zipf-based saturation veto. |
src/fuzzer_tool/services/report.py |
Adds the report section; one moderate issue remains (1 vote) regarding independent Heaps rendering. |
src/fuzzer_tool/core/zipf.py |
Adds fitting algorithms; one critical issue (3 votes) concerns bounded geometric normalization, and one moderate issue (1 vote) concerns xmax filtering. |
src/fuzzer_tool/core/edge_tracker.py |
Adds estimator APIs and caching; two moderate issues remain (1 vote each) regarding cost gating and resize cache invalidation. |
docs/TODO.md |
Documents follow-up calibration work. |
docs/images/architecture.svg |
Regenerated architecture diagram. |
docs/DEEP_DIVE.md |
Documents the new analysis and gating behavior. |
docs/architecture.dot |
Updates architecture source. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| # Geometric on the shifted tail, MLE p = 1 / (1 + mean shift). | ||
| p = 1.0 / (1.0 + float((shift * w).sum() / n)) | ||
| ll_geo = math.log(p) + shift * math.log1p(-p) if p < 1.0 else np.zeros_like(shift) |

What
core/zipf.py: fits a discrete power law to seeds-per-edge counts (Clauset–Shalizi–Newman MLE), capped at m seeds, and fits Heaps' law to the coverage timeline.TailLawverdict. The guard exists because lognormal data passes Vuong.Wiring
EdgeTracker.zipf_estimate()(memoized on an owner-count version) andheaps_estimate().Zipf tail:andHeaps:lines, next to the Chao2 lines.--report: new "Zipf Tail" section after the Chao2 section.--stats-file: newzipfentry.SeedPicker._saturation_gateno longer switches on while the tail isPOWER_LAWand Heaps β ≥ZIPF_GROWTH_BETA(0.05, R² ≥ 0.9). Chao2 is only a lower bound when the tail is a power law, so its "saturated" reading can't be trusted there. The fit only runs when Chao2 already reads ≥ 0.99.DEEP_DIVE.md,TODO.md,architecture.dotand the png/svg rebuilt from it.Tests
tests/test_zipf.py: fixtures are deterministic, built from each model's PMF, so no RNG is involved.tests/test_zipf_wiring.py: tracker fit and cache, including a prune plus new seed that leaves all totals unchanged; the gate veto with its falsification and adversarial cases; the report section and stats output.TestFuzzerWiringconstructor-flag-order tests (test_kruskal_count,test_novelty_confirm,test_seed_round_robin). Those three also fail on master and are unrelated to this change.zipf.py, and lizard CCN 15 are all clean. All pre-commit hooks pass, including impactguard.ci.ymltriggers onmain, and the base ismaster.Cost
Calibration (fuzzgoat, clang,
_noasan.so)On this machine the ASAN builds fail for every target, not just fuzzgoat.
End-to-end at 20k execs:
Zipf tail: s=4.08 (alpha=1.25, power_law)beta=0.21, 2x execs -> +16.0% edgesThe veto never fired: the gate needs Chao2 ≥ 0.99, which short runs don't reach.
Speed, master vs branch, same seeds, 20k execs, 4 runs in parallel on 4 cores:
Peak eps is the same within noise. Average eps and edges follow each run's own path: the runs diverge early (crash replays, cmplog load up to 4.1M comparisons), so these numbers say nothing about the change. That is expected: below saturation the branch adds no per-exec work, only a version-counter increment in
record_edges. This is a speed check, not a coverage A/B. Measuring the veto's effect on coverage needs long campaigns and a CLI toggle; that is tracked indocs/TODO.md.🤖 Generated with Claude Code
https://claude.ai/code/session_01UbKUguEPpHswuuUbhCXSuT