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.
Files changed (413) hide show
  1. {ergodic-0.3.1 → ergodic-0.3.2}/CHANGELOG.md +26 -1
  2. {ergodic-0.3.1 → ergodic-0.3.2}/PKG-INFO +1 -1
  3. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/choose-a-method.md +14 -0
  4. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/discover-a-graph.md +14 -0
  5. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/evaluate-a-graph.md +7 -0
  6. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/install.md +1 -1
  7. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/test-a-graph.md +20 -0
  8. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/time-series-discovery.md +6 -6
  9. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/site.toml +1 -1
  10. ergodic-0.3.2/scripts/audits/qualify_ci_discovery.py +49 -0
  11. ergodic-0.3.2/scripts/audits/qualify_gcm.py +51 -0
  12. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/__init__.py +1 -1
  13. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/_posterior.py +41 -3
  14. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/_stats.py +9 -0
  15. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/algorithms/_mmpc.py +13 -9
  16. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/ci/_gcm.py +8 -2
  17. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/ci/_linear.py +5 -3
  18. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_discovery_ci.py +35 -0
  19. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_discovery_graphtest.py +10 -4
  20. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_discovery_mmpc.py +29 -0
  21. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_graph_posterior.py +43 -0
  22. {ergodic-0.3.1 → ergodic-0.3.2}/.gitignore +0 -0
  23. {ergodic-0.3.1 → ergodic-0.3.2}/CONTRIBUTING.md +0 -0
  24. {ergodic-0.3.1 → ergodic-0.3.2}/LICENSE +0 -0
  25. {ergodic-0.3.1 → ergodic-0.3.2}/NOTICE +0 -0
  26. {ergodic-0.3.1 → ergodic-0.3.2}/README.md +0 -0
  27. {ergodic-0.3.1 → ergodic-0.3.2}/SECURITY.md +0 -0
  28. {ergodic-0.3.1 → ergodic-0.3.2}/docs/case-studies/01-birthweight.py +0 -0
  29. {ergodic-0.3.1 → ergodic-0.3.2}/docs/case-studies/02-berkeley.py +0 -0
  30. {ergodic-0.3.1 → ergodic-0.3.2}/docs/case-studies/03-pension401k.py +0 -0
  31. {ergodic-0.3.1 → ergodic-0.3.2}/docs/case-studies/04-synthetic-control.py +0 -0
  32. {ergodic-0.3.1 → ergodic-0.3.2}/docs/case-studies/05-discovery.py +0 -0
  33. {ergodic-0.3.1 → ergodic-0.3.2}/docs/case-studies/06-clv.py +0 -0
  34. {ergodic-0.3.1 → ergodic-0.3.2}/docs/case-studies/07-churn.py +0 -0
  35. {ergodic-0.3.1 → ergodic-0.3.2}/docs/learn/01-why-causality.py +0 -0
  36. {ergodic-0.3.1 → ergodic-0.3.2}/docs/learn/02-good-and-bad-controls.py +0 -0
  37. {ergodic-0.3.1 → ergodic-0.3.2}/docs/learn/03-estimating-effects.py +0 -0
  38. {ergodic-0.3.1 → ergodic-0.3.2}/docs/learn/04-heterogeneous-effects.py +0 -0
  39. {ergodic-0.3.1 → ergodic-0.3.2}/docs/learn/05-hidden-confounding.py +0 -0
  40. {ergodic-0.3.1 → ergodic-0.3.2}/docs/learn/06-discovering-structure.py +0 -0
  41. {ergodic-0.3.1 → ergodic-0.3.2}/docs/learn/07-quasi-experiments.py +0 -0
  42. {ergodic-0.3.1 → ergodic-0.3.2}/docs/learn/08-process-mining.py +0 -0
  43. {ergodic-0.3.1 → ergodic-0.3.2}/docs/learn/09-process-intelligence.py +0 -0
  44. {ergodic-0.3.1 → ergodic-0.3.2}/docs/learn/10-decision-points.py +0 -0
  45. {ergodic-0.3.1 → ergodic-0.3.2}/docs/learn/11-forecasting.py +0 -0
  46. {ergodic-0.3.1 → ergodic-0.3.2}/docs/learn/12-structural-causal-models.py +0 -0
  47. {ergodic-0.3.1 → ergodic-0.3.2}/docs/learn/13-hidden-regimes.py +0 -0
  48. {ergodic-0.3.1 → ergodic-0.3.2}/docs/learn/14-causal-forests-and-policy-trees.py +0 -0
  49. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/assets/docs.css +0 -0
  50. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/assets/docs.js +0 -0
  51. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/assets/ergodic-mark-dark.svg +0 -0
  52. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/assets/ergodic-mark-light.svg +0 -0
  53. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/assets/fonts/OFL-Geist-Mono.txt +0 -0
  54. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/assets/fonts/OFL-Geist.txt +0 -0
  55. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/assets/fonts/geist-latin-ext-wght-normal.woff2 +0 -0
  56. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/assets/fonts/geist-latin-wght-normal.woff2 +0 -0
  57. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/assets/fonts/geist-mono-latin-ext-wght-normal.woff2 +0 -0
  58. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/assets/fonts/geist-mono-latin-wght-normal.woff2 +0 -0
  59. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/assets/site.css +0 -0
  60. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/assets/tokens.css +0 -0
  61. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/adjustment-sets.md +0 -0
  62. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/assumptions.md +0 -0
  63. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/backtest-a-forecast.md +0 -0
  64. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/bootstrap-stability.md +0 -0
  65. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/certify-and-calibrate.md +0 -0
  66. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/changelog.md +0 -0
  67. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/compatibility.md +0 -0
  68. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/conformance-and-decisions.md +0 -0
  69. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/decode-a-graph.md +0 -0
  70. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/difference-in-differences.md +0 -0
  71. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/domain-knowledge.md +0 -0
  72. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/edge-probabilities.md +0 -0
  73. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/estimate-average-effect.md +0 -0
  74. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/fit-an-scm.md +0 -0
  75. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/forecast-with-drivers.md +0 -0
  76. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/forecast-with-welt.md +0 -0
  77. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/glossary.md +0 -0
  78. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/graphs-and-marks.md +0 -0
  79. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/heterogeneous-effects.md +0 -0
  80. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/identify-an-effect.md +0 -0
  81. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/interventions-and-counterfactuals.md +0 -0
  82. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/map-a-process.md +0 -0
  83. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/mediation.md +0 -0
  84. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/reconcile-a-hierarchy.md +0 -0
  85. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/recover-hidden-regimes.md +0 -0
  86. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/refute-an-estimate.md +0 -0
  87. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/run-scenarios.md +0 -0
  88. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/start.md +0 -0
  89. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/synthetic-control.md +0 -0
  90. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/target-a-policy.md +0 -0
  91. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/pages/welt.md +0 -0
  92. {ergodic-0.3.1 → ergodic-0.3.2}/docsite/provenance.json +0 -0
  93. {ergodic-0.3.1 → ergodic-0.3.2}/examples/README.md +0 -0
  94. {ergodic-0.3.1 → ergodic-0.3.2}/examples/adjustment_birthweight.py +0 -0
  95. {ergodic-0.3.1 → ergodic-0.3.2}/examples/cate/01_cate_basics.py +0 -0
  96. {ergodic-0.3.1 → ergodic-0.3.2}/examples/cate/02_meta_learners_compared.py +0 -0
  97. {ergodic-0.3.1 → ergodic-0.3.2}/examples/cate/03_is_there_heterogeneity.py +0 -0
  98. {ergodic-0.3.1 → ergodic-0.3.2}/examples/cate/04_where_is_the_effect_higher.py +0 -0
  99. {ergodic-0.3.1 → ergodic-0.3.2}/examples/cate/05_targeting_with_rate.py +0 -0
  100. {ergodic-0.3.1 → ergodic-0.3.2}/examples/cate/06_per_unit_uncertainty.py +0 -0
  101. {ergodic-0.3.1 → ergodic-0.3.2}/examples/cate/07_cate_vs_ite.py +0 -0
  102. {ergodic-0.3.1 → ergodic-0.3.2}/examples/cate/08_identification_gating.py +0 -0
  103. {ergodic-0.3.1 → ergodic-0.3.2}/examples/cate/09_case_study_uplift_end_to_end.py +0 -0
  104. {ergodic-0.3.1 → ergodic-0.3.2}/examples/cate/10_causal_forest.py +0 -0
  105. {ergodic-0.3.1 → ergodic-0.3.2}/examples/cate/11_policy_trees.py +0 -0
  106. {ergodic-0.3.1 → ergodic-0.3.2}/examples/cate/12_model_selection.py +0 -0
  107. {ergodic-0.3.1 → ergodic-0.3.2}/examples/cate/13_case_study_phase2.py +0 -0
  108. {ergodic-0.3.1 → ergodic-0.3.2}/examples/cate/README.md +0 -0
  109. {ergodic-0.3.1 → ergodic-0.3.2}/examples/confounding_berkeley.py +0 -0
  110. {ergodic-0.3.1 → ergodic-0.3.2}/examples/discovery_asia.py +0 -0
  111. {ergodic-0.3.1 → ergodic-0.3.2}/examples/double_ml_pension.py +0 -0
  112. {ergodic-0.3.1 → ergodic-0.3.2}/examples/hidden_regimes.py +0 -0
  113. {ergodic-0.3.1 → ergodic-0.3.2}/examples/latent_switch.py +0 -0
  114. {ergodic-0.3.1 → ergodic-0.3.2}/examples/scm/01_three_rungs.py +0 -0
  115. {ergodic-0.3.1 → ergodic-0.3.2}/examples/scm/02_honesty_and_tipping_point.py +0 -0
  116. {ergodic-0.3.1 → ergodic-0.3.2}/examples/scm/03_causal_price_optimization.py +0 -0
  117. {ergodic-0.3.1 → ergodic-0.3.2}/examples/scm/04_counterfactual_recourse.py +0 -0
  118. {ergodic-0.3.1 → ergodic-0.3.2}/examples/scm/05_counterfactual_fairness.py +0 -0
  119. {ergodic-0.3.1 → ergodic-0.3.2}/examples/scm/06_causal_attribution.py +0 -0
  120. {ergodic-0.3.1 → ergodic-0.3.2}/examples/scm/07_but_for_accounting.py +0 -0
  121. {ergodic-0.3.1 → ergodic-0.3.2}/examples/scm/08_off_policy_evaluation.py +0 -0
  122. {ergodic-0.3.1 → ergodic-0.3.2}/examples/scm/09_root_cause_attribution.py +0 -0
  123. {ergodic-0.3.1 → ergodic-0.3.2}/examples/scm/10_frontdoor_unmeasured_confounder.py +0 -0
  124. {ergodic-0.3.1 → ergodic-0.3.2}/examples/scm/README.md +0 -0
  125. {ergodic-0.3.1 → ergodic-0.3.2}/examples/synthetic_control_smoking.py +0 -0
  126. {ergodic-0.3.1 → ergodic-0.3.2}/public/ergodic_logomark_dark.svg +0 -0
  127. {ergodic-0.3.1 → ergodic-0.3.2}/public/ergodic_logomark_light.svg +0 -0
  128. {ergodic-0.3.1 → ergodic-0.3.2}/pyproject.toml +0 -0
  129. {ergodic-0.3.1 → ergodic-0.3.2}/scripts/audits/qualify_aipw.py +0 -0
  130. {ergodic-0.3.1 → ergodic-0.3.2}/scripts/docsite/__init__.py +0 -0
  131. {ergodic-0.3.1 → ergodic-0.3.2}/scripts/docsite/__main__.py +0 -0
  132. {ergodic-0.3.1 → ergodic-0.3.2}/scripts/docsite/build.py +0 -0
  133. {ergodic-0.3.1 → ergodic-0.3.2}/scripts/docsite/config.py +0 -0
  134. {ergodic-0.3.1 → ergodic-0.3.2}/scripts/docsite/examples.py +0 -0
  135. {ergodic-0.3.1 → ergodic-0.3.2}/scripts/docsite/graphite.py +0 -0
  136. {ergodic-0.3.1 → ergodic-0.3.2}/scripts/docsite/links.py +0 -0
  137. {ergodic-0.3.1 → ergodic-0.3.2}/scripts/docsite/notebooks.py +0 -0
  138. {ergodic-0.3.1 → ergodic-0.3.2}/scripts/docsite/reference.py +0 -0
  139. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/data/__init__.py +0 -0
  140. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/data/_dataset.py +0 -0
  141. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/data/_eventlog.py +0 -0
  142. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/data/_frame.py +0 -0
  143. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/data/_hierarchy.py +0 -0
  144. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/data/_hts.py +0 -0
  145. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/data/_panel.py +0 -0
  146. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/data/_schema.py +0 -0
  147. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/data/_tabular.py +0 -0
  148. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/data/_timeseries.py +0 -0
  149. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/datasets/__init__.py +0 -0
  150. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/datasets/_bayesnet.py +0 -0
  151. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/datasets/_bunch.py +0 -0
  152. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/datasets/_bundled.py +0 -0
  153. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/datasets/_tables.py +0 -0
  154. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/datasets/data/birthweight.csv +0 -0
  155. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/datasets/data/germany.csv +0 -0
  156. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/datasets/data/pension401k.csv +0 -0
  157. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/datasets/data/smoking.csv +0 -0
  158. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/__init__.py +0 -0
  159. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/_average.py +0 -0
  160. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/_bootstrap.py +0 -0
  161. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/_ci.py +0 -0
  162. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/_decode.py +0 -0
  163. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/_discover.py +0 -0
  164. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/_encode.py +0 -0
  165. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/_evaluate.py +0 -0
  166. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/_exact.py +0 -0
  167. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/_granger.py +0 -0
  168. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/_graphtest.py +0 -0
  169. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/_lagged.py +0 -0
  170. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/_mcmc.py +0 -0
  171. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/_probabilities.py +0 -0
  172. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/_registry.py +0 -0
  173. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/_result.py +0 -0
  174. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/_score.py +0 -0
  175. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/_target.py +0 -0
  176. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/_tsresult.py +0 -0
  177. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/algorithms/__init__.py +0 -0
  178. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/algorithms/_astar.py +0 -0
  179. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/algorithms/_boss.py +0 -0
  180. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/algorithms/_fci.py +0 -0
  181. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/algorithms/_ges.py +0 -0
  182. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/algorithms/_glasso.py +0 -0
  183. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/algorithms/_grasp.py +0 -0
  184. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/algorithms/_hc.py +0 -0
  185. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/algorithms/_lingam.py +0 -0
  186. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/algorithms/_pc.py +0 -0
  187. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/algorithms/_pcmci.py +0 -0
  188. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/algorithms/_rfci.py +0 -0
  189. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/algorithms/_skeleton.py +0 -0
  190. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/ci/__init__.py +0 -0
  191. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/ci/_discrete.py +0 -0
  192. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/ci/_mixed.py +0 -0
  193. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/scores/__init__.py +0 -0
  194. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/scores/_bdeu.py +0 -0
  195. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/scores/_bge.py +0 -0
  196. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/scores/_bic.py +0 -0
  197. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/scores/_discrete_bic.py +0 -0
  198. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/discovery/scores/_mixed_bic.py +0 -0
  199. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/forecast/__init__.py +0 -0
  200. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/forecast/_attribution.py +0 -0
  201. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/forecast/_backtest.py +0 -0
  202. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/forecast/_baselines.py +0 -0
  203. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/forecast/_facade.py +0 -0
  204. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/forecast/_fit.py +0 -0
  205. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/forecast/_mechanism.py +0 -0
  206. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/forecast/_metrics.py +0 -0
  207. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/forecast/_protocol.py +0 -0
  208. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/forecast/_quantiles.py +0 -0
  209. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/forecast/_reconcile.py +0 -0
  210. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/forecast/_result.py +0 -0
  211. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/forecast/_scalers.py +0 -0
  212. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/forecast/_schedule.py +0 -0
  213. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/forecast/_staged.py +0 -0
  214. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/forecast/_structure.py +0 -0
  215. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/forecast/mechanisms/__init__.py +0 -0
  216. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/forecast/mechanisms/_bass.py +0 -0
  217. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/forecast/mechanisms/_ou.py +0 -0
  218. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/forecast/mechanisms/_structural.py +0 -0
  219. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/graph/__init__.py +0 -0
  220. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/graph/_graph.py +0 -0
  221. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/graph/_io.py +0 -0
  222. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/graph/_marks.py +0 -0
  223. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/graph/_meek.py +0 -0
  224. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/graph/_pag.py +0 -0
  225. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/graph/_primitives.py +0 -0
  226. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/graph/_separation.py +0 -0
  227. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/graph/_surgery.py +0 -0
  228. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/identification/__init__.py +0 -0
  229. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/identification/_criteria.py +0 -0
  230. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/identification/_distribution.py +0 -0
  231. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/identification/_evaluate.py +0 -0
  232. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/identification/_functional.py +0 -0
  233. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/identification/_id.py +0 -0
  234. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/identification/_ida.py +0 -0
  235. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/identification/_mediation.py +0 -0
  236. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/identification/_recognize.py +0 -0
  237. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/identification/_record.py +0 -0
  238. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/__init__.py +0 -0
  239. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/_crossfit.py +0 -0
  240. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/_design.py +0 -0
  241. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/_estimate.py +0 -0
  242. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/_estimator.py +0 -0
  243. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/_learner.py +0 -0
  244. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/_overlap.py +0 -0
  245. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/_perunit.py +0 -0
  246. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/_refute.py +0 -0
  247. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/_registry.py +0 -0
  248. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/_result.py +0 -0
  249. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/_scores.py +0 -0
  250. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/_select.py +0 -0
  251. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/_stats.py +0 -0
  252. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/_target.py +0 -0
  253. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/estimators/__init__.py +0 -0
  254. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/estimators/_did.py +0 -0
  255. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/estimators/_dml.py +0 -0
  256. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/estimators/_doubly_robust.py +0 -0
  257. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/estimators/_forest.py +0 -0
  258. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/estimators/_frontdoor.py +0 -0
  259. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/estimators/_its.py +0 -0
  260. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/estimators/_iv.py +0 -0
  261. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/estimators/_mediation.py +0 -0
  262. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/estimators/_meta.py +0 -0
  263. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/estimators/_null.py +0 -0
  264. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/estimators/_plugin.py +0 -0
  265. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/estimators/_regression.py +0 -0
  266. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/estimators/_synthetic_control.py +0 -0
  267. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/estimators/_weighting.py +0 -0
  268. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/explain/__init__.py +0 -0
  269. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/explain/_blp.py +0 -0
  270. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/explain/_calibration.py +0 -0
  271. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/explain/_clan.py +0 -0
  272. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/explain/_gates.py +0 -0
  273. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/explain/_linalg.py +0 -0
  274. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/explain/_policy.py +0 -0
  275. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/explain/_rate.py +0 -0
  276. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/explain/_record.py +0 -0
  277. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/learners/__init__.py +0 -0
  278. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/learners/_linear.py +0 -0
  279. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/learners/_neighbors.py +0 -0
  280. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/inference/learners/_pymc.py +0 -0
  281. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/knowledge.py +0 -0
  282. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/latent/__init__.py +0 -0
  283. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/latent/_engine.py +0 -0
  284. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/latent/_facade.py +0 -0
  285. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/latent/_identify.py +0 -0
  286. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/latent/_likelihood.py +0 -0
  287. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/latent/_marginalize.py +0 -0
  288. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/latent/_result.py +0 -0
  289. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/latent/likelihoods/__init__.py +0 -0
  290. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/latent/likelihoods/_ar.py +0 -0
  291. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/latent/likelihoods/_spectral.py +0 -0
  292. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/process/__init__.py +0 -0
  293. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/process/_case_table.py +0 -0
  294. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/process/_cases.py +0 -0
  295. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/process/_conformance.py +0 -0
  296. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/process/_decisions.py +0 -0
  297. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/process/_filters.py +0 -0
  298. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/process/_map.py +0 -0
  299. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/process/_mine.py +0 -0
  300. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/process/_net.py +0 -0
  301. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/process/_panel.py +0 -0
  302. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/process/_performance.py +0 -0
  303. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/process/_soundness.py +0 -0
  304. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/process/_tree.py +0 -0
  305. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/process/_variants.py +0 -0
  306. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/py.typed +0 -0
  307. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/scm/__init__.py +0 -0
  308. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/scm/_bounds.py +0 -0
  309. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/scm/_compose.py +0 -0
  310. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/scm/_district.py +0 -0
  311. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/scm/_mechanism.py +0 -0
  312. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/scm/_noise.py +0 -0
  313. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/scm/_result.py +0 -0
  314. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/scm/_sampling.py +0 -0
  315. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/scm/_scm.py +0 -0
  316. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/welt/__init__.py +0 -0
  317. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/welt/_client.py +0 -0
  318. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/welt/_data.py +0 -0
  319. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/welt/_discovery.py +0 -0
  320. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/welt/_forecast.py +0 -0
  321. {ergodic-0.3.1 → ergodic-0.3.2}/src/ergodic/welt/_learner.py +0 -0
  322. {ergodic-0.3.1 → ergodic-0.3.2}/tests/conftest.py +0 -0
  323. {ergodic-0.3.1 → ergodic-0.3.2}/tests/data/exp0626_decoder_parity.json +0 -0
  324. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_average_effect.py +0 -0
  325. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_bayes.py +0 -0
  326. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_case_table.py +0 -0
  327. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_cate.py +0 -0
  328. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_cate_forest.py +0 -0
  329. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_cate_phase2_stress.py +0 -0
  330. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_cate_policy.py +0 -0
  331. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_cate_select.py +0 -0
  332. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_cate_stress.py +0 -0
  333. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_conformance.py +0 -0
  334. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_data.py +0 -0
  335. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_datasets.py +0 -0
  336. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_decisions.py +0 -0
  337. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_decode.py +0 -0
  338. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_decode_knowledge.py +0 -0
  339. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_did.py +0 -0
  340. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_discovery.py +0 -0
  341. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_discovery_bootstrap.py +0 -0
  342. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_discovery_boss.py +0 -0
  343. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_discovery_fci.py +0 -0
  344. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_discovery_ges.py +0 -0
  345. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_discovery_glasso.py +0 -0
  346. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_discovery_grasp.py +0 -0
  347. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_discovery_hc.py +0 -0
  348. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_discovery_lingam.py +0 -0
  349. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_discovery_pc.py +0 -0
  350. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_discovery_pcmci.py +0 -0
  351. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_discovery_rfci.py +0 -0
  352. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_discovery_score.py +0 -0
  353. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_docs_welt_forecast_page.py +0 -0
  354. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_docs_welt_page.py +0 -0
  355. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_docsite.py +0 -0
  356. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_estimators_e2.py +0 -0
  357. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_evaluate_graph.py +0 -0
  358. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_forecast_backtest.py +0 -0
  359. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_forecast_backtest_mcmc.py +0 -0
  360. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_forecast_core.py +0 -0
  361. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_forecast_explain.py +0 -0
  362. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_forecast_facade.py +0 -0
  363. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_forecast_mechanisms.py +0 -0
  364. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_forecast_metrics.py +0 -0
  365. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_forecast_quantiles.py +0 -0
  366. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_forecast_reconcile.py +0 -0
  367. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_forecast_staged.py +0 -0
  368. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_forecast_structure.py +0 -0
  369. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_forecast_zoo.py +0 -0
  370. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_granger.py +0 -0
  371. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_graph.py +0 -0
  372. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_graph_format.py +0 -0
  373. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_graph_primitives.py +0 -0
  374. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_id_algorithm.py +0 -0
  375. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_id_integration.py +0 -0
  376. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_identification.py +0 -0
  377. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_identification_classes.py +0 -0
  378. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_inductive_miner.py +0 -0
  379. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_inference.py +0 -0
  380. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_inference_guardrails.py +0 -0
  381. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_its.py +0 -0
  382. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_knowledge.py +0 -0
  383. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_knowledge_soft.py +0 -0
  384. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_kpi_panel.py +0 -0
  385. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_latent_calibrate.py +0 -0
  386. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_latent_certify.py +0 -0
  387. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_latent_facade.py +0 -0
  388. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_latent_golden.py +0 -0
  389. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_latent_identify.py +0 -0
  390. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_latent_marginalize.py +0 -0
  391. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_mediation.py +0 -0
  392. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_native_learners.py +0 -0
  393. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_pag.py +0 -0
  394. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_plugin.py +0 -0
  395. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_process_filters.py +0 -0
  396. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_process_map.py +0 -0
  397. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_refute.py +0 -0
  398. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_release_cate_regressions.py +0 -0
  399. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_reprs.py +0 -0
  400. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_review_fixes.py +0 -0
  401. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_scm.py +0 -0
  402. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_scm_pymc.py +0 -0
  403. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_score_posterior.py +0 -0
  404. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_sharp_edges.py +0 -0
  405. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_sklearn_interop.py +0 -0
  406. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_smoke.py +0 -0
  407. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_synthetic_control.py +0 -0
  408. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_temporal_wiring.py +0 -0
  409. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_user_facing_wording.py +0 -0
  410. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_welt.py +0 -0
  411. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_welt_forecast.py +0 -0
  412. {ergodic-0.3.1 → ergodic-0.3.2}/tests/test_welt_isolation.py +0 -0
  413. {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.1...HEAD
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ergodic
3
- Version: 0.3.1
3
+ Version: 0.3.2
4
4
  Summary: Causal AI: causal discovery, causal inference, and process intelligence.
5
5
  Project-URL: Homepage, https://docs.ergodic.dev
6
6
  Project-URL: Documentation, https://docs.ergodic.dev
@@ -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.1 writes ergodic.graph/1
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=0
58
- ads[t-1] -> visits[t] p=0
59
- sales[t-1] -> sales[t] p=0
60
- visits[t-2] -> sales[t] p=4.4e-15
61
- visits[t-1] -> visits[t] p=0
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 49
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()
@@ -144,7 +144,7 @@ from .scm import (
144
144
  Sensitivity,
145
145
  )
146
146
 
147
- __version__ = "0.3.1"
147
+ __version__ = "0.3.2"
148
148
 
149
149
  __all__ = [
150
150
  "GRAPH_FORMAT",
@@ -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 and their weights, heaviest first (ties by log target)."""
222
- order = np.lexsort((-self.log_targets, -self.weights))[:k]
223
- return [(self.graph(int(i)), float(self.weights[i])) for i in order]
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 at or below ``alpha``.
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
- best_p = float("inf")
204
- best_x: str | None = None
205
- for x in pool:
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 _norm_cdf
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 = 2.0 * (1.0 - _norm_cdf(abs(stat)))
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 _norm_cdf
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 = 2.0 * (1.0 - _norm_cdf(abs(stat)))
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
- def test_claims_that_underflow_to_zero_rank_by_their_statistic() -> None:
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
- # Both |z| are above 8.3, where Linear's p-value underflows to exactly 0.
468
- assert p == [0.0, 0.0]
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