antecedent.gcm
Discover-then-fit GCM composition helpers.
Attribution never discovers structure internally (ADR 0012/0015). These helpers
compose discover_* → discovery_to_dag → fit_gcm / attribute_*.
1"""Discover-then-fit GCM composition helpers. 2 3Attribution never discovers structure internally (ADR 0012/0015). These helpers 4compose ``discover_*`` → ``discovery_to_dag`` → ``fit_gcm`` / ``attribute_*``. 5""" 6 7from __future__ import annotations 8 9from typing import Any, Sequence 10 11from ._data import as_columns 12from ._native import ( 13 anomaly_attribution, 14 attribute_distribution_change, 15 attribute_paths, 16 fit_gcm, 17) 18from .discovery import ( 19 FCI, 20 GES, 21 LiNGAM, 22 NOTEARS, 23 PC, 24 RFCI, 25 discover_ges, 26 discover_lingam, 27 discover_notears, 28 discover_pc, 29 discovery_to_dag, 30) 31 32 33def _run_static_discovery(data, discovery, *, seed: int, threads: int): 34 if isinstance(discovery, PC): 35 return discover_pc( 36 data, alpha=discovery.alpha, fdr=discovery.fdr, seed=seed, threads=threads 37 ), "pc" 38 if isinstance(discovery, GES): 39 return discover_ges( 40 data, alpha=discovery.alpha, fdr=discovery.fdr, seed=seed, threads=threads 41 ), "ges" 42 if isinstance(discovery, LiNGAM): 43 return discover_lingam(data, seed=seed, threads=threads), "lingam" 44 if isinstance(discovery, NOTEARS): 45 return discover_notears(data, seed=seed, threads=threads), "notears" 46 if isinstance(discovery, (FCI, RFCI)): 47 algo = "fci" if isinstance(discovery, FCI) else "rfci" 48 raise ValueError( 49 f"{algo}: fit_gcm_discovered requires a fully oriented DAG; " 50 "use PC/GES/LiNGAM/NOTEARS, or orient the PAG and call fit_gcm directly" 51 ) 52 raise TypeError(f"unsupported discovery type for GCM compose: {type(discovery)!r}") 53 54 55def fit_gcm_discovered( 56 data: Any, 57 *, 58 discovery: PC | GES | LiNGAM | NOTEARS, 59 seed: int = 1, 60 threads: int = 1, 61): 62 """Discover structure, coerce to a DAG, then ``fit_gcm``. 63 64 Returns ``(fitted_gcm, graph_edges)``. Incomplete CPDAG/PAG marks raise 65 ``ValueError`` (orientations are never invented). Structure provenance is 66 the caller-supplied ``discovery`` algorithm — attribution does not discover. 67 """ 68 result, _algo = _run_static_discovery(data, discovery, seed=seed, threads=threads) 69 dag = discovery_to_dag(result) 70 names, columns = as_columns(data) 71 edges = list(dag.edges()) 72 fitted = fit_gcm(names, columns, edges, threads=threads) 73 return fitted, edges 74 75 76def attribute_paths_discovered( 77 data: Any, 78 *, 79 discovery: PC | GES | LiNGAM | NOTEARS, 80 sources: Sequence[str], 81 outcome: str, 82 max_paths: int = 64, 83 max_len: int = 16, 84 seed: int = 1, 85 threads: int = 1, 86): 87 """``fit_gcm_discovered`` then ``attribute_paths``. Returns ``(result, graph_edges)``.""" 88 fitted, edges = fit_gcm_discovered( 89 data, discovery=discovery, seed=seed, threads=threads 90 ) 91 _ = fitted 92 names, columns = as_columns(data) 93 result = attribute_paths( 94 names, 95 columns, 96 edges, 97 list(sources), 98 outcome, 99 max_paths=max_paths, 100 max_len=max_len, 101 seed=seed, 102 threads=threads, 103 ) 104 return result, edges 105 106 107def anomaly_attribution_discovered( 108 data: Any, 109 *, 110 discovery: PC | GES | LiNGAM | NOTEARS, 111 outcomes: Sequence[str], 112 max_units: int = 0, 113 seed: int = 1, 114 threads: int = 1, 115): 116 """``fit_gcm_discovered`` then ``anomaly_attribution``. Returns ``(result, graph_edges)``.""" 117 fitted, edges = fit_gcm_discovered( 118 data, discovery=discovery, seed=seed, threads=threads 119 ) 120 _ = fitted 121 names, columns = as_columns(data) 122 result = anomaly_attribution( 123 names, columns, edges, list(outcomes), max_units=max_units 124 ) 125 return result, edges 126 127 128def attribute_distribution_change_discovered( 129 data: Any, 130 *, 131 discovery: PC | GES | LiNGAM | NOTEARS, 132 outcome: str, 133 baseline_start: int, 134 baseline_end: int, 135 comparison_start: int, 136 comparison_end: int, 137 n_samples: int = 500, 138 seed: int = 1, 139 threads: int = 1, 140): 141 """Compose discover → DAG → ``attribute_distribution_change``.""" 142 fitted, edges = fit_gcm_discovered( 143 data, discovery=discovery, seed=seed, threads=threads 144 ) 145 _ = fitted 146 names, columns = as_columns(data) 147 result = attribute_distribution_change( 148 names, 149 columns, 150 edges, 151 outcome, 152 baseline_start, 153 baseline_end, 154 comparison_start, 155 comparison_end, 156 n_samples=n_samples, 157 seed=seed, 158 threads=threads, 159 ) 160 return result, edges 161 162 163__all__ = [ 164 "anomaly_attribution_discovered", 165 "attribute_distribution_change_discovered", 166 "attribute_paths_discovered", 167 "fit_gcm_discovered", 168]
def
anomaly_attribution_discovered( data: Any, *, discovery: antecedent.PC | antecedent.GES | antecedent.LiNGAM | antecedent.NOTEARS, outcomes: Sequence[str], max_units: int = 0, seed: int = 1, threads: int = 1):
108def anomaly_attribution_discovered( 109 data: Any, 110 *, 111 discovery: PC | GES | LiNGAM | NOTEARS, 112 outcomes: Sequence[str], 113 max_units: int = 0, 114 seed: int = 1, 115 threads: int = 1, 116): 117 """``fit_gcm_discovered`` then ``anomaly_attribution``. Returns ``(result, graph_edges)``.""" 118 fitted, edges = fit_gcm_discovered( 119 data, discovery=discovery, seed=seed, threads=threads 120 ) 121 _ = fitted 122 names, columns = as_columns(data) 123 result = anomaly_attribution( 124 names, columns, edges, list(outcomes), max_units=max_units 125 ) 126 return result, edges
fit_gcm_discovered then anomaly_attribution. Returns (result, graph_edges).
def
attribute_distribution_change_discovered( data: Any, *, discovery: antecedent.PC | antecedent.GES | antecedent.LiNGAM | antecedent.NOTEARS, outcome: str, baseline_start: int, baseline_end: int, comparison_start: int, comparison_end: int, n_samples: int = 500, seed: int = 1, threads: int = 1):
129def attribute_distribution_change_discovered( 130 data: Any, 131 *, 132 discovery: PC | GES | LiNGAM | NOTEARS, 133 outcome: str, 134 baseline_start: int, 135 baseline_end: int, 136 comparison_start: int, 137 comparison_end: int, 138 n_samples: int = 500, 139 seed: int = 1, 140 threads: int = 1, 141): 142 """Compose discover → DAG → ``attribute_distribution_change``.""" 143 fitted, edges = fit_gcm_discovered( 144 data, discovery=discovery, seed=seed, threads=threads 145 ) 146 _ = fitted 147 names, columns = as_columns(data) 148 result = attribute_distribution_change( 149 names, 150 columns, 151 edges, 152 outcome, 153 baseline_start, 154 baseline_end, 155 comparison_start, 156 comparison_end, 157 n_samples=n_samples, 158 seed=seed, 159 threads=threads, 160 ) 161 return result, edges
Compose discover → DAG → attribute_distribution_change.
def
attribute_paths_discovered( data: Any, *, discovery: antecedent.PC | antecedent.GES | antecedent.LiNGAM | antecedent.NOTEARS, sources: Sequence[str], outcome: str, max_paths: int = 64, max_len: int = 16, seed: int = 1, threads: int = 1):
77def attribute_paths_discovered( 78 data: Any, 79 *, 80 discovery: PC | GES | LiNGAM | NOTEARS, 81 sources: Sequence[str], 82 outcome: str, 83 max_paths: int = 64, 84 max_len: int = 16, 85 seed: int = 1, 86 threads: int = 1, 87): 88 """``fit_gcm_discovered`` then ``attribute_paths``. Returns ``(result, graph_edges)``.""" 89 fitted, edges = fit_gcm_discovered( 90 data, discovery=discovery, seed=seed, threads=threads 91 ) 92 _ = fitted 93 names, columns = as_columns(data) 94 result = attribute_paths( 95 names, 96 columns, 97 edges, 98 list(sources), 99 outcome, 100 max_paths=max_paths, 101 max_len=max_len, 102 seed=seed, 103 threads=threads, 104 ) 105 return result, edges
fit_gcm_discovered then attribute_paths. Returns (result, graph_edges).
def
fit_gcm_discovered( data: Any, *, discovery: antecedent.PC | antecedent.GES | antecedent.LiNGAM | antecedent.NOTEARS, seed: int = 1, threads: int = 1):
56def fit_gcm_discovered( 57 data: Any, 58 *, 59 discovery: PC | GES | LiNGAM | NOTEARS, 60 seed: int = 1, 61 threads: int = 1, 62): 63 """Discover structure, coerce to a DAG, then ``fit_gcm``. 64 65 Returns ``(fitted_gcm, graph_edges)``. Incomplete CPDAG/PAG marks raise 66 ``ValueError`` (orientations are never invented). Structure provenance is 67 the caller-supplied ``discovery`` algorithm — attribution does not discover. 68 """ 69 result, _algo = _run_static_discovery(data, discovery, seed=seed, threads=threads) 70 dag = discovery_to_dag(result) 71 names, columns = as_columns(data) 72 edges = list(dag.edges()) 73 fitted = fit_gcm(names, columns, edges, threads=threads) 74 return fitted, edges
Discover structure, coerce to a DAG, then fit_gcm.
Returns (fitted_gcm, graph_edges). Incomplete CPDAG/PAG marks raise
ValueError (orientations are never invented). Structure provenance is
the caller-supplied discovery algorithm — attribution does not discover.