Coverage for src/causalspyne/benchmark_fci.py: 100%
16 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"""
2Core logic for the paired FCI benchmark (root vs intermediate hidden).
4Factored out of examples/ so it can be unit-tested and reused.
5The key invariant: both scenarios are generated from the same integer seed,
6so they share the same DAG adjacency matrix and the same full data array;
7only the hidden column indices differ.
8"""
10from __future__ import annotations
12import numpy as np
14from causalspyne.main import gen_partially_observed
15from causalspyne.dag_gen_topo_order import RootConfounderDAG
18SCENARIO_HIDDEN = {
19 "root": [0], # topologically first confounder
20 "intermediate": [1.0], # topologically last confounder
21}
24def run_paired_scenarios(
25 seed: int,
26 num_macro_nodes: int = 4,
27 size_micro_node_dag: int = 3,
28 max_num_local_nodes: int = 4,
29 degree: float = 2.0,
30 num_sample: int = 200,
31 output_dir: str = "/tmp/benchmark_fci",
32 strategy_cls=None,
33) -> dict:
34 """
35 Run both scenarios (root hidden, intermediate hidden) on the same DAG.
37 Returns a dict with keys 'root' and 'intermediate', each containing:
38 - 'subview': the DAGView object (observed data + metadata)
39 - 'full_data': np.ndarray of shape (num_sample, num_nodes_total)
40 — the data BEFORE any columns are hidden
41 - 'adj': binary adjacency matrix of the ground-truth DAG
42 - 'hidden': list of global node indices that were hidden
44 Invariant (tested in tests/test_benchmark_fci.py):
45 results['root']['adj'] == results['intermediate']['adj']
46 results['root']['full_data'] == results['intermediate']['full_data']
47 results['root']['hidden'] != results['intermediate']['hidden']
48 """
49 if strategy_cls is None:
50 strategy_cls = RootConfounderDAG
52 results = {}
53 for scenario_name, hidden_spec in SCENARIO_HIDDEN.items():
54 import io, contextlib
55 buf = io.StringIO()
56 with contextlib.redirect_stdout(buf):
57 subview = gen_partially_observed(
58 size_micro_node_dag=size_micro_node_dag,
59 max_num_local_nodes=max_num_local_nodes,
60 num_macro_nodes=num_macro_nodes,
61 degree=degree,
62 list_confounder2hide=hidden_spec,
63 num_sample=num_sample,
64 output_dir=f"{output_dir}/{scenario_name}/seed_{seed}",
65 rng=seed, # integer → fresh RNG → reproducible
66 plot=False,
67 strategy_cls=strategy_cls,
68 )
70 results[scenario_name] = {
71 "subview": subview,
72 "full_data": subview._data_arr, # set by DAGView.run(), pre-hide
73 "adj": (subview.dag.mat_adjacency != 0).astype(int),
74 "hidden": list(subview.list_global_inds_nodes2hide),
75 }
77 return results