Coverage for src/causalspyne/main.py: 54%
96 statements
« prev ^ index » next coverage.py v7.11.0, created at 2026-07-23 14:40 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2026-07-23 14:40 +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 min_num_local_nodes=3,
31 num_macro_nodes=4,
32 num_sample=200,
33 output_dir="output/",
34 rng=None,
35 dft_noise="Gaussian",
36 graphviz=False,
37 plot=True,
38 strategy_cls=None,
39):
40 """
41 sole function as user interface
42 strategy_cls: optional skeleton generator class (default: Erdos_Renyi_PLP)
43 """
44 if list_confounder2hide is None:
45 list_confounder2hide = [0.5, 0.9]
46 rng = coerce_rng(rng)
47 timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_")
48 output_dir = Path(output_dir)
49 output_dir.mkdir(parents=True, exist_ok=True)
51 simple_dag_gen = GenDAG(num_nodes=size_micro_node_dag,
52 degree=degree, rng=rng, strategy_cls=strategy_cls)
54 dag_gen = GenDAG2Level(
55 dag_generator=simple_dag_gen,
56 num_macro_nodes=num_macro_nodes,
57 num_micro_nodes=size_micro_node_dag,
58 max_num_local_nodes=max_num_local_nodes,
59 min_num_local_nodes=min_num_local_nodes,
60 rng=rng,
61 )
62 dag = dag_gen.run()
63 dag.to_binary_csv(benchpress=False,
64 name=output_dir / f"ground_truth_dag_{timestamp}d.csv")
66 subview = DAGView(dag=dag, rng=rng, dft_noise=dft_noise)
67 return re_hide(subview, dag, num_sample, list_confounder2hide, output_dir,
68 graphviz, timestamp, plot=plot)
71def gen_root_confounder_hidden(
72 degree=2,
73 size_micro_node_dag=4,
74 max_num_local_nodes=4,
75 num_macro_nodes=4,
76 num_sample=200,
77 output_dir="output/",
78 rng=None,
79 dft_noise="Gaussian",
80 graphviz=False,
81 plot=True,
82):
83 """
84 FCI benchmark where the root macro node (guaranteed confounder) is
85 entirely hidden. Uses RootConfounderDAG for the macro backbone so the
86 root always confounds at least two other macro nodes, then hides all
87 micro nodes belonging to that root macro cluster.
88 """
89 rng = coerce_rng(rng)
90 timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_")
91 output_dir = Path(output_dir)
92 output_dir.mkdir(parents=True, exist_ok=True)
94 simple_dag_gen = GenDAG(
95 num_nodes=size_micro_node_dag,
96 degree=degree,
97 rng=rng,
98 strategy_cls=RootConfounderDAG,
99 )
101 dag_gen = GenDAG2Level(
102 dag_generator=simple_dag_gen,
103 num_macro_nodes=num_macro_nodes,
104 num_micro_nodes=size_micro_node_dag,
105 max_num_local_nodes=max_num_local_nodes,
106 rng=rng,
107 )
108 dag = dag_gen.run()
109 dag.to_binary_csv(benchpress=False,
110 name=output_dir / f"ground_truth_dag_{timestamp}d.csv")
112 # hide all micro nodes of every root macro node
113 root_names = dag_gen.get_root_macro_names()
114 list_global_inds_to_hide = []
115 for name in root_names:
116 list_global_inds_to_hide.extend(dag_gen.get_macro_node_global_inds(name))
118 subview = DAGView(dag=dag, rng=rng, dft_noise=dft_noise)
119 # DAGView.hide_top_order expects topological-order positions, not global indices
120 topo_positions = [dag.list_ind_nodes_sorted.index(g)
121 for g in list_global_inds_to_hide]
122 subview._data_arr = subview.data_gen.gen(num_sample)
123 subview.hide_top_order(topo_positions)
125 return re_hide_by_inds(subview, dag, list_global_inds_to_hide,
126 output_dir, graphviz, timestamp, plot=plot)
129def re_hide_by_inds(subview, dag, list_global_inds_to_hide,
130 output_dir, graphviz, timestamp, plot=True):
131 """Like re_hide but uses pre-computed global indices instead of percentages."""
132 dag2ancestral = DAG2Ancestral(dag.mat_adjacency)
133 pred_ancestral_graph_mat = dag2ancestral.run(list_global_inds_to_hide)
135 if plot:
136 fig, (ax1, ax2, ax3) = plt.subplots(1, 3)
137 str_hidden = "_".join(str(i) for i in list_global_inds_to_hide)
138 mtitle = "hide_root_" + str_hidden
139 fig.suptitle(mtitle)
141 dag.visualize(title="DAG", ax=ax1, graphviz=graphviz)
142 ax1.set_title("DAG")
144 draw_dags_nx(
145 pred_ancestral_graph_mat,
146 dict_ind2name={
147 i: name for i, name in enumerate(sorted(subview.node_names))
148 },
149 title="ancestral",
150 ax=ax2,
151 graphviz=graphviz,
152 )
153 ax2.set_title("ancestral")
155 subview.visualize(title="subDAG", ax=ax3, graphviz=graphviz)
156 ax3.set_title("subDAG")
158 with chdir(output_dir):
159 subview.to_csv()
160 if plot:
161 fig.savefig(f"graph_compare_{timestamp}dags.pdf", format="pdf")
162 fig.savefig(f"graph_compare_{timestamp}dags.svg", format="svg")
163 plt.close(fig)
164 with open("hidden_nodes.csv", "w") as outfile:
165 outfile.write(",".join(str(i) for i in list_global_inds_to_hide))
166 return subview
169def ordered_ind_col2global_ind(inds_cols, subview_global_inds):
170 """
171 given a predicted causal order in the form of column indices, transform it
172 into global index of ground truth DAG
173 """
174 list_global_inds = [subview_global_inds[ind_col] for ind_col in inds_cols]
175 return list_global_inds
178def re_hide(subview, dag, num_sample, list_confounder2hide, output_dir,
179 graphviz, timestamp, plot=True):
180 subview.run(
181 num_samples=num_sample, confound=True,
182 list_nodes2hide=list_confounder2hide
183 )
184 str_node2hide = subview.str_node2hide
186 dag2ancestral = DAG2Ancestral(dag.mat_adjacency)
187 list_confounder2hide_global_ind = subview.list_global_inds_nodes2hide
188 pred_ancestral_graph_mat = dag2ancestral.run(
189 list_confounder2hide_global_ind)
191 if plot:
192 fig, (ax1, ax2, ax3) = plt.subplots(1, 3)
193 mtitle = "hide_" + str_node2hide
194 fig.suptitle(mtitle) # super-title
196 # ax1
197 dag.visualize(title="DAG", ax=ax1, graphviz=graphviz)
198 ax1.set_title("DAG")
200 # ax2
201 draw_dags_nx(
202 pred_ancestral_graph_mat,
203 dict_ind2name={
204 i: name for i, name in enumerate(sorted(subview.node_names))
205 },
206 title="ancestral",
207 ax=ax2,
208 graphviz=graphviz,
209 )
210 ax2.set_title("ancestral")
211 # ax3
212 subview.visualize(
213 title="subDAG", ax=ax3, graphviz=graphviz
214 )
215 ax3.set_title("subDAG")
217 with chdir(output_dir):
218 subview.to_csv()
219 if plot:
220 fig.savefig(f"graph_compare_{timestamp}dags.pdf", format="pdf")
221 fig.savefig(f"graph_compare_{timestamp}dags.svg", format="svg")
222 plt.close(fig)
223 with open("hidden_nodes.csv", "w") as outfile:
224 outfile.write(
225 ",".join(str(node) for node in
226 subview._list_global_inds_unobserved)
227 )
228 return subview