44import logging
55import os
66import tempfile
7+ from importlib .metadata import entry_points
78from pathlib import Path
89
10+ import networkx as nx
11+ import pandas as pd
12+
913from causal_testing .testing .metamorphic_relation import generate_causal_tests
1014
1115from .main import CausalTestingFramework , CausalTestingPaths , Command , parse_args , setup_logging
@@ -20,74 +24,113 @@ def main() -> None:
2024
2125 # Parse arguments
2226 args = parse_args ()
23-
24- if args .command == Command .GENERATE :
25- logging .info ("Generating causal tests" )
26- generate_causal_tests (
27- args .dag_path ,
28- args .output ,
29- args .ignore_cycles ,
30- args .threads ,
31- effect_type = args .effect_type ,
32- estimate_type = args .estimate_type ,
33- estimator = args .estimator ,
34- skip = False ,
35- )
36- logging .info ("Causal test generation completed successfully" )
37- return
38-
3927 # Setup logging
40- setup_logging (args .verbose )
41-
42- # Create paths object
43- paths = CausalTestingPaths (
44- dag_path = args .dag_path ,
45- data_paths = args .data_paths ,
46- test_config_path = args .test_config ,
47- output_path = args .output ,
48- )
49-
50- # Create and setup framework
51- framework = CausalTestingFramework (paths , ignore_cycles = args .ignore_cycles , query = args .query )
52- framework .setup ()
53-
54- # Load and run tests
55- framework .load_tests ()
56-
57- if args .batch_size > 0 :
58- logging .info (f"Running tests in batches of size { args .batch_size } " )
59- with tempfile .TemporaryDirectory () as tmpdir :
60- output_files = []
61- for i , results in enumerate (
62- framework .run_tests_in_batches (
63- batch_size = args .batch_size ,
64- silent = args .silent ,
65- adequacy = args .adequacy ,
66- bootstrap_size = args .bootstrap_size ,
28+ setup_logging (args .log_level )
29+
30+ match args .command :
31+ case Command .GENERATE :
32+ logging .info ("Generating causal tests" )
33+ generate_causal_tests (
34+ args .dag_path ,
35+ args .output ,
36+ args .ignore_cycles ,
37+ args .threads ,
38+ effect_type = args .effect_type ,
39+ estimate_type = args .estimate_type ,
40+ estimator = args .estimator ,
41+ skip = False ,
42+ )
43+ logging .info ("Causal test generation completed successfully." )
44+
45+ case Command .DISCOVER :
46+ discover_map = {ff .name : ff for ff in entry_points (group = "discovery" )}
47+ if args .technique not in discover_map :
48+ raise ValueError (
49+ f"Unsupported technique { args .technique } . Supported: { sorted (discover_map )} . "
50+ "If you have implemented a custom technique, you will need to add this to your entrypoints via "
51+ "your pyproject.toml file."
6752 )
68- ):
69- temp_file_path = os .path .join (tmpdir , f"output_{ i } .json" )
70- framework .save_results (results , temp_file_path )
71- output_files .append (temp_file_path )
72- del results
73-
74- # Now stitch the results together from the temporary files
75- all_results = []
76- for file_path in output_files :
77- with open (file_path , "r" , encoding = "utf-8" ) as f :
78- all_results .extend (json .load (f ))
79-
80- output_path = Path (args .output )
81- output_path .parent .mkdir (parents = True , exist_ok = True )
82-
83- with open (args .output , "w" , encoding = "utf-8" ) as f :
84- json .dump (all_results , f , indent = 4 )
85- else :
86- logging .info ("Running tests in regular mode" )
87- results = framework .run_tests (silent = args .silent , adequacy = args .adequacy , bootstrap_size = args .bootstrap_size )
88- framework .save_results (results )
89-
90- logging .info ("Causal testing completed successfully." )
53+ kwargs = {}
54+ for argument in args .technique_kwargs :
55+ split = argument .split ("=" )
56+ if len (split ) != 2 :
57+ raise ValueError (f"Malformed argument { argument } . Should be specified as `arg_name=arg_value`" )
58+ kwargs [split [0 ]] = split [1 ]
59+
60+ logging .info ("Discovering causal structure" )
61+ # Need to reset index to allow for multiple files having the same index (i.e. starting at zero).
62+ # Otherwise you end up with duplicate indices, which causes problems further down the line
63+ df = pd .concat ([pd .read_csv (path ) for path in args .data_paths ]).reset_index ()
64+ if args .variables :
65+ df = df [args .variables ]
66+
67+ discover_class = discover_map [args .technique ].load ()
68+ discover = discover_class (
69+ df = df ,
70+ exclude_edges = (
71+ list (nx .nx_pydot .read_dot (args .exclude_edges ).edges ()) if args .exclude_edges is not None else []
72+ ),
73+ include_edges = (
74+ list (nx .nx_pydot .read_dot (args .include_edges ).edges ()) if args .include_edges is not None else []
75+ ),
76+ alpha = args .alpha ,
77+ ** kwargs ,
78+ )
79+ evolved_dag = discover .discover ()
80+ discover .write_dot (evolved_dag , args .output )
81+ logging .info ("Causal structure discovery completed successfully." )
82+ case Command .TEST :
83+ # Create paths object
84+ paths = CausalTestingPaths (
85+ dag_path = args .dag_path ,
86+ data_paths = args .data_paths ,
87+ test_config_path = args .test_config ,
88+ output_path = args .output ,
89+ )
90+
91+ # Create and setup framework
92+ framework = CausalTestingFramework (paths , ignore_cycles = args .ignore_cycles , query = args .query )
93+ framework .setup ()
94+
95+ # Load and run tests
96+ framework .load_tests ()
97+
98+ if args .batch_size > 0 :
99+ logging .info (f"Running tests in batches of size { args .batch_size } " )
100+ with tempfile .TemporaryDirectory () as tmpdir :
101+ output_files = []
102+ for i , results in enumerate (
103+ framework .run_tests_in_batches (
104+ batch_size = args .batch_size ,
105+ silent = args .silent ,
106+ adequacy = args .adequacy ,
107+ bootstrap_size = args .bootstrap_size ,
108+ )
109+ ):
110+ temp_file_path = os .path .join (tmpdir , f"output_{ i } .json" )
111+ framework .save_results (results , temp_file_path )
112+ output_files .append (temp_file_path )
113+ del results
114+
115+ # Now stitch the results together from the temporary files
116+ all_results = []
117+ for file_path in output_files :
118+ with open (file_path , "r" , encoding = "utf-8" ) as f :
119+ all_results .extend (json .load (f ))
120+
121+ output_path = Path (args .output )
122+ output_path .parent .mkdir (parents = True , exist_ok = True )
123+
124+ with open (args .output , "w" , encoding = "utf-8" ) as f :
125+ json .dump (all_results , f , indent = 4 )
126+ else :
127+ logging .info ("Running tests in regular mode" )
128+ results = framework .run_tests (
129+ silent = args .silent , adequacy = args .adequacy , bootstrap_size = args .bootstrap_size
130+ )
131+ framework .save_results (results )
132+
133+ logging .info ("Causal testing completed successfully." )
91134
92135
93136if __name__ == "__main__" :
0 commit comments