Coverage for src/causalspyne/main.py: 54%
96 statements
« prev ^ index » next coverage.py v7.11.0, created at 2026-05-26 05:29 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2026-05-26 05:29 +0000
1"""
2generate DAG and its marginal DAG
3"""
5from datetime import datetime
6try:
7 from contextlib import chdir
8except Exception:
9 from causalspyne.py3_9_10_compatibility import chdir
11from pathlib import Path
13import matplotlib.pyplot as plt
15from causalspyne.gen_dag_2level import GenDAG2Level
16from causalspyne.dag_gen import GenDAG
17from causalspyne.dag_gen_topo_order import RootConfounderDAG
18from causalspyne.dag_viewer import DAGView
19from causalspyne.dag2ancestral import DAG2Ancestral
21from causalspyne.draw_dags import draw_dags_nx
22from causalspyne.utils_random import coerce_rng
25def gen_partially_observed(
26 degree=2,
27 list_confounder2hide=None,
28 size_micro_node_dag=4,
29 max_num_local_nodes=4,
30 num_macro_nodes=4,
31 num_sample=200,
32 output_dir="output/",
33 rng=None,
34 dft_noise="Gaussian",
35 graphviz=False,
36 plot=True,
37 strategy_cls=None,
38):
39 """
40 sole function as user interface
41 strategy_cls: optional skeleton generator class (default: Erdos_Renyi_PLP)
42 """
43 if list_confounder2hide is None:
44 list_confounder2hide = [0.5, 0.9]
45 rng = coerce_rng(rng)
46 timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_")
47 output_dir = Path(output_dir)
48 output_dir.mkdir(parents=True, exist_ok=True)
50 simple_dag_gen = GenDAG(num_nodes=size_micro_node_dag,
51 degree=degree, rng=rng, strategy_cls=strategy_cls)
53 dag_gen = GenDAG2Level(
54 dag_generator=simple_dag_gen,
55 num_macro_nodes=num_macro_nodes,
56 num_micro_nodes=size_micro_node_dag,
57 max_num_local_nodes=max_num_local_nodes,
58 rng=rng,
59 )
60 dag = dag_gen.run()
61 dag.to_binary_csv(benchpress=False,
62 name=output_dir / f"ground_truth_dag_{timestamp}d.csv")
64 subview = DAGView(dag=dag, rng=rng, dft_noise=dft_noise)
65 return re_hide(subview, dag, num_sample, list_confounder2hide, output_dir,
66 graphviz, timestamp, plot=plot)
69def gen_root_confounder_hidden(
70 degree=2,
71 size_micro_node_dag=4,
72 max_num_local_nodes=4,
73 num_macro_nodes=4,
74 num_sample=200,
75 output_dir="output/",
76 rng=None,
77 dft_noise="Gaussian",
78 graphviz=False,
79 plot=True,
80):
81 """
82 FCI benchmark where the root macro node (guaranteed confounder) is
83 entirely hidden. Uses RootConfounderDAG for the macro backbone so the
84 root always confounds at least two other macro nodes, then hides all
85 micro nodes belonging to that root macro cluster.
86 """
87 rng = coerce_rng(rng)
88 timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_")
89 output_dir = Path(output_dir)
90 output_dir.mkdir(parents=True, exist_ok=True)
92 simple_dag_gen = GenDAG(
93 num_nodes=size_micro_node_dag,
94 degree=degree,
95 rng=rng,
96 strategy_cls=RootConfounderDAG,
97 )
99 dag_gen = GenDAG2Level(
100 dag_generator=simple_dag_gen,
101 num_macro_nodes=num_macro_nodes,
102 num_micro_nodes=size_micro_node_dag,
103 max_num_local_nodes=max_num_local_nodes,
104 rng=rng,
105 )
106 dag = dag_gen.run()
107 dag.to_binary_csv(benchpress=False,
108 name=output_dir / f"ground_truth_dag_{timestamp}d.csv")
110 # hide all micro nodes of every root macro node
111 root_names = dag_gen.get_root_macro_names()
112 list_global_inds_to_hide = []
113 for name in root_names:
114 list_global_inds_to_hide.extend(dag_gen.get_macro_node_global_inds(name))
116 subview = DAGView(dag=dag, rng=rng, dft_noise=dft_noise)
117 # DAGView.hide_top_order expects topological-order positions, not global indices
118 topo_positions = [dag.list_ind_nodes_sorted.index(g)
119 for g in list_global_inds_to_hide]
120 subview._data_arr = subview.data_gen.gen(num_sample)
121 subview.hide_top_order(topo_positions)
123 return re_hide_by_inds(subview, dag, list_global_inds_to_hide,
124 output_dir, graphviz, timestamp, plot=plot)
127def re_hide_by_inds(subview, dag, list_global_inds_to_hide,
128 output_dir, graphviz, timestamp, plot=True):
129 """Like re_hide but uses pre-computed global indices instead of percentages."""
130 dag2ancestral = DAG2Ancestral(dag.mat_adjacency)
131 pred_ancestral_graph_mat = dag2ancestral.run(list_global_inds_to_hide)
133 if plot:
134 fig, (ax1, ax2, ax3) = plt.subplots(1, 3)
135 str_hidden = "_".join(str(i) for i in list_global_inds_to_hide)
136 mtitle = "hide_root_" + str_hidden
137 fig.suptitle(mtitle)
139 dag.visualize(title="DAG", ax=ax1, graphviz=graphviz)
140 ax1.set_title("DAG")
142 draw_dags_nx(
143 pred_ancestral_graph_mat,
144 dict_ind2name={
145 i: name for i, name in enumerate(sorted(subview.node_names))
146 },
147 title="ancestral",
148 ax=ax2,
149 graphviz=graphviz,
150 )
151 ax2.set_title("ancestral")
153 subview.visualize(title="subDAG", ax=ax3, graphviz=graphviz)
154 ax3.set_title("subDAG")
156 with chdir(output_dir):
157 subview.to_csv()
158 if plot:
159 fig.savefig(f"graph_compare_{timestamp}dags.pdf", format="pdf")
160 fig.savefig(f"graph_compare_{timestamp}dags.svg", format="svg")
161 plt.close(fig)
162 with open("hidden_nodes.csv", "w") as outfile:
163 outfile.write(",".join(str(i) for i in list_global_inds_to_hide))
164 return subview
167def ordered_ind_col2global_ind(inds_cols, subview_global_inds):
168 """
169 given a predicted causal order in the form of column indices, transform it
170 into global index of ground truth DAG
171 """
172 list_global_inds = [subview_global_inds[ind_col] for ind_col in inds_cols]
173 return list_global_inds
176def re_hide(subview, dag, num_sample, list_confounder2hide, output_dir,
177 graphviz, timestamp, plot=True):
178 subview.run(
179 num_samples=num_sample, confound=True,
180 list_nodes2hide=list_confounder2hide
181 )
182 str_node2hide = subview.str_node2hide
184 dag2ancestral = DAG2Ancestral(dag.mat_adjacency)
185 list_confounder2hide_global_ind = subview.list_global_inds_nodes2hide
186 pred_ancestral_graph_mat = dag2ancestral.run(
187 list_confounder2hide_global_ind)
189 if plot:
190 fig, (ax1, ax2, ax3) = plt.subplots(1, 3)
191 mtitle = "hide_" + str_node2hide
192 fig.suptitle(mtitle) # super-title
194 # ax1
195 dag.visualize(title="DAG", ax=ax1, graphviz=graphviz)
196 ax1.set_title("DAG")
198 # ax2
199 draw_dags_nx(
200 pred_ancestral_graph_mat,
201 dict_ind2name={
202 i: name for i, name in enumerate(sorted(subview.node_names))
203 },
204 title="ancestral",
205 ax=ax2,
206 graphviz=graphviz,
207 )
208 ax2.set_title("ancestral")
209 # ax3
210 subview.visualize(
211 title="subDAG", ax=ax3, graphviz=graphviz
212 )
213 ax3.set_title("subDAG")
215 with chdir(output_dir):
216 subview.to_csv()
217 if plot:
218 fig.savefig(f"graph_compare_{timestamp}dags.pdf", format="pdf")
219 fig.savefig(f"graph_compare_{timestamp}dags.svg", format="svg")
220 plt.close(fig)
221 with open("hidden_nodes.csv", "w") as outfile:
222 outfile.write(
223 ",".join(str(node) for node in
224 subview._list_global_inds_unobserved)
225 )
226 return subview