itbound 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. itbound-0.1.0/PKG-INFO +508 -0
  2. itbound-0.1.0/README.md +487 -0
  3. itbound-0.1.0/pyproject.toml +43 -0
  4. itbound-0.1.0/setup.cfg +4 -0
  5. itbound-0.1.0/src/fbound/__init__.py +1 -0
  6. itbound-0.1.0/src/fbound/estimators/__init__.py +17 -0
  7. itbound-0.1.0/src/fbound/estimators/causal_bound.py +1533 -0
  8. itbound-0.1.0/src/fbound/utils/__init__.py +32 -0
  9. itbound-0.1.0/src/fbound/utils/data_generating.py +471 -0
  10. itbound-0.1.0/src/fbound/utils/divergences.py +523 -0
  11. itbound-0.1.0/src/fbound/utils/models.py +312 -0
  12. itbound-0.1.0/src/fbound/utils/plotting.py +11 -0
  13. itbound-0.1.0/src/fbound/utils/result.py +18 -0
  14. itbound-0.1.0/src/fbound/utils/utils.py +199 -0
  15. itbound-0.1.0/src/itbound/__init__.py +22 -0
  16. itbound-0.1.0/src/itbound/__main__.py +5 -0
  17. itbound-0.1.0/src/itbound/api.py +219 -0
  18. itbound-0.1.0/src/itbound/artifacts.py +361 -0
  19. itbound-0.1.0/src/itbound/claims.py +176 -0
  20. itbound-0.1.0/src/itbound/cli.py +562 -0
  21. itbound-0.1.0/src/itbound/config.py +163 -0
  22. itbound-0.1.0/src/itbound/live_demo.py +336 -0
  23. itbound-0.1.0/src/itbound/plotting.py +93 -0
  24. itbound-0.1.0/src/itbound/report.py +108 -0
  25. itbound-0.1.0/src/itbound/reproduce_final_arxiv_plots.py +293 -0
  26. itbound-0.1.0/src/itbound/standard.py +622 -0
  27. itbound-0.1.0/src/itbound.egg-info/PKG-INFO +508 -0
  28. itbound-0.1.0/src/itbound.egg-info/SOURCES.txt +59 -0
  29. itbound-0.1.0/src/itbound.egg-info/dependency_links.txt +1 -0
  30. itbound-0.1.0/src/itbound.egg-info/entry_points.txt +2 -0
  31. itbound-0.1.0/src/itbound.egg-info/requires.txt +14 -0
  32. itbound-0.1.0/src/itbound.egg-info/top_level.txt +2 -0
  33. itbound-0.1.0/tests/test_agents_p0_validity.py +135 -0
  34. itbound-0.1.0/tests/test_aggregation_p0.py +78 -0
  35. itbound-0.1.0/tests/test_artifact_contract.py +97 -0
  36. itbound-0.1.0/tests/test_batching.py +28 -0
  37. itbound-0.1.0/tests/test_claims_engine.py +63 -0
  38. itbound-0.1.0/tests/test_cli_demo.py +237 -0
  39. itbound-0.1.0/tests/test_cli_example.py +28 -0
  40. itbound-0.1.0/tests/test_cli_quick.py +144 -0
  41. itbound-0.1.0/tests/test_cli_reproduce.py +38 -0
  42. itbound-0.1.0/tests/test_cli_run.py +89 -0
  43. itbound-0.1.0/tests/test_config_validation.py +8 -0
  44. itbound-0.1.0/tests/test_docs_assets.py +11 -0
  45. itbound-0.1.0/tests/test_fit_api.py +129 -0
  46. itbound-0.1.0/tests/test_import_itbound.py +6 -0
  47. itbound-0.1.0/tests/test_packaging_metadata_contract.py +20 -0
  48. itbound-0.1.0/tests/test_plotting.py +109 -0
  49. itbound-0.1.0/tests/test_readme_pypi_rendering.py +22 -0
  50. itbound-0.1.0/tests/test_release_ci_contract.py +32 -0
  51. itbound-0.1.0/tests/test_release_docs_contract.py +28 -0
  52. itbound-0.1.0/tests/test_release_external_ops_contract.py +27 -0
  53. itbound-0.1.0/tests/test_release_gate_rehearsal_contract.py +39 -0
  54. itbound-0.1.0/tests/test_release_gitignore_contract.py +10 -0
  55. itbound-0.1.0/tests/test_release_pipeline_contract.py +36 -0
  56. itbound-0.1.0/tests/test_release_preflight_contract.py +23 -0
  57. itbound-0.1.0/tests/test_release_runbook_postrelease_contract.py +12 -0
  58. itbound-0.1.0/tests/test_release_version_sync_contract.py +23 -0
  59. itbound-0.1.0/tests/test_smoke_gate.py +142 -0
  60. itbound-0.1.0/tests/test_standard_library.py +393 -0
  61. itbound-0.1.0/tests/test_trust_pack_regression.py +97 -0
itbound-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,508 @@
1
+ Metadata-Version: 2.4
2
+ Name: itbound
3
+ Version: 0.1.0
4
+ Summary: Data-driven information-theoretic causal bounds under unmeasured confounding.
5
+ Author: Yonghan Jung, Jiwoong Kang
6
+ Keywords: causal-inference,partial-identification,bounds,f-divergence
7
+ Requires-Python: >=3.9
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: numpy
10
+ Requires-Dist: pandas
11
+ Requires-Dist: torch
12
+ Requires-Dist: scikit-learn
13
+ Requires-Dist: pyyaml
14
+ Provides-Extra: experiments
15
+ Requires-Dist: matplotlib; extra == "experiments"
16
+ Requires-Dist: scipy; extra == "experiments"
17
+ Requires-Dist: statsmodels; extra == "experiments"
18
+ Provides-Extra: dev
19
+ Requires-Dist: pytest; extra == "dev"
20
+ Requires-Dist: build; extra == "dev"
21
+
22
+ # Information-Theoretic Causal Bounds under Unmeasured Confounding
23
+
24
+ This repo implements the method in "Information-Theoretic Causal Bounds under Unmeasured Confounding (Jung & Kang, 2026)." It provides lower and upper bounds on causal estimands under unmeasured confounding without bounded outcomes, sensitivity parameters, instruments/proxies, or full SCM specification.
25
+
26
+ Target estimands (paper notation):
27
+
28
+ ```
29
+ θ(a, x) = E_{Q_{a,x}}[φ(Y)], Q_{a,x} = P(Y | do(A=a), X=x)
30
+ θ(a) = E_{Q_a}[φ(Y)]
31
+ ```
32
+
33
+ ## Install (step-by-step, very detailed)
34
+
35
+ Below is a public, copy‑paste friendly guide. The key idea is that `pip install .` installs the package from the **current directory** (this repo) into your **currently active Python environment**.
36
+
37
+ ### 0) Open a terminal and go to the repo root
38
+
39
+ You must run these commands **in the repository root** (the folder containing `pyproject.toml`).
40
+
41
+ If you haven’t cloned the repo yet:
42
+
43
+ ```bash
44
+ git clone https://github.com/yonghanjung/Information-Theretic-Bounds.git
45
+ cd Information-Theretic-Bounds
46
+ ```
47
+
48
+ If you already have the repo locally, just `cd` into it:
49
+
50
+ ```bash
51
+ cd <your-local-repo-path>
52
+ ```
53
+
54
+ Check you are in the right place:
55
+
56
+ ```bash
57
+ ls pyproject.toml
58
+ ```
59
+
60
+ If it prints `pyproject.toml`, you are in the correct directory.
61
+
62
+ ### 1) Create and activate a clean virtual environment (recommended)
63
+
64
+ This keeps dependencies isolated to this project.
65
+
66
+ ```bash
67
+ python -m venv .venv
68
+ ```
69
+
70
+ Activate it:
71
+
72
+ ```bash
73
+ source .venv/bin/activate # macOS/Linux
74
+ ```
75
+
76
+ On Windows (PowerShell):
77
+
78
+ ```powershell
79
+ .venv\\Scripts\\Activate.ps1
80
+ ```
81
+
82
+ On Windows (cmd.exe):
83
+
84
+ ```bat
85
+ .venv\\Scripts\\activate.bat
86
+ ```
87
+
88
+ After activation, your shell prompt usually shows `(.venv)`.
89
+
90
+ ### 2) Upgrade pip inside the venv
91
+
92
+ ```bash
93
+ python -m pip install --upgrade pip
94
+ ```
95
+
96
+ ### 3) Install this package from the repo directory
97
+
98
+ This installs **itbound** into the active environment.
99
+
100
+ ```bash
101
+ python -m pip install .
102
+ ```
103
+
104
+ What this means:
105
+ - `.` means “the current folder.”
106
+ - So `pip install .` tells pip to read `pyproject.toml` here and install the package.
107
+
108
+ ### 4) (Optional) Development install (editable)
109
+
110
+ Use this if you want local code changes to be reflected immediately without reinstalling.
111
+
112
+ ```bash
113
+ python -m pip install -e .
114
+ ```
115
+
116
+ ### 5) (Optional) Extras for figure reproduction
117
+
118
+ If you want to run the `reproduce` command and create figures:
119
+
120
+ ```bash
121
+ python -m pip install '.[experiments]'
122
+ ```
123
+
124
+ ### 6) Quick sanity check
125
+
126
+ ```bash
127
+ python -m itbound --help
128
+ ```
129
+
130
+ If you see the CLI help output, the installation worked.
131
+
132
+ ## 10-Minute Quickstart (Opt-in Wrapper)
133
+
134
+ `quick` is an opt-in wrapper around `itbound.fit(...)`; default math remains paper-equivalent (`mode=paper-default`).
135
+
136
+ ```bash
137
+ python -m venv .venv && source .venv/bin/activate
138
+ python -m pip install --upgrade pip
139
+ python -m pip install .
140
+ python -m itbound example --out /tmp/itbound_example.csv
141
+ python -m itbound quick --data /tmp/itbound_example.csv --treatment a --outcome y --covariates x1,x2 --outdir /tmp/itbound_quick
142
+ ```
143
+
144
+ What the outputs mean:
145
+ - `lower`/`upper`: robust causal bound endpoints, not a single identified point estimate.
146
+ - `width = upper - lower`: interval uncertainty under the allowed confounding set.
147
+ - `valid_interval`: whether each row has a finite valid interval after domain/validity checks.
148
+
149
+ Artifact location:
150
+ - All quick outputs are written under `--outdir` (for example `/tmp/itbound_quick`).
151
+ - Expected files: `summary.txt`, `results.json`, `claims.json`, `claims.md`, `plots/`, optional `report.html`.
152
+ - Details: [`docs/quickstart.md`](docs/quickstart.md), [`docs/artifact_contract.md`](docs/artifact_contract.md), schema [`docs/results_schema_v0.md`](docs/results_schema_v0.md).
153
+
154
+ ## Python API
155
+
156
+ New wrapper (recommended):
157
+
158
+ ```python
159
+ import itbound
160
+ ```
161
+
162
+ Standard-library function:
163
+
164
+ ```python
165
+ from itbound.standard import run_standard_bounds
166
+
167
+ result = run_standard_bounds(
168
+ csv_path="/tmp/itbound_toy.csv",
169
+ outcome_col="y",
170
+ treatment_col="a",
171
+ covariate_cols=["x1", "x2"],
172
+ divergences=["KL", "JS", "Hellinger", "TV", "Chi2"],
173
+ aggregation_mode="paper_adaptive_k",
174
+ outdir="/tmp/itbound_standard",
175
+ write_html=True,
176
+ )
177
+ ```
178
+
179
+ Legacy import (still supported):
180
+
181
+ ```python
182
+ import fbound
183
+ ```
184
+
185
+ ## CLI
186
+
187
+ ### Run bounds from config
188
+
189
+ ```bash
190
+ itbound run --config docs/cli-config.example.yaml
191
+ ```
192
+
193
+ You can override output path:
194
+
195
+ ```bash
196
+ itbound run --config docs/cli-config.example.yaml --out /tmp/itbound_bounds.csv
197
+ ```
198
+
199
+ ### Run a quick synthetic example
200
+
201
+ ```bash
202
+ itbound example --out /tmp/itbound_example.csv
203
+ ```
204
+
205
+ ### Reproduce arXiv plots
206
+
207
+ ```bash
208
+ itbound reproduce --dry-run
209
+ ```
210
+
211
+ Notes:
212
+ - `reproduce` expects the final-arxiv JSON summaries under `experiments/final-arxiv`.
213
+ - If you installed the package, run `itbound reproduce` from the repo root so the data files are found.
214
+ - Install extras first: `pip install .[experiments]`.
215
+
216
+ ### Standard-library run (CSV -> bounds + claims JSON + plots + optional HTML)
217
+
218
+ ```bash
219
+ itbound standard \
220
+ --csv /tmp/itbound_toy.csv \
221
+ --y-col y \
222
+ --a-col a \
223
+ --x-cols x1,x2 \
224
+ --outdir /tmp/itbound_standard \
225
+ --divergences KL,JS,Hellinger,TV,Chi2 \
226
+ --aggregation-mode paper_adaptive_k \
227
+ --html
228
+ ```
229
+
230
+ Aggregation modes:
231
+ - `paper_adaptive_k` (default): increase endpoint-k until feasible interval is found.
232
+ - `fixed_k_endpoint`: use exactly `--fixed-k` for both endpoints.
233
+ - `tight_kth`: experiment-style tight-kth aggregation (starts from large k, relaxes down).
234
+ - default divergences for this mode are the same built-in 5: `KL,JS,Hellinger,TV,Chi2`.
235
+ - you can override to a subset with `--divergences` (for example `KL,TV`).
236
+
237
+ Ground-truth plot overlay options (visualization only; math unchanged):
238
+ - `--ground-truth-col <col>`: draw per-row truth values from a column.
239
+ - `--ground-truth-effect <float>`: draw a scalar horizontal truth line.
240
+ - `--no-ground-truth-auto`: disable auto detection from `mu1-mu0`.
241
+ - default behavior auto-detects `mu1-mu0` when available.
242
+
243
+ Example (`tight_kth`):
244
+
245
+ ```bash
246
+ itbound standard \
247
+ --csv /tmp/itbound_toy.csv \
248
+ --y-col y \
249
+ --a-col a \
250
+ --x-cols x1,x2 \
251
+ --outdir /tmp/itbound_standard_tight \
252
+ --aggregation-mode tight_kth
253
+ ```
254
+
255
+ Example (`tight_kth` subset override + explicit truth column):
256
+
257
+ ```bash
258
+ itbound standard \
259
+ --csv /tmp/itbound_toy.csv \
260
+ --y-col y \
261
+ --a-col a \
262
+ --x-cols x1,x2 \
263
+ --outdir /tmp/itbound_standard_tight_kltv \
264
+ --divergences KL,TV \
265
+ --aggregation-mode tight_kth \
266
+ --ground-truth-col tau_true
267
+ ```
268
+
269
+ Artifacts written to `--outdir`:
270
+ - `bounds.csv`
271
+ - `summary.json` (claims + diagnostics + run config)
272
+ - plot PNGs when `matplotlib` is available
273
+ - `report.html` when `--html` is enabled
274
+
275
+ If `matplotlib` is missing, plotting is skipped with a warning and other artifacts are still produced.
276
+ See also: [`docs/aggregation_modes.md`](docs/aggregation_modes.md)
277
+
278
+ ### Artifact Contract Run (schema-versioned `results.json`)
279
+
280
+ ```bash
281
+ itbound artifacts \
282
+ --csv /tmp/itbound_toy.csv \
283
+ --y-col y \
284
+ --a-col a \
285
+ --x-cols x1,x2 \
286
+ --outdir /tmp/itbound_artifacts \
287
+ --divergences KL
288
+ ```
289
+
290
+ This opt-in command writes a fixed folder contract:
291
+ - `summary.txt`
292
+ - `results.json` (schema versioned; see `docs/results_schema_v0.md`)
293
+ - `claims.json`
294
+ - `claims.md`
295
+ - `plots/`
296
+ - `report.html` (optional with `--html`)
297
+
298
+ ### Quick Wrapper Run (`itbound.fit` via CLI)
299
+
300
+ ```bash
301
+ itbound quick \
302
+ --data /tmp/itbound_toy.csv \
303
+ --treatment a \
304
+ --outcome y \
305
+ --covariates x1,x2 \
306
+ --outdir /tmp/itbound_quick
307
+ ```
308
+
309
+ `quick` is an opt-in wrapper around `itbound.fit(...)` with default `mode=paper-default`,
310
+ and writes the same artifact contract (`summary.txt`, `results.json`, `claims.json`, `claims.md`, `plots/`).
311
+
312
+ ### Live Demo (Toy + Benchmark)
313
+
314
+ IHDP scenario preview (GIF rendered from the actual CLI run):
315
+
316
+ ![itbound demo toy live preview](https://raw.githubusercontent.com/yonghanjung/Information-Theretic-Bounds/main/docs/media/quick-demo-v5.gif)
317
+
318
+ Run a fast IHDP demo with explicit KL + truth-coverage envelope:
319
+
320
+ ```bash
321
+ itbound demo --scenario ihdp --divergence KL --enforce-truth-coverage --eval-points 240 --rounds 5 --n-folds 5 --outdir /tmp/itbound_live_demo --batch-size 8
322
+ ```
323
+
324
+ Run on a benchmark-style IHDP CSV:
325
+
326
+ ```bash
327
+ itbound demo --scenario ihdp --ihdp-data /path/to/ihdp_npci_1.csv --divergence KL --enforce-truth-coverage --eval-points 240 --rounds 5 --n-folds 5 --outdir /tmp/itbound_live_demo --batch-size 8
328
+ ```
329
+
330
+ Run both in one command:
331
+
332
+ ```bash
333
+ itbound demo --scenario both --toy-n 1000 --divergence KL --enforce-truth-coverage --eval-points 240 --rounds 5 --n-folds 5 --outdir /tmp/itbound_live_demo --batch-size 8
334
+ ```
335
+
336
+ The demo writes per-scenario artifact folders (`toy/`, `ihdp/`) plus `live_demo_summary.md` under `--outdir`.
337
+ By default, IHDP plots use a demo-only truth-aware envelope so every point satisfies `lower < truth < upper` visually.
338
+ Use `--no-enforce-truth-coverage` to disable this visualization aid.
339
+ Use `--eval-points` (e.g., `240`) to render fewer evaluation points for smoother, less spiky demo plots.
340
+
341
+ To regenerate the GIF:
342
+
343
+ ```bash
344
+ bash scripts/demo/make_quick_demo.sh
345
+ ```
346
+
347
+ ## Good Example (End-to-End)
348
+
349
+ Install, run a quick example, and verify the output columns:
350
+
351
+ ```bash
352
+ pip install .
353
+ itbound example --out /tmp/itbound_example.csv
354
+
355
+ python - <<'PY'
356
+ import pandas as pd
357
+ df = pd.read_csv("/tmp/itbound_example.csv")
358
+ print(df.columns.tolist())
359
+ PY
360
+ ```
361
+
362
+ Expected columns include `lower` and `upper`.
363
+
364
+ ## Example Diagram
365
+
366
+ ```mermaid
367
+ flowchart LR
368
+ A[Config YAML/JSON] --> B[Data Loader]
369
+ B --> C[Bound Estimator]
370
+ C --> D[Bounds CSV]
371
+ B --> E[Diagnostics]
372
+ C --> E
373
+ ```
374
+
375
+ ## Example Plot
376
+
377
+ ![Bound width example (all divergences)](https://raw.githubusercontent.com/yonghanjung/Information-Theretic-Bounds/main/docs/latex/figures/width-alldivergences.png)
378
+
379
+ ![IHDP ribbon example](https://raw.githubusercontent.com/yonghanjung/Information-Theretic-Bounds/main/docs/latex/figures/ribbon_ihdp.png)
380
+
381
+ ## Config schema (CLI)
382
+
383
+ The config must be YAML or JSON and include:
384
+
385
+ - `data`: one of `synthetic`, `npz_path`, or `csv_path`
386
+ - `divergence`
387
+ - `propensity_model`
388
+ - `m_model`
389
+ - `dual_net_config`
390
+ - `fit_config`
391
+ - `seed`
392
+
393
+ Optional:
394
+ - `phi` (default: `identity`)
395
+ - `output_path` (default: `itbound_bounds.csv`)
396
+
397
+ See `docs/cli-config.example.yaml` for a complete example.
398
+
399
+ For a full field-by-field explanation, see `docs/config.md`.
400
+
401
+ ## Toy CSV example
402
+
403
+ Create a toy CSV and run:
404
+
405
+ ```bash
406
+ python - <<'PY'
407
+ import numpy as np
408
+ import pandas as pd
409
+
410
+ rng = np.random.default_rng(0)
411
+ n = 50
412
+ x1 = rng.normal(size=n)
413
+ x2 = rng.normal(size=n)
414
+ a = (x1 + rng.normal(scale=0.5, size=n) > 0).astype(int)
415
+ y = 1.0 + 0.5 * a + 0.3 * x1 - 0.2 * x2 + rng.normal(scale=0.1, size=n)
416
+
417
+ df = pd.DataFrame({"y": y, "a": a, "x1": x1, "x2": x2})
418
+ df.to_csv("/tmp/itbound_toy.csv", index=False)
419
+ print("/tmp/itbound_toy.csv")
420
+ PY
421
+
422
+ cat <<'YAML' > /tmp/itbound_toy.yaml
423
+ data:
424
+ csv_path: /tmp/itbound_toy.csv
425
+ y_col: y
426
+ a_col: a
427
+ x_cols: [x1, x2]
428
+ divergence: KL
429
+ phi: identity
430
+ propensity_model: logistic
431
+ m_model: linear
432
+ dual_net_config:
433
+ hidden_sizes: [8, 8]
434
+ activation: relu
435
+ dropout: 0.0
436
+ h_clip: 10.0
437
+ device: cpu
438
+ fit_config:
439
+ n_folds: 2
440
+ num_epochs: 2
441
+ batch_size: 16
442
+ lr: 0.005
443
+ weight_decay: 0.0
444
+ max_grad_norm: 5.0
445
+ eps_propensity: 0.001
446
+ deterministic_torch: true
447
+ train_m_on_fold: true
448
+ propensity_config:
449
+ C: 1.0
450
+ max_iter: 200
451
+ penalty: l2
452
+ solver: lbfgs
453
+ n_jobs: 1
454
+ m_config:
455
+ alpha: 1.0
456
+ verbose: false
457
+ log_every: 1
458
+ seed: 123
459
+ YAML
460
+
461
+ itbound run --config /tmp/itbound_toy.yaml --out /tmp/itbound_toy_bounds.csv
462
+ ```
463
+
464
+ ## Agent Skill
465
+
466
+ Agent-friendly usage is documented in `docs/agent/SKILL.md`.
467
+
468
+ ## Theory at a glance (ITB.pdf)
469
+
470
+ ### Divergence bound from propensity (Theorem 1)
471
+
472
+ For any action `a` and covariates `x` with `P(a|x) > 0`:
473
+
474
+ ```
475
+ D_f(P_{a,x} || Q_{a,x}) <= B_f(e_a(x)), B_f(e) = e f(1/e) + (1-e) f(0)
476
+ ```
477
+
478
+ This upper bound depends only on the propensity score, making the divergence radius fully propensity-score-based.
479
+
480
+ Specializations used in the code:
481
+ - `KL`: `D_KL(P||Q) <= -log e`
482
+ - `Hellinger`: `D_H(P||Q) <= 1 - sqrt(e)`
483
+ - `Chi2`: `D_chi2(P||Q) <= (1-e)/(2e)`
484
+ - `TV`: `D_TV(P||Q) <= 1 - e`
485
+ - `JS`: `D_JS(P||Q) <= B_fJS(e)` (closed form in the paper)
486
+
487
+ ### Dual causal bound (Theorem 2)
488
+
489
+ Define `g(s)=s f(1/s)` and its convex conjugate `g*(t)`. The upper bound solves:
490
+
491
+ ```
492
+ θ_up(a,x) = inf_{λ>0, u in R} { λ η_f(a,x) + u + λ E_{P_{a,x}}[g*((φ(Y)-u)/λ)] }
493
+ ```
494
+
495
+ ### Debiased semiparametric estimator (Section 5)
496
+
497
+ The code minimizes the paper's risk function (Definition 4) with cross-fitting and a Neyman-orthogonal correction, using:
498
+ - PyTorch dual nets for `h(a,x)` and `u(a,x)` with `λ(a,x)=exp(h(a,x))`
499
+ - sklearn propensity + outcome regressors for nuisances
500
+
501
+ ## Diagnostics (AGENTS P0)
502
+
503
+ Endpoint-wise aggregation reports per-point diagnostics:
504
+ - `n_eff_up`, `n_eff_lo`: effective candidate counts after filtering.
505
+ - `k_used_up`, `k_used_lo`: order-statistic index used (default 1).
506
+ - `invalid_up`, `invalid_lo`, `nonfinite_upper`, `nonfinite_lower`, `inverted_filtered`: rejection counts.
507
+
508
+ Run example outputs include validity masks and these diagnostics in the saved tables.