ergodic 0.3.1__tar.gz → 0.3.2__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.
- {ergodic-0.3.1 → ergodic-0.3.2}/CHANGELOG.md +26 -1
- {ergodic-0.3.1 → ergodic-0.3.2}/PKG-INFO +1 -1
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/choose-a-method.md +14 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/discover-a-graph.md +14 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/evaluate-a-graph.md +7 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/install.md +1 -1
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/test-a-graph.md +20 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/time-series-discovery.md +6 -6
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/site.toml +1 -1
- ergodic-0.3.2/scripts/audits/qualify_ci_discovery.py +49 -0
- ergodic-0.3.2/scripts/audits/qualify_gcm.py +51 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/__init__.py +1 -1
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/_posterior.py +41 -3
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/_stats.py +9 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/algorithms/_mmpc.py +13 -9
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/ci/_gcm.py +8 -2
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/ci/_linear.py +5 -3
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_discovery_ci.py +35 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_discovery_graphtest.py +10 -4
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_discovery_mmpc.py +29 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_graph_posterior.py +43 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/.gitignore +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/CONTRIBUTING.md +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/LICENSE +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/NOTICE +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/README.md +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/SECURITY.md +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docs/case-studies/01-birthweight.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docs/case-studies/02-berkeley.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docs/case-studies/03-pension401k.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docs/case-studies/04-synthetic-control.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docs/case-studies/05-discovery.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docs/case-studies/06-clv.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docs/case-studies/07-churn.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docs/learn/01-why-causality.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docs/learn/02-good-and-bad-controls.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docs/learn/03-estimating-effects.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docs/learn/04-heterogeneous-effects.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docs/learn/05-hidden-confounding.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docs/learn/06-discovering-structure.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docs/learn/07-quasi-experiments.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docs/learn/08-process-mining.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docs/learn/09-process-intelligence.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docs/learn/10-decision-points.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docs/learn/11-forecasting.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docs/learn/12-structural-causal-models.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docs/learn/13-hidden-regimes.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docs/learn/14-causal-forests-and-policy-trees.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/assets/docs.css +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/assets/docs.js +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/assets/ergodic-mark-dark.svg +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/assets/ergodic-mark-light.svg +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/assets/fonts/OFL-Geist-Mono.txt +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/assets/fonts/OFL-Geist.txt +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/assets/fonts/geist-latin-ext-wght-normal.woff2 +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/assets/fonts/geist-latin-wght-normal.woff2 +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/assets/fonts/geist-mono-latin-ext-wght-normal.woff2 +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/assets/fonts/geist-mono-latin-wght-normal.woff2 +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/assets/site.css +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/assets/tokens.css +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/adjustment-sets.md +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/assumptions.md +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/backtest-a-forecast.md +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/bootstrap-stability.md +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/certify-and-calibrate.md +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/changelog.md +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/compatibility.md +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/conformance-and-decisions.md +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/decode-a-graph.md +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/difference-in-differences.md +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/domain-knowledge.md +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/edge-probabilities.md +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/estimate-average-effect.md +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/fit-an-scm.md +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/forecast-with-drivers.md +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/forecast-with-welt.md +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/glossary.md +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/graphs-and-marks.md +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/heterogeneous-effects.md +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/identify-an-effect.md +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/interventions-and-counterfactuals.md +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/map-a-process.md +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/mediation.md +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/reconcile-a-hierarchy.md +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/recover-hidden-regimes.md +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/refute-an-estimate.md +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/run-scenarios.md +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/start.md +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/synthetic-control.md +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/target-a-policy.md +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/welt.md +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/docsite/provenance.json +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/examples/README.md +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/examples/adjustment_birthweight.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/examples/cate/01_cate_basics.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/examples/cate/02_meta_learners_compared.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/examples/cate/03_is_there_heterogeneity.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/examples/cate/04_where_is_the_effect_higher.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/examples/cate/05_targeting_with_rate.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/examples/cate/06_per_unit_uncertainty.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/examples/cate/07_cate_vs_ite.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/examples/cate/08_identification_gating.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/examples/cate/09_case_study_uplift_end_to_end.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/examples/cate/10_causal_forest.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/examples/cate/11_policy_trees.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/examples/cate/12_model_selection.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/examples/cate/13_case_study_phase2.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/examples/cate/README.md +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/examples/confounding_berkeley.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/examples/discovery_asia.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/examples/double_ml_pension.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/examples/hidden_regimes.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/examples/latent_switch.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/examples/scm/01_three_rungs.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/examples/scm/02_honesty_and_tipping_point.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/examples/scm/03_causal_price_optimization.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/examples/scm/04_counterfactual_recourse.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/examples/scm/05_counterfactual_fairness.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/examples/scm/06_causal_attribution.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/examples/scm/07_but_for_accounting.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/examples/scm/08_off_policy_evaluation.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/examples/scm/09_root_cause_attribution.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/examples/scm/10_frontdoor_unmeasured_confounder.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/examples/scm/README.md +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/examples/synthetic_control_smoking.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/public/ergodic_logomark_dark.svg +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/public/ergodic_logomark_light.svg +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/pyproject.toml +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/scripts/audits/qualify_aipw.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/scripts/docsite/__init__.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/scripts/docsite/__main__.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/scripts/docsite/build.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/scripts/docsite/config.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/scripts/docsite/examples.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/scripts/docsite/graphite.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/scripts/docsite/links.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/scripts/docsite/notebooks.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/scripts/docsite/reference.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/data/__init__.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/data/_dataset.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/data/_eventlog.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/data/_frame.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/data/_hierarchy.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/data/_hts.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/data/_panel.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/data/_schema.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/data/_tabular.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/data/_timeseries.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/datasets/__init__.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/datasets/_bayesnet.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/datasets/_bunch.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/datasets/_bundled.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/datasets/_tables.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/datasets/data/birthweight.csv +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/datasets/data/germany.csv +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/datasets/data/pension401k.csv +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/datasets/data/smoking.csv +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/__init__.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/_average.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/_bootstrap.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/_ci.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/_decode.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/_discover.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/_encode.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/_evaluate.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/_exact.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/_granger.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/_graphtest.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/_lagged.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/_mcmc.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/_probabilities.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/_registry.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/_result.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/_score.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/_target.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/_tsresult.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/algorithms/__init__.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/algorithms/_astar.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/algorithms/_boss.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/algorithms/_fci.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/algorithms/_ges.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/algorithms/_glasso.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/algorithms/_grasp.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/algorithms/_hc.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/algorithms/_lingam.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/algorithms/_pc.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/algorithms/_pcmci.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/algorithms/_rfci.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/algorithms/_skeleton.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/ci/__init__.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/ci/_discrete.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/ci/_mixed.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/scores/__init__.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/scores/_bdeu.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/scores/_bge.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/scores/_bic.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/scores/_discrete_bic.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/scores/_mixed_bic.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/forecast/__init__.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/forecast/_attribution.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/forecast/_backtest.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/forecast/_baselines.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/forecast/_facade.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/forecast/_fit.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/forecast/_mechanism.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/forecast/_metrics.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/forecast/_protocol.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/forecast/_quantiles.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/forecast/_reconcile.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/forecast/_result.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/forecast/_scalers.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/forecast/_schedule.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/forecast/_staged.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/forecast/_structure.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/forecast/mechanisms/__init__.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/forecast/mechanisms/_bass.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/forecast/mechanisms/_ou.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/forecast/mechanisms/_structural.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/graph/__init__.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/graph/_graph.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/graph/_io.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/graph/_marks.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/graph/_meek.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/graph/_pag.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/graph/_primitives.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/graph/_separation.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/graph/_surgery.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/identification/__init__.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/identification/_criteria.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/identification/_distribution.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/identification/_evaluate.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/identification/_functional.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/identification/_id.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/identification/_ida.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/identification/_mediation.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/identification/_recognize.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/identification/_record.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/__init__.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/_crossfit.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/_design.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/_estimate.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/_estimator.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/_learner.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/_overlap.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/_perunit.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/_refute.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/_registry.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/_result.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/_scores.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/_select.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/_stats.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/_target.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/estimators/__init__.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/estimators/_did.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/estimators/_dml.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/estimators/_doubly_robust.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/estimators/_forest.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/estimators/_frontdoor.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/estimators/_its.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/estimators/_iv.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/estimators/_mediation.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/estimators/_meta.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/estimators/_null.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/estimators/_plugin.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/estimators/_regression.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/estimators/_synthetic_control.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/estimators/_weighting.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/explain/__init__.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/explain/_blp.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/explain/_calibration.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/explain/_clan.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/explain/_gates.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/explain/_linalg.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/explain/_policy.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/explain/_rate.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/explain/_record.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/learners/__init__.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/learners/_linear.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/learners/_neighbors.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/learners/_pymc.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/knowledge.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/latent/__init__.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/latent/_engine.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/latent/_facade.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/latent/_identify.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/latent/_likelihood.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/latent/_marginalize.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/latent/_result.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/latent/likelihoods/__init__.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/latent/likelihoods/_ar.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/latent/likelihoods/_spectral.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/process/__init__.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/process/_case_table.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/process/_cases.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/process/_conformance.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/process/_decisions.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/process/_filters.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/process/_map.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/process/_mine.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/process/_net.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/process/_panel.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/process/_performance.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/process/_soundness.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/process/_tree.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/process/_variants.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/py.typed +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/scm/__init__.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/scm/_bounds.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/scm/_compose.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/scm/_district.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/scm/_mechanism.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/scm/_noise.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/scm/_result.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/scm/_sampling.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/scm/_scm.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/welt/__init__.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/welt/_client.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/welt/_data.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/welt/_discovery.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/welt/_forecast.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/welt/_learner.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/conftest.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/data/exp0626_decoder_parity.json +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_average_effect.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_bayes.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_case_table.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_cate.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_cate_forest.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_cate_phase2_stress.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_cate_policy.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_cate_select.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_cate_stress.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_conformance.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_data.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_datasets.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_decisions.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_decode.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_decode_knowledge.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_did.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_discovery.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_discovery_bootstrap.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_discovery_boss.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_discovery_fci.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_discovery_ges.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_discovery_glasso.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_discovery_grasp.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_discovery_hc.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_discovery_lingam.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_discovery_pc.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_discovery_pcmci.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_discovery_rfci.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_discovery_score.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_docs_welt_forecast_page.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_docs_welt_page.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_docsite.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_estimators_e2.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_evaluate_graph.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_forecast_backtest.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_forecast_backtest_mcmc.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_forecast_core.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_forecast_explain.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_forecast_facade.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_forecast_mechanisms.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_forecast_metrics.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_forecast_quantiles.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_forecast_reconcile.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_forecast_staged.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_forecast_structure.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_forecast_zoo.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_granger.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_graph.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_graph_format.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_graph_primitives.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_id_algorithm.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_id_integration.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_identification.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_identification_classes.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_inductive_miner.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_inference.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_inference_guardrails.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_its.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_knowledge.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_knowledge_soft.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_kpi_panel.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_latent_calibrate.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_latent_certify.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_latent_facade.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_latent_golden.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_latent_identify.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_latent_marginalize.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_mediation.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_native_learners.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_pag.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_plugin.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_process_filters.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_process_map.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_refute.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_release_cate_regressions.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_reprs.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_review_fixes.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_scm.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_scm_pymc.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_score_posterior.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_sharp_edges.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_sklearn_interop.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_smoke.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_synthetic_control.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_temporal_wiring.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_user_facing_wording.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_welt.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_welt_forecast.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_welt_isolation.py +0 -0
- {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_workflow_net.py +0 -0
|
@@ -7,6 +7,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.3.2] - 2026-10-24
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- Linear and GCM use `erfc` for normal tails: e.g. z=9 now gives approximately
|
|
14
|
+
2.26e-19 instead of zero. Values below binary64 range still underflow (z=40);
|
|
15
|
+
they are not floored or presented as accurately representable probabilities.
|
|
16
|
+
PCMCI can change its test count: the documented time-series fixture retains
|
|
17
|
+
all five links, while its unconstrained CI queries change from 49 to 47
|
|
18
|
+
(the knowledge-constrained run remains at 37).
|
|
19
|
+
- `GraphSamples.top(k)` groups numerical weight ties at relative tolerance 1e-12,
|
|
20
|
+
then log-target ties at absolute tolerance 1e-9, anchored at each group's largest
|
|
21
|
+
value, and sorts final ties by named directed-edge tuples. Weights are unchanged.
|
|
22
|
+
- MMPC forward associations within absolute p-value distance 1e-15 now tie by
|
|
23
|
+
variable name; alpha decisions keep unrounded p-values. This stabilizes greedy
|
|
24
|
+
admission when tiny tails split, without claiming invariance to variable renaming.
|
|
25
|
+
On 150 registered seven-node worlds, the tie change alone changed no skeletons
|
|
26
|
+
(137 total skeleton errors before and after); PC/FCI/RFCI were also unchanged.
|
|
27
|
+
|
|
28
|
+
### Documentation
|
|
29
|
+
- GCM's default KNN remains uncalibrated. A registered direct-CI study (200 paired
|
|
30
|
+
seeds per family/sample size) found 7.5–12% null rejection at nominal 5%.
|
|
31
|
+
OLS failed on nonlinear conditional means, so neither qualified as a general
|
|
32
|
+
default. Discover and graph-testing guides now disclose the measured limits.
|
|
33
|
+
|
|
10
34
|
## [0.3.1] - 2026-10-20
|
|
11
35
|
|
|
12
36
|
Correctness and inference safeguards from the pre-release audit (API-179 through
|
|
@@ -982,7 +1006,8 @@ inference, discovery, process, SCM, and forecast pillars.
|
|
|
982
1006
|
silently with single-graph semantics; it now dispatches on the face, and
|
|
983
1007
|
mixed graphs raise instead of being misread.
|
|
984
1008
|
|
|
985
|
-
[Unreleased]: https://github.com/ergodic-ai/ergodic/compare/v0.3.
|
|
1009
|
+
[Unreleased]: https://github.com/ergodic-ai/ergodic/compare/v0.3.2...HEAD
|
|
1010
|
+
[0.3.2]: https://github.com/ergodic-ai/ergodic/compare/v0.3.1...v0.3.2
|
|
986
1011
|
[0.3.1]: https://github.com/ergodic-ai/ergodic/compare/v0.3.0...v0.3.1
|
|
987
1012
|
[0.3.0]: https://github.com/ergodic-ai/ergodic/compare/v0.2.1...v0.3.0
|
|
988
1013
|
[0.2.1]: https://github.com/ergodic-ai/ergodic/compare/v0.2.0...v0.2.1
|
|
@@ -171,3 +171,17 @@ and restrict the search to the screen (see
|
|
|
171
171
|
- [Python reference](reference-discovery.md): every method, test and score.
|
|
172
172
|
|
|
173
173
|
</div>
|
|
174
|
+
|
|
175
|
+
!!! caution "GCM learner calibration"
|
|
176
|
+
The default GCM learner remains KNN; it is not a generally calibrated CI test.
|
|
177
|
+
A paired 200-seed direct-CI diagnostic at alpha 0.05 found null rejection rates
|
|
178
|
+
of 9.5%/7.5% (linear), 12%/10% (quadratic), and 10%/9% (sine), at n=200/1000.
|
|
179
|
+
OLS gave 7.5%/5%, 100%/100%, and 99%/100%, respectively: replacing KNN with
|
|
180
|
+
OLS would fail badly for these nonlinear conditional means. Neither qualified
|
|
181
|
+
across all cells of the registered 2–8% investigation band, so the default
|
|
182
|
+
is unchanged. All cells used 200 trials with zero refusals; 95% Wilson intervals
|
|
183
|
+
for the KNN rates span roughly 4.6–17.2%. These are direct-CI results, distinct
|
|
184
|
+
from the earlier ~30% rejection of true graphs by Fisher's combined test.
|
|
185
|
+
Learner choice must match the conditional mean; these diagnostics do not
|
|
186
|
+
establish calibration for arbitrary data or guarantee detection of dependence.
|
|
187
|
+
The reproducible protocol is in `scripts/audits/qualify_gcm.py`.
|
|
@@ -150,3 +150,17 @@ decodes the model's edge probabilities locally. See
|
|
|
150
150
|
- [Python reference](reference-discovery.md): every method and its options.
|
|
151
151
|
|
|
152
152
|
</div>
|
|
153
|
+
|
|
154
|
+
!!! caution "GCM learner calibration"
|
|
155
|
+
The default GCM learner remains KNN; it is not a generally calibrated CI test.
|
|
156
|
+
A paired 200-seed direct-CI diagnostic at alpha 0.05 found null rejection rates
|
|
157
|
+
of 9.5%/7.5% (linear), 12%/10% (quadratic), and 10%/9% (sine), at n=200/1000.
|
|
158
|
+
OLS gave 7.5%/5%, 100%/100%, and 99%/100%, respectively: replacing KNN with
|
|
159
|
+
OLS would fail badly for these nonlinear conditional means. Neither qualified
|
|
160
|
+
across all cells of the registered 2–8% investigation band, so the default
|
|
161
|
+
is unchanged. All cells used 200 trials with zero refusals; 95% Wilson intervals
|
|
162
|
+
for the KNN rates span roughly 4.6–17.2%. These are direct-CI results, distinct
|
|
163
|
+
from the earlier ~30% rejection of true graphs by Fisher's combined test.
|
|
164
|
+
Learner choice must match the conditional mean; these diagnostics do not
|
|
165
|
+
establish calibration for arbitrary data or guarantee detection of dependence.
|
|
166
|
+
The reproducible protocol is in `scripts/audits/qualify_gcm.py`.
|
|
@@ -525,3 +525,10 @@ Nothing touches numpy's global random state, and the same `seed` gives the same
|
|
|
525
525
|
- [Python reference](reference-discovery.md): `graph_posterior`, `evaluate_graph`, `GraphSamples`.
|
|
526
526
|
|
|
527
527
|
</div>
|
|
528
|
+
|
|
529
|
+
`GraphSamples.top(k)` lists the heaviest DAGs first. Weight ties within 1e-12
|
|
530
|
+
relative to the group's maximum are ordered by log target; log-target ties
|
|
531
|
+
within absolute 1e-9 are ordered by sorted named directed edges. Groups are
|
|
532
|
+
anchored at their maximum, not chained through approximate pairwise equality.
|
|
533
|
+
This removes floating-point noise from equivalent-graph ordering without
|
|
534
|
+
rounding returned masses or changing the posterior.
|
|
@@ -72,7 +72,7 @@ for module, use in optional.items():
|
|
|
72
72
|
```
|
|
73
73
|
|
|
74
74
|
```output from="install_check.py" lines="1"
|
|
75
|
-
ergodic 0.3.
|
|
75
|
+
ergodic 0.3.2 writes ergodic.graph/1
|
|
76
76
|
```
|
|
77
77
|
|
|
78
78
|
- **Step 1.** The version, and `ergodic.graph/1`: the format `MixedGraph.to_dict()`
|
|
@@ -210,3 +210,23 @@ own variables, and they hold in the margin over those variables.
|
|
|
210
210
|
- [Python reference](reference-discovery.md): `test_graph` and `GraphTest` in full.
|
|
211
211
|
|
|
212
212
|
</div>
|
|
213
|
+
|
|
214
|
+
!!! caution "GCM learner calibration"
|
|
215
|
+
The default GCM learner remains KNN; it is not a generally calibrated CI test.
|
|
216
|
+
A paired 200-seed direct-CI diagnostic at alpha 0.05 found null rejection rates
|
|
217
|
+
of 9.5%/7.5% (linear), 12%/10% (quadratic), and 10%/9% (sine), at n=200/1000.
|
|
218
|
+
OLS gave 7.5%/5%, 100%/100%, and 99%/100%, respectively: replacing KNN with
|
|
219
|
+
OLS would fail badly for these nonlinear conditional means. Neither qualified
|
|
220
|
+
across all cells of the registered 2–8% investigation band, so the default
|
|
221
|
+
is unchanged. All cells used 200 trials with zero refusals; 95% Wilson intervals
|
|
222
|
+
for the KNN rates span roughly 4.6–17.2%. These are direct-CI results, distinct
|
|
223
|
+
from the earlier ~30% rejection of true graphs by Fisher's combined test.
|
|
224
|
+
Learner choice must match the conditional mean; these diagnostics do not
|
|
225
|
+
establish calibration for arbitrary data or guarantee detection of dependence.
|
|
226
|
+
The reproducible protocol is in `scripts/audits/qualify_gcm.py`.
|
|
227
|
+
|
|
228
|
+
Linear and GCM compute two-sided normal tails with `erfc`, so strong statistics
|
|
229
|
+
such as z=9 retain their nonzero p-values (about 2.26e-19). Tails below binary64's
|
|
230
|
+
representable range still become zero, for example z=40. Graph-test aggregation
|
|
231
|
+
retains its existing safeguards for zero tails; no finite float can represent
|
|
232
|
+
those smaller probabilities accurately.
|
|
@@ -54,14 +54,14 @@ print(f"{pruned} links pruned; {tests[0]} tests instead of {tests[1]}")
|
|
|
54
54
|
|
|
55
55
|
```output from="time_series.py"
|
|
56
56
|
TimeSeriesDiscoveryResult(pcmci/linear: dag on 9 window nodes, max_lag=2, 5 lagged links, 0 contemporaneous, alpha=0.05, n=496)
|
|
57
|
-
ads[t-1] -> ads[t] p=
|
|
58
|
-
ads[t-1] -> visits[t] p=
|
|
59
|
-
sales[t-1] -> sales[t] p=
|
|
60
|
-
visits[t-2] -> sales[t] p=4.
|
|
61
|
-
visits[t-1] -> visits[t] p=
|
|
57
|
+
ads[t-1] -> ads[t] p=1.9e-38
|
|
58
|
+
ads[t-1] -> visits[t] p=6.1e-35
|
|
59
|
+
sales[t-1] -> sales[t] p=6.1e-34
|
|
60
|
+
visits[t-2] -> sales[t] p=4.5e-15
|
|
61
|
+
visits[t-1] -> visits[t] p=1.5e-26
|
|
62
62
|
granger: [('ads', 'visits'), ('visits', 'sales')]
|
|
63
63
|
True
|
|
64
|
-
9 links pruned; 37 tests instead of
|
|
64
|
+
9 links pruned; 37 tests instead of 47
|
|
65
65
|
```
|
|
66
66
|
|
|
67
67
|
- **Step 2.** The result holds a window graph over nodes such as `ads[t-1]`. With
|
|
@@ -31,7 +31,7 @@ commands = [
|
|
|
31
31
|
|
|
32
32
|
[versions]
|
|
33
33
|
# Published versions older than this build that stay in the picker (v<version>/), oldest first.
|
|
34
|
-
previous = ["0.2.0", "0.2.1", "0.3.0"]
|
|
34
|
+
previous = ["0.2.0", "0.2.1", "0.3.0", "0.3.1"]
|
|
35
35
|
|
|
36
36
|
[[products]]
|
|
37
37
|
name = "ergodic"
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Frozen DEC-104 paired discovery diagnostic. Run separately on baseline and candidate."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
from ergodic import discover, tabular
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def main() -> None:
|
|
13
|
+
for seed in range(150):
|
|
14
|
+
rng = np.random.default_rng(seed)
|
|
15
|
+
names = tuple("ABCDEFG")
|
|
16
|
+
coefficients = np.zeros((7, 7))
|
|
17
|
+
for i in range(7):
|
|
18
|
+
for j in range(i + 1, 7):
|
|
19
|
+
if rng.random() < 0.35:
|
|
20
|
+
coefficients[i, j] = rng.choice([-1, 1]) * rng.uniform(0.8, 1.5)
|
|
21
|
+
values = rng.normal(size=(2000, 7))
|
|
22
|
+
for j in range(7):
|
|
23
|
+
values[:, j] += values[:, :j] @ coefficients[:j, j]
|
|
24
|
+
data = tabular(dict(zip(names, values.T, strict=True)))
|
|
25
|
+
reverse = tabular(dict(zip(names[::-1], values.T[::-1], strict=True)))
|
|
26
|
+
truth = {
|
|
27
|
+
tuple(sorted((names[i], names[j])))
|
|
28
|
+
for i, j in zip(*np.nonzero(coefficients), strict=True)
|
|
29
|
+
}
|
|
30
|
+
for method in ("mmpc", "pc", "fci", "rfci"):
|
|
31
|
+
graph = discover(data, method=method, test="linear").graph
|
|
32
|
+
pairs = {tuple(sorted((e.u, e.v))) for e in graph.edges}
|
|
33
|
+
permuted = discover(reverse, method=method, test="linear").graph
|
|
34
|
+
print(
|
|
35
|
+
json.dumps(
|
|
36
|
+
{
|
|
37
|
+
"seed": seed,
|
|
38
|
+
"method": method,
|
|
39
|
+
"edges": sorted(pairs),
|
|
40
|
+
"errors": len(pairs ^ truth),
|
|
41
|
+
"permutation_equal": graph == permuted,
|
|
42
|
+
}
|
|
43
|
+
),
|
|
44
|
+
flush=True,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
if __name__ == "__main__":
|
|
49
|
+
main()
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Frozen API-167 diagnostic; see Welt API DEC-104. JSON output goes outside Git."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
from ergodic.discovery import GCM
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def main() -> None:
|
|
13
|
+
records = []
|
|
14
|
+
for family in ("linear", "quadratic", "sine"):
|
|
15
|
+
for n in (200, 1000):
|
|
16
|
+
for learner in ("knn", "ols"):
|
|
17
|
+
rejected = [0, 0]
|
|
18
|
+
refused = [0, 0]
|
|
19
|
+
for seed in range(200):
|
|
20
|
+
rng = np.random.default_rng(seed)
|
|
21
|
+
z, ex, ey = rng.normal(size=(3, n))
|
|
22
|
+
f = (
|
|
23
|
+
z
|
|
24
|
+
if family == "linear"
|
|
25
|
+
else z**2
|
|
26
|
+
if family == "quadratic"
|
|
27
|
+
else np.sin(2 * z)
|
|
28
|
+
)
|
|
29
|
+
x = (f + ex)[:, None]
|
|
30
|
+
for alternative in (0, 1):
|
|
31
|
+
y = (f + 0.5 * alternative * ex + ey)[:, None]
|
|
32
|
+
try:
|
|
33
|
+
_, p, _ = GCM(learner=learner).statistic(x, y, z[:, None])
|
|
34
|
+
rejected[alternative] += int(p <= 0.05)
|
|
35
|
+
except ValueError:
|
|
36
|
+
refused[alternative] += 1
|
|
37
|
+
records.append(
|
|
38
|
+
{
|
|
39
|
+
"family": family,
|
|
40
|
+
"n": n,
|
|
41
|
+
"learner": learner,
|
|
42
|
+
"rejected": rejected,
|
|
43
|
+
"refused": refused,
|
|
44
|
+
"trials": 200,
|
|
45
|
+
}
|
|
46
|
+
)
|
|
47
|
+
print(json.dumps(records[-1]), flush=True)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
if __name__ == "__main__":
|
|
51
|
+
main()
|
|
@@ -218,9 +218,47 @@ class GraphSamples:
|
|
|
218
218
|
return _to_dag(self.variables, self.adjacency[index])
|
|
219
219
|
|
|
220
220
|
def top(self, k: int = 5) -> list[tuple[MixedGraph, float]]:
|
|
221
|
-
"""The ``k`` heaviest DAGs
|
|
222
|
-
|
|
223
|
-
|
|
221
|
+
"""The ``k`` heaviest DAGs, with canonical ordering for numerical ties.
|
|
222
|
+
|
|
223
|
+
Weight ties span at most 1e-12 times their group's largest weight.
|
|
224
|
+
Within each weight group, log-target ties span at most 1e-9 (absolute).
|
|
225
|
+
Each group is anchored at its largest value, avoiding nontransitive
|
|
226
|
+
pairwise approximate comparisons. Final ties sort by the tuple of named
|
|
227
|
+
directed edges, independent of variable or storage order. Returned
|
|
228
|
+
weights are unchanged; sampled frequencies outside tolerance stay ranked.
|
|
229
|
+
"""
|
|
230
|
+
|
|
231
|
+
def graph_key(i: int) -> tuple[tuple[str, str], ...]:
|
|
232
|
+
sources, targets = np.nonzero(self.adjacency[i])
|
|
233
|
+
return tuple(
|
|
234
|
+
sorted(
|
|
235
|
+
(self.variables[s], self.variables[t])
|
|
236
|
+
for s, t in zip(sources, targets, strict=True)
|
|
237
|
+
)
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
remaining = sorted(range(len(self.weights)), key=lambda i: -float(self.weights[i]))
|
|
241
|
+
order: list[int] = []
|
|
242
|
+
start = 0
|
|
243
|
+
while start < len(remaining):
|
|
244
|
+
anchor = float(self.weights[remaining[start]])
|
|
245
|
+
end = start + 1
|
|
246
|
+
while end < len(remaining) and anchor - self.weights[remaining[end]] <= 1e-12 * anchor:
|
|
247
|
+
end += 1
|
|
248
|
+
group = sorted(remaining[start:end], key=lambda i: -float(self.log_targets[i]))
|
|
249
|
+
log_start = 0
|
|
250
|
+
while log_start < len(group):
|
|
251
|
+
log_anchor = float(self.log_targets[group[log_start]])
|
|
252
|
+
log_end = log_start + 1
|
|
253
|
+
while log_end < len(group):
|
|
254
|
+
value = float(self.log_targets[group[log_end]])
|
|
255
|
+
if value != log_anchor and log_anchor - value > 1e-9:
|
|
256
|
+
break
|
|
257
|
+
log_end += 1
|
|
258
|
+
order.extend(sorted(group[log_start:log_end], key=graph_key))
|
|
259
|
+
log_start = log_end
|
|
260
|
+
start = end
|
|
261
|
+
return [(self.graph(i), float(self.weights[i])) for i in order[:k]]
|
|
224
262
|
|
|
225
263
|
@property
|
|
226
264
|
def best_graph(self) -> MixedGraph:
|
|
@@ -19,6 +19,15 @@ def _norm_cdf(x: float) -> float:
|
|
|
19
19
|
return 0.5 * (1.0 + math.erf(x / math.sqrt(2.0)))
|
|
20
20
|
|
|
21
21
|
|
|
22
|
+
def _norm_two_sided(stat: float) -> float:
|
|
23
|
+
"""Two-sided normal tail without CDF cancellation.
|
|
24
|
+
|
|
25
|
+
Tails below binary64's representable range still round to zero (e.g. |z|=40).
|
|
26
|
+
Unlike 2*(1-cdf), representable tails beyond |z|=8.3 remain positive.
|
|
27
|
+
"""
|
|
28
|
+
return math.erfc(abs(stat) / math.sqrt(2.0))
|
|
29
|
+
|
|
30
|
+
|
|
22
31
|
def _lower_series(s: float, x: float) -> float:
|
|
23
32
|
"""Regularized lower incomplete gamma P(s, x) by series; needs x < s + 1."""
|
|
24
33
|
if x <= 0.0:
|
|
@@ -69,6 +69,11 @@ class MMPC:
|
|
|
69
69
|
sinks) do not constrain an undirected adjacency and are left to the
|
|
70
70
|
search that runs inside the support.
|
|
71
71
|
|
|
72
|
+
Forward associations within absolute p-value distance 1e-15 of the
|
|
73
|
+
strongest eligible candidate tie and are admitted by variable name. The
|
|
74
|
+
significance decision uses the unrounded p-value. This stabilizes numerical
|
|
75
|
+
tails, but does not make the greedy search invariant to variable renaming.
|
|
76
|
+
|
|
72
77
|
``fit(data, knowledge)`` returns a :class:`DiscoveryResult` whose graph
|
|
73
78
|
holds one tail-tail edge per surviving pair, with the ``mixed`` face.
|
|
74
79
|
"""
|
|
@@ -191,7 +196,8 @@ def _parents_children(
|
|
|
191
196
|
never tested, never removed. Each forward round recomputes every
|
|
192
197
|
remaining candidate's worst-case p-value against the current CPC (the
|
|
193
198
|
oracle caches, so revisited subsets cost a lookup) and admits the
|
|
194
|
-
smallest, ties broken by name, while it stays
|
|
199
|
+
smallest, numerical ties within 1e-15 broken by name, while it stays
|
|
200
|
+
at or below ``alpha``.
|
|
195
201
|
The backward phase is the classic single sweep in sorted order: a
|
|
196
202
|
member leaves when some subset of the current CPC without it tests
|
|
197
203
|
independent, and later members see the shrunken set.
|
|
@@ -200,15 +206,13 @@ def _parents_children(
|
|
|
200
206
|
pool = sorted(candidates)
|
|
201
207
|
n_added = 0
|
|
202
208
|
while pool:
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
worst = _worst_p(oracle, x, target, cpc, alpha=alpha, cap=cap)
|
|
207
|
-
if worst < best_p:
|
|
208
|
-
best_p = worst
|
|
209
|
-
best_x = x
|
|
210
|
-
if best_x is None or best_p > alpha:
|
|
209
|
+
associations = [(x, _worst_p(oracle, x, target, cpc, alpha=alpha, cap=cap)) for x in pool]
|
|
210
|
+
eligible = [(x, p) for x, p in associations if p <= alpha]
|
|
211
|
+
if not eligible:
|
|
211
212
|
break
|
|
213
|
+
best_p = min(p for _, p in eligible)
|
|
214
|
+
# Anchor ties at the minimum: pairwise isclose comparisons are not transitive.
|
|
215
|
+
best_x = min(x for x, p in eligible if p - best_p <= 1e-15)
|
|
212
216
|
cpc.add(best_x)
|
|
213
217
|
pool.remove(best_x)
|
|
214
218
|
n_added += 1
|
|
@@ -28,7 +28,7 @@ import numpy as np
|
|
|
28
28
|
from ...inference import KNNRegressor
|
|
29
29
|
from ...inference._learner import _as_learner, _fresh
|
|
30
30
|
from .._ci import CITest
|
|
31
|
-
from .._stats import
|
|
31
|
+
from .._stats import _norm_two_sided
|
|
32
32
|
|
|
33
33
|
if TYPE_CHECKING:
|
|
34
34
|
import numpy.typing as npt
|
|
@@ -51,6 +51,11 @@ class GCM(CITest):
|
|
|
51
51
|
a plain covariance test. Constant endpoints, exact or numerically exact
|
|
52
52
|
fits, and constant residual products raise ``ValueError``: the normal
|
|
53
53
|
reference statistic is undefined, so no independence decision is returned.
|
|
54
|
+
The default KNN learner is not generally calibrated: in a 200-seed
|
|
55
|
+
direct-CI diagnostic it rejected 7.5-12% of nulls at nominal 5% across
|
|
56
|
+
linear, quadratic and sine conditional means (n=200/1000). OLS was not
|
|
57
|
+
a safe general replacement. See the discovery guide for the protocol.
|
|
58
|
+
|
|
54
59
|
An in-sample learner must leave nondegenerate residual variation; this
|
|
55
60
|
check alone does not establish calibration for an arbitrary learner.
|
|
56
61
|
|
|
@@ -63,6 +68,7 @@ class GCM(CITest):
|
|
|
63
68
|
first use.
|
|
64
69
|
"""
|
|
65
70
|
|
|
71
|
+
# Two-sided tails use erfc; below binary64 range (e.g. |z|=40), p is zero.
|
|
66
72
|
name: ClassVar[str] = "gcm"
|
|
67
73
|
needs: ClassVar[str] = "numeric"
|
|
68
74
|
|
|
@@ -107,7 +113,7 @@ class GCM(CITest):
|
|
|
107
113
|
"GCM residual product has zero or numerically degenerate variance; no valid p-value"
|
|
108
114
|
)
|
|
109
115
|
stat = math.sqrt(n) * mean_r / math.sqrt(var_r)
|
|
110
|
-
p =
|
|
116
|
+
p = _norm_two_sided(stat)
|
|
111
117
|
return stat, p, None
|
|
112
118
|
|
|
113
119
|
# -- internals -----------------------------------------------------------
|
|
@@ -17,7 +17,7 @@ from typing import TYPE_CHECKING, ClassVar
|
|
|
17
17
|
import numpy as np
|
|
18
18
|
|
|
19
19
|
from .._ci import CITest
|
|
20
|
-
from .._stats import
|
|
20
|
+
from .._stats import _norm_two_sided
|
|
21
21
|
|
|
22
22
|
if TYPE_CHECKING:
|
|
23
23
|
import numpy.typing as npt
|
|
@@ -40,9 +40,11 @@ class Linear(CITest):
|
|
|
40
40
|
Residualizes x and y on ``[1, z]``, computes the correlation ``r`` of the
|
|
41
41
|
residuals, and reports ``sqrt(n - dz - 3) * atanh(r)`` against the
|
|
42
42
|
standard normal, two-sided. The sign of ``r`` survives in the statistic;
|
|
43
|
-
the p-value does not depend on it.
|
|
43
|
+
the p-value does not depend on it. Tails use erfc to avoid CDF subtraction
|
|
44
|
+
cancellation; below binary64 range (e.g. |z|=40), p still becomes zero.
|
|
44
45
|
"""
|
|
45
46
|
|
|
47
|
+
# Two-sided tails use erfc; below binary64 range (e.g. |z|=40), p is zero.
|
|
46
48
|
name: ClassVar[str] = "linear"
|
|
47
49
|
needs: ClassVar[str] = "numeric"
|
|
48
50
|
|
|
@@ -69,5 +71,5 @@ class Linear(CITest):
|
|
|
69
71
|
r = float(rx @ ry) / denom if denom > 0.0 else 0.0
|
|
70
72
|
r = min(_CLIP, max(-_CLIP, r))
|
|
71
73
|
stat = math.sqrt(n - dz - 3) * math.atanh(r)
|
|
72
|
-
p =
|
|
74
|
+
p = _norm_two_sided(stat)
|
|
73
75
|
return stat, p, None
|
|
@@ -457,3 +457,38 @@ def test_gcm_statistic_does_not_depend_on_measurement_units(given: list[str], sc
|
|
|
457
457
|
def test_gcm_refuses_a_single_observation() -> None:
|
|
458
458
|
with pytest.raises(ValueError, match="at least two complete observations"):
|
|
459
459
|
GCM(tabular({"X": [1.0], "Y": [2.0]})).test("X", "Y")
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
@pytest.mark.parametrize(
|
|
463
|
+
("z", "expected"),
|
|
464
|
+
[
|
|
465
|
+
(8.0, 1.2441921148543568e-15),
|
|
466
|
+
(9.0, 2.2571768119076845e-19),
|
|
467
|
+
(20.0, 5.507248237212467e-89),
|
|
468
|
+
(30.0, 9.813427854296374e-198),
|
|
469
|
+
(38.0, 5.7708567e-316),
|
|
470
|
+
(40.0, 0.0),
|
|
471
|
+
],
|
|
472
|
+
)
|
|
473
|
+
def test_normal_tail_preserves_representable_extreme_probabilities(
|
|
474
|
+
z: float, expected: float
|
|
475
|
+
) -> None:
|
|
476
|
+
from ergodic.discovery._stats import _norm_two_sided
|
|
477
|
+
|
|
478
|
+
for sign in (-1, 1):
|
|
479
|
+
p = _norm_two_sided(sign * z)
|
|
480
|
+
assert p == pytest.approx(expected, rel=1e-7, abs=0.0)
|
|
481
|
+
assert (p > 0) == (z < 40)
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
@pytest.mark.parametrize("test_class", [Linear, GCM])
|
|
485
|
+
def test_ci_consumers_use_survival_tail_for_strong_signal(test_class: type) -> None:
|
|
486
|
+
import math
|
|
487
|
+
|
|
488
|
+
rng = np.random.default_rng(14)
|
|
489
|
+
x = rng.normal(size=400)
|
|
490
|
+
y = x + rng.normal(size=400)
|
|
491
|
+
stat, p, _ = test_class().statistic(x[:, None], y[:, None], np.empty((400, 0)))
|
|
492
|
+
assert 8.5 < abs(stat) < 38
|
|
493
|
+
assert p > 0.0
|
|
494
|
+
assert p == pytest.approx(math.erfc(abs(stat) / math.sqrt(2)), rel=1e-14, abs=0.0)
|
|
@@ -454,9 +454,9 @@ def test_zero_dof_claims_are_vacuous_and_left_out_of_k() -> None:
|
|
|
454
454
|
assert len(result.to_dict()["vacuous"]) == 2
|
|
455
455
|
|
|
456
456
|
|
|
457
|
-
|
|
457
|
+
@pytest.mark.parametrize("n", [1500, 40000])
|
|
458
|
+
def test_strong_claims_rank_by_strength_even_when_tails_underflow(n: int) -> None:
|
|
458
459
|
rng = np.random.default_rng(6)
|
|
459
|
-
n = 1500
|
|
460
460
|
a = rng.normal(size=n)
|
|
461
461
|
b = a + 2.0 * rng.normal(size=n)
|
|
462
462
|
c = b + 3.0 * rng.normal(size=n)
|
|
@@ -464,8 +464,14 @@ def test_claims_that_underflow_to_zero_rank_by_their_statistic() -> None:
|
|
|
464
464
|
result = test_graph(dag(["a -> b"], nodes=["c"]), tabular({"a": a, "b": b, "c": c}))
|
|
465
465
|
p = [i.p_value for i in result.implications]
|
|
466
466
|
stats = [abs(i.statistic) for i in result.implications]
|
|
467
|
-
#
|
|
468
|
-
|
|
467
|
+
# Representable tails survive beyond z=8.3; true binary64 underflow still
|
|
468
|
+
# needs the graph-test statistic tie-break (above z=40 in the larger world).
|
|
469
|
+
if n == 1500:
|
|
470
|
+
assert all(value > 0.0 for value in p)
|
|
471
|
+
assert p == sorted(p)
|
|
472
|
+
else:
|
|
473
|
+
assert min(stats) > 40.0
|
|
474
|
+
assert p == [0.0, 0.0]
|
|
469
475
|
assert min(stats) > 9.0
|
|
470
476
|
assert stats == sorted(stats, reverse=True)
|
|
471
477
|
top = result.implications[0]
|
|
@@ -182,3 +182,32 @@ def test_diagnostics_count_the_phases() -> None:
|
|
|
182
182
|
assert d["n_backward_removed"] >= 0.0
|
|
183
183
|
assert result.alpha == 0.05
|
|
184
184
|
assert result.n == data.n_rows
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def test_mmpc_is_invariant_to_every_column_permutation() -> None:
|
|
188
|
+
from itertools import permutations
|
|
189
|
+
|
|
190
|
+
rng = np.random.default_rng(42)
|
|
191
|
+
a, ex, ey, ez = rng.normal(size=(4, 500))
|
|
192
|
+
frame = pd.DataFrame({"A": a, "B": a + ex, "C": a + ey, "D": a + ex + ez})
|
|
193
|
+
expected = MMPC().fit(tabular(frame)).graph
|
|
194
|
+
for columns in permutations(frame.columns):
|
|
195
|
+
assert MMPC().fit(tabular(frame[list(columns)])).graph == expected
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
@pytest.mark.parametrize("small_tail", [0.0, 1e-30, 1e-16])
|
|
199
|
+
def test_forward_numerical_ties_use_names_without_rounding_alpha(small_tail: float) -> None:
|
|
200
|
+
from ergodic.discovery._ci import CIResult, CITest
|
|
201
|
+
from ergodic.discovery.algorithms._mmpc import _parents_children
|
|
202
|
+
|
|
203
|
+
class TailOracle(CITest):
|
|
204
|
+
def test(self, x: str, y: str, given: object = ()) -> CIResult:
|
|
205
|
+
# A and B explain the same signal; whichever enters first excludes the other.
|
|
206
|
+
p = 1.0 if given else small_tail if x == "A" else 0.0
|
|
207
|
+
return CIResult(9.0, p)
|
|
208
|
+
|
|
209
|
+
result, _, _ = _parents_children(TailOracle(), "T", ["B", "A"], frozenset(), alpha=0.05, cap=3)
|
|
210
|
+
assert result == {"A"}
|
|
211
|
+
# A genuinely above alpha must not enter just because it is within tie tolerance.
|
|
212
|
+
result, _, _ = _parents_children(TailOracle(), "T", ["B", "A"], frozenset(), alpha=1e-32, cap=3)
|
|
213
|
+
assert result == ({"A"} if small_tail == 0.0 else {"B"})
|
|
@@ -371,3 +371,46 @@ def test_exact_refuses_where_it_does_not_apply() -> None:
|
|
|
371
371
|
with warnings.catch_warnings():
|
|
372
372
|
warnings.simplefilter("error")
|
|
373
373
|
assert graph_posterior(probs, max_exact=6, samples=4000, seed=0).method == "mcmc"
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def test_top_numerical_ties_are_canonical_across_storage_and_variable_order() -> None:
|
|
377
|
+
from dataclasses import replace
|
|
378
|
+
from itertools import permutations
|
|
379
|
+
|
|
380
|
+
base = graph_posterior(_probs(3, 0))
|
|
381
|
+
adjacency = np.zeros((3, 3, 3), dtype=bool)
|
|
382
|
+
adjacency[0, 0, 1] = True # A -> B
|
|
383
|
+
adjacency[1, 1, 0] = True # B -> A
|
|
384
|
+
adjacency[2, 0, 2] = True # A -> C
|
|
385
|
+
expected = [dag(["A -> B"], nodes=NAMES[:3]), dag(["A -> C"], nodes=NAMES[:3])]
|
|
386
|
+
for order in permutations(range(3)):
|
|
387
|
+
for variables in ((0, 1, 2), (2, 0, 1)):
|
|
388
|
+
for sign in (-1, 1):
|
|
389
|
+
weights = np.array([1 / 3, 1 / 3 + sign * 1e-14, 1 / 3 - sign * 1e-14])
|
|
390
|
+
logs = np.array([-2.0, -2.0 + sign * 1e-13, -2.0 - sign * 1e-13])
|
|
391
|
+
samples = replace(
|
|
392
|
+
base,
|
|
393
|
+
variables=tuple(NAMES[i] for i in variables),
|
|
394
|
+
adjacency=adjacency[list(order)][:, list(variables)][:, :, list(variables)],
|
|
395
|
+
weights=weights[list(order)],
|
|
396
|
+
log_targets=logs[list(order)],
|
|
397
|
+
)
|
|
398
|
+
result = samples.top(2)
|
|
399
|
+
assert [g for g, _ in result] == expected
|
|
400
|
+
assert [w for _, w in result] == [weights[0], weights[2]]
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def test_top_preserves_distinct_weights_and_log_targets() -> None:
|
|
404
|
+
from dataclasses import replace
|
|
405
|
+
|
|
406
|
+
base = graph_posterior(_probs(3, 0))
|
|
407
|
+
weights = np.arange(1, len(base.weights) + 1, dtype=float)
|
|
408
|
+
weights /= weights.sum()
|
|
409
|
+
samples = replace(base, weights=weights)
|
|
410
|
+
assert samples.top(1)[0] == (samples.graph(len(weights) - 1), weights[-1])
|
|
411
|
+
equal = np.ones(len(weights)) / len(weights)
|
|
412
|
+
logs = np.arange(len(weights), dtype=float)
|
|
413
|
+
samples = replace(base, weights=equal, log_targets=logs)
|
|
414
|
+
assert samples.top(1)[0][0] == samples.graph(len(weights) - 1)
|
|
415
|
+
assert samples.top(0) == []
|
|
416
|
+
assert len(samples.top(len(weights) + 1)) == len(weights)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|