├── .gitignore ├── CovMatCalculator.py ├── DotTool.py ├── FBackCovMatCalculator.py ├── FBackGEmanager.py ├── FBackGainsCalculator.py ├── FBackGainsEstimator.py ├── FBackGraph.py ├── FBackRandomDataMaker.py ├── GainsCalculator.py ├── GainsEstimator.py ├── Graph.py ├── MIT-License.txt ├── README.md ├── RandomDataMaker.py ├── core_matrices.py ├── dot_atlas ├── README.md ├── __init__.py ├── back-door.dot ├── fback-2node.dot ├── front-door.dot ├── good_bad_controls.txt ├── good_bad_trols_G1.dot ├── good_bad_trols_G10.dot ├── good_bad_trols_G11.dot ├── good_bad_trols_G11u.dot ├── good_bad_trols_G12.dot ├── good_bad_trols_G13.dot ├── good_bad_trols_G14.dot ├── good_bad_trols_G15.dot ├── good_bad_trols_G16.dot ├── good_bad_trols_G17.dot ├── good_bad_trols_G18.dot ├── good_bad_trols_G2.dot ├── good_bad_trols_G3.dot ├── good_bad_trols_G4.dot ├── good_bad_trols_G5.dot ├── good_bad_trols_G6.dot ├── good_bad_trols_G7.dot ├── good_bad_trols_G7up.dot ├── good_bad_trols_G8.dot ├── good_bad_trols_G9.dot ├── linear-naive-bayes-3x.dot ├── linear-regression-3x.dot ├── napkin.dot ├── potential-outcomes.dot ├── table-2-fallacy-GM.dot ├── table-2-fallacy-GMZ.dot ├── table-2-fallacy-GZ.dot └── unconfounded-children.dot ├── jupyter_notebooks ├── G1-covariance-matrix.ipynb ├── G1-gains.ipynb ├── G10-covariance-matrix.ipynb ├── G10-gains.ipynb ├── G11-covariance-matrix.ipynb ├── G11-gains.ipynb ├── G11u-covariance-matrix.ipynb ├── G11u-gains.ipynb ├── G12-covariance-matrix.ipynb ├── G12-gains.ipynb ├── G13-covariance-matrix.ipynb ├── G13-gains.ipynb ├── G14-covariance-matrix.ipynb ├── G14-gains.ipynb ├── G15-covariance-matrix.ipynb ├── G15-gains.ipynb ├── G16-covariance-matrix.ipynb ├── G16-gains.ipynb ├── G17-covariance-matrix.ipynb ├── G17-gains.ipynb ├── G18-covariance-matrix.ipynb ├── G18-gains.ipynb ├── G2-covariance-matrix.ipynb ├── G2-gains.ipynb ├── G3-covariance-matrix.ipynb ├── G3-gains.ipynb ├── G4-covariance-matrix.ipynb ├── G4-gains.ipynb ├── G5-covariance-matrix.ipynb ├── G5-gains.ipynb ├── G6-covariance-matrix.ipynb ├── G6-gains.ipynb ├── G7-covariance-matrix.ipynb ├── G7-gains.ipynb ├── G7up-covariance-matrix.ipynb ├── G7up-gains.ipynb ├── G8-covariance-matrix.ipynb ├── G8-gains.ipynb ├── G9-covariance-matrix.ipynb ├── G9-gains.ipynb ├── SUMMARY.ipynb ├── back-door.ipynb ├── estimating-gains.ipynb ├── fback-2node-cov-mat.ipynb ├── fback-2node-gains.ipynb ├── fback-estimating-gains.ipynb ├── front-door.ipynb ├── linear-naive-bayes-3x.ipynb ├── linear-regression-3x.ipynb ├── napkin.ipynb ├── potential-outcomes.ipynb ├── r493.pdf ├── scumpyREADME.md ├── table-2-fallacy.ipynb └── unconfounded-children.ipynb ├── latexify.py ├── numerical_subs.py ├── pond-scum.jpg ├── run_all_nb.py └── run_all_py.py /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | __pycache__ 3 | .ipynb_checkpoints -------------------------------------------------------------------------------- /CovMatCalculator.py: -------------------------------------------------------------------------------- 1 | from core_matrices import * 2 | from numerical_subs import * 3 | from copy import deepcopy 4 | 5 | 6 | class CovMatCalculator: 7 | """ 8 | The purpose of this class is to calculate/store a symbolic 9 | representation of the covariance matrix C and the Jacobian matrix J, 10 | expressed as a function of the gains \alpha_{i,j}. We define C_{j, 11 | k} = , where x_j are the internal nodes and \epsilon_j are the 12 | external ones. We define J_{i, j}= partial of x_i with respect to x_j. 13 | 14 | If self.conditioned_nds = None, we assume =0 15 | for $i \neq j$. 16 | 17 | If list self.conditioned_nds is non-empty, we allow some non-diagonal 18 | <\epsilon_i, \epsilon_j> to be nonzero, because if we are conditioning 19 | on a collider, this can introduce a non-blocked path between \epsilon_i 20 | and \epsilon_j. 21 | 22 | Attributes 23 | ---------- 24 | conditioned_nds: list[str] 25 | List of the nodes that we want to condition on 26 | cov_mat_sb: sp.Matrix 27 | an sp.Matrix for the covariance matrix C. 28 | graph: Graph 29 | jacobian_sb: sp.Matrix 30 | an sp.Matrix for the Jacobian matrix J. 31 | one_minus_A_inv_sb: sp.Matrix 32 | (1-A).inv(), where A is the matrix of gains \alpha_{i|j} 33 | 34 | """ 35 | 36 | def __init__(self, graph, conditioned_nds=None): 37 | """ 38 | Constructor 39 | 40 | Parameters 41 | ---------- 42 | graph: Graph 43 | conditioned_nds: None or list[str] 44 | Nodes that are being conditioned on (a.k.a the "controls") 45 | """ 46 | self.graph = graph 47 | if conditioned_nds is None: 48 | self.conditioned_nds = [] 49 | else: 50 | assert set(conditioned_nds).issubset(graph.ord_nodes) 51 | self.conditioned_nds = conditioned_nds 52 | 53 | self.cov_mat_sb = None 54 | self.jacobian_sb = None 55 | self.one_minus_A_inv_sb = None 56 | 57 | def calculate_cov_mat(self): 58 | """ 59 | This method calculates and stores in 'self.cov_mat_sb', a symbolic 60 | expression for each of the entries C_{i,j} = of the 61 | covariance matrix C. 62 | 63 | It also calculates and stores in 'self.jacobian_sb', a symbolic 64 | expression for each of the entries J_{i,j} of the Jacobian matrix J. 65 | 66 | It also calculates and stores in 'self.one_minus_A_inv_sb', 67 | a symbolic expression for (1-A).inv(), where A is the strictly lower 68 | diagonal matrix of gains \alpha_{i|j}. 69 | 70 | 71 | Returns 72 | ------- 73 | None 74 | 75 | """ 76 | dim = self.graph.num_nds 77 | mat_A = set_to_zero_gains_without_arrows(self.graph, 78 | alpha_sb_mat(dim)) 79 | one_minus_A = sp.eye(dim) - mat_A 80 | self.one_minus_A_inv_sb = one_minus_A.inv() 81 | 82 | eps_cov = ee_sb_mat(dim) 83 | for row, col in product(range(dim), range(dim)): 84 | row_nd = self.graph.ord_nodes[row] 85 | col_nd = self.graph.ord_nodes[col] 86 | if len(self.conditioned_nds) == 0: 87 | if row_nd != col_nd: 88 | eps_cov = eps_cov.subs(eps_cov[row, col], 0) 89 | else: 90 | if (row_nd in self.conditioned_nds or 91 | col_nd in self.conditioned_nds): 92 | eps_cov = eps_cov.subs(eps_cov[row, col], 0) 93 | 94 | cov_mat = sp.simplify(self.one_minus_A_inv_sb * eps_cov * 95 | self.one_minus_A_inv_sb.T) 96 | sigma_nd_sq_inv = sp.zeros(dim) 97 | for i in range(dim): 98 | sigma_nd_sq_inv[i, i] = 1 / cov_mat[i, i] 99 | jacobian = sp.simplify(cov_mat * sigma_nd_sq_inv) 100 | self.cov_mat_sb = cov_mat 101 | self.jacobian_sb = jacobian 102 | 103 | def print_cov_mat(self, verbose=False, time=None): 104 | """ 105 | This method prints the info in self.cov_mat_sb. It does this by 106 | calling latexify:print_matrix_sb(). 107 | 108 | 109 | Parameters 110 | ---------- 111 | verbose: bool 112 | time: None or str or int 113 | must be either None, "one", "n" or "n_plus_one" or int 114 | 115 | Returns 116 | ------- 117 | sp.Symbol 118 | 119 | """ 120 | if time is None: 121 | mat_str = "cov" 122 | elif time in ["one", "n", "n_plus_one"]: 123 | mat_str = "cov_" + time 124 | elif isinstance(time, int): 125 | mat_str = "cov_n" + str(time) 126 | else: 127 | assert False 128 | return print_matrix_sb(self.cov_mat_sb, 129 | mat_str, 130 | self.graph, 131 | verbose=verbose, 132 | time=time) 133 | 134 | def print_jacobian(self, verbose=False, time=None): 135 | """ 136 | This method prints the info in self.jacobian_sb. It does this by 137 | calling latexify:print_matrix_sb(). 138 | 139 | Parameters 140 | ---------- 141 | verbose: bool 142 | time: None or str or int 143 | 144 | Returns 145 | ------- 146 | sp.Symbol 147 | 148 | """ 149 | return print_matrix_sb(self.jacobian_sb, 150 | "pder", 151 | self.graph, 152 | verbose=verbose, 153 | time=time) 154 | 155 | 156 | if __name__ == "__main__": 157 | def main(): 158 | dot = "digraph G {\n" \ 159 | "a->b;\n" \ 160 | "a->s;\n" \ 161 | "n->s,a,b;\n" \ 162 | "}" 163 | with open("tempo13.txt", "w") as file: 164 | file.write(dot) 165 | conditioned = True 166 | if not conditioned: 167 | path = 'tempo13.txt' 168 | conditioned_nds = None 169 | else: 170 | path = 'dot_atlas/good_bad_trols_G1.dot' 171 | conditioned_nds = ["Z"] 172 | graph = Graph(path) 173 | cal = CovMatCalculator(graph, 174 | conditioned_nds=conditioned_nds) 175 | cal.calculate_cov_mat() 176 | cal.print_cov_mat(verbose=True) 177 | cal.print_jacobian(verbose=True) 178 | 179 | 180 | main() 181 | 182 | 183 | -------------------------------------------------------------------------------- /DotTool.py: -------------------------------------------------------------------------------- 1 | import graphviz as gv 2 | from IPython.display import display, Image 3 | from PIL.Image import open as open_image 4 | import matplotlib.pyplot as plt 5 | import pydotplus as pdp 6 | import networkx as nx 7 | import os 8 | 9 | 10 | class DotTool: 11 | """ 12 | This class has no constructor or attributes. It stores static methods 13 | that help to deal with dot files (drawing them, turning them to networkx 14 | graphs, etc.) 15 | 16 | """ 17 | 18 | @staticmethod 19 | def draw(dot_file_path, jupyter=True): 20 | """ 21 | This method uses graphviz to draw the dot file located at 22 | dot_file_path. It creates a temporary file called tempo.png with a 23 | png of the dot file. If jupyter=True, it embeds the png in a jupyter 24 | notebook. If jupyter=False, it opens a window showing the png. 25 | 26 | Parameters 27 | ---------- 28 | dot_file_path : str 29 | jupyter : bool 30 | 31 | 32 | Returns 33 | ------- 34 | None 35 | 36 | """ 37 | s = gv.Source.from_file(dot_file_path) 38 | 39 | # using display(s) will draw the graph but will not embed it 40 | # permanently in the notebook. To embed it permanently, 41 | # must generate temporary image file and use Image(). 42 | # display(s) 43 | 44 | x = s.render("tempo123", format='png', view=False) 45 | os.remove("tempo123") 46 | if jupyter: 47 | display(Image(x)) 48 | else: 49 | open_image("tempo123.png").show() 50 | 51 | @staticmethod 52 | def read_dot_file(dot_file_path): 53 | """ 54 | Unfortunately, the networkx function for reading a dot file is broken. 55 | 56 | # does not understand dot statements like X->Y,Z; 57 | nx_graph = nx.nx_pydot.read_dot(dot_file_path) 58 | 59 | This function will read a dot file of a very basic form only. An 60 | example of the basic form is: 61 | 62 | dot = "digraph G {\n" \ 63 | "a->b;\n" \ 64 | "a->s;\n" \ 65 | "n->s,a,b;\n" \ 66 | "b->s;\n"\ 67 | "}" 68 | 69 | Parameters 70 | ---------- 71 | dot_file_path: str 72 | 73 | Returns 74 | ------- 75 | list, list 76 | 77 | """ 78 | nodes = [] 79 | arrows = [] 80 | with open(dot_file_path) as f: 81 | in_lines = f.readlines() 82 | for line in in_lines: 83 | if "->" in line: 84 | # ignore arrow attributes 85 | line = line.split(sep="[")[0] 86 | split_list = line.split(sep="->") 87 | # print("ffgg", split_list) 88 | pa = split_list[0].strip() 89 | if pa not in nodes: 90 | nodes.append(pa) 91 | ch_list = split_list[1].split(",") 92 | ch_list = [x.strip().strip(";").strip() for x in ch_list] 93 | # print("ffgg", pa) 94 | # print("ffgg", ch_list) 95 | for ch in ch_list: 96 | arrows.append((pa, ch)) 97 | if ch not in nodes: 98 | nodes.append(ch) 99 | 100 | return nodes, arrows 101 | 102 | @staticmethod 103 | def nx_graph_from_dot_file(dot_file_path): 104 | """ 105 | This has the same input and output as nx.nx_pydot.from_pydot( 106 | dot_file_path), but it understands lines in dot_file_path with 107 | multiple children (e.g., X->A,B;) 108 | 109 | Parameters 110 | ---------- 111 | dot_file_path 112 | 113 | Returns 114 | ------- 115 | nx_graph : nx.DiGraph 116 | networkx graph. To plot it, use 117 | nx.draw(nx_graph, with_labels=True, node_color='white') 118 | plt.show() 119 | """ 120 | # this does not understand dot statements like X->Y,Z; 121 | # nx_graph = nx.nx_pydot.read_dot(dot_file_path) 122 | 123 | nodes, arrows = DotTool.read_dot_file(dot_file_path) 124 | g = nx.DiGraph() 125 | g.add_edges_from(arrows) 126 | 127 | return g 128 | 129 | @staticmethod 130 | def write_dot_file_from_nx_graph(nx_graph, dot_file_path): 131 | """ 132 | This method takes as input an nx_graph and it writes a dot file from 133 | it. The output dot file's path is dot_file_path. 134 | 135 | Parameters 136 | ---------- 137 | nx_graph : nx.DiGraph 138 | dot_file_path : str 139 | path of output dot file 140 | 141 | Returns 142 | ------- 143 | None 144 | 145 | """ 146 | nx.drawing.nx_pydot.write_dot(nx_graph, dot_file_path) 147 | 148 | 149 | if __name__ == "__main__": 150 | 151 | def main(): 152 | dot = "digraph G {\n" \ 153 | "a->b;\n" \ 154 | "a->s;\n" \ 155 | "n->s,a,b;\n" \ 156 | "b->s\n" \ 157 | "}" 158 | with open("./tempo13.txt", "w") as file: 159 | file.write(dot) 160 | path = './tempo13.txt' 161 | DotTool.draw(path, jupyter=False) 162 | nx_graph = DotTool.nx_graph_from_dot_file(path) 163 | nx.draw(nx_graph, with_labels=True, node_color='white') 164 | plt.show() 165 | DotTool.write_dot_file_from_nx_graph(nx_graph, "tempo9.dot") 166 | 167 | main() 168 | 169 | 170 | -------------------------------------------------------------------------------- /FBackCovMatCalculator.py: -------------------------------------------------------------------------------- 1 | from CovMatCalculator import * 2 | from copy import deepcopy 3 | 4 | 5 | class FBackCovMatCalculator(CovMatCalculator): 6 | """ 7 | This class is a subclass of 'CovMatCalculator'. Whereas the parent class 8 | is for analyzing DAGs without feedback loops, this class can handle 9 | feedback loops. 10 | 11 | This class implements formulae derived in my book "Bayesuvius", in the 12 | chapter entitled "LDEN diagrams with feedback loops". In that chapter, 13 | I show that 14 | 15 | C^n = G^{n-1} C^1 (G^T)^{n-1} 16 | 17 | C^1 = (1-A).inv diag(\sigma^2_{\epsilon_i}) (1-A).inv.T 18 | 19 | G = [(1-A).inv]B 20 | 21 | where n=1,2,3,... corresponds to time, G is called the growth matrix, 22 | and C^1 is the covariance matrix when n=1 23 | 24 | The class calculates/stores a symbolic representation of the covariance 25 | matrix C^t= at time t=1, and of the growth matrix G, 26 | both expressed as a function of the matrix A of inslice arrow gains 27 | \alpha_{i, j} and the matrix B of feedback arrow gains \beta{i|j}. 28 | 29 | 30 | Attributes 31 | ---------- 32 | growth_mat_sb: sp.Matrix 33 | 34 | """ 35 | 36 | def __init__(self, graph, conditioned_nds=None): 37 | """ 38 | Constructor 39 | 40 | Parameters 41 | ---------- 42 | graph: FBackGraph 43 | conditioned_nds: list[str] 44 | """ 45 | CovMatCalculator.__init__(self, graph, conditioned_nds=conditioned_nds) 46 | self.growth_mat_sb = None 47 | 48 | def calculate_cov_mat(self): 49 | """ 50 | This method overrides CovMatCalculator.calculate_cov_mat(self). 51 | It calls that parent method, plus, in addition, it calculates/stores 52 | the growth matrix G. 53 | 54 | Returns 55 | ------- 56 | None 57 | 58 | """ 59 | dim = self.graph.num_nds 60 | CovMatCalculator.calculate_cov_mat(self) 61 | mat_B = set_to_zero_fback_gains_without_arrows(self.graph, 62 | beta_sb_mat(dim)) 63 | self.growth_mat_sb = sp.simplify(self.one_minus_A_inv_sb*mat_B) 64 | 65 | def print_cov_mat(self, verbose=False, time=None): 66 | """ 67 | This method prevents the user from using the parent method that it 68 | overrides. 69 | 70 | Parameters 71 | ---------- 72 | verbose: bool 73 | time: None or str or int 74 | 75 | Returns 76 | ------- 77 | None 78 | 79 | """ 80 | print("Do you mean 'print_initial_cov_mat()'?") 81 | 82 | def print_jacobian(self, verbose=False, time=None): 83 | """ 84 | This method prevents the user from using the parent method that it 85 | overrides. 86 | 87 | Parameters 88 | ---------- 89 | verbose: bool 90 | time: None or str or int 91 | 92 | Returns 93 | ------- 94 | None 95 | 96 | """ 97 | print("Don't use this when the graph has feedback loops.") 98 | 99 | def print_initial_cov_mat(self, verbose=False): 100 | """ 101 | This method prints the info in self.cov_mat_sb. It does this by 102 | calling CovMatCalculator.print_cov_mat( ) which in turn 103 | calls latexify:print_matrix_sb(). 104 | 105 | 106 | Parameters 107 | ---------- 108 | verbose: bool 109 | 110 | Returns 111 | ------- 112 | sp.Symbol 113 | 114 | """ 115 | return CovMatCalculator.print_cov_mat(self, 116 | verbose=verbose, 117 | time="one") 118 | 119 | def print_growth_mat(self, verbose=False): 120 | """ 121 | This method prints the info in self.growth_mat_sb. It does 122 | this by calling latexify:print_matrix_sb(). 123 | 124 | 125 | Parameters 126 | ---------- 127 | verbose: bool 128 | 129 | Returns 130 | ------- 131 | sp.Symbol 132 | 133 | """ 134 | return print_matrix_sb(self.growth_mat_sb, 135 | "G", 136 | self.graph, 137 | verbose=verbose) 138 | 139 | 140 | if __name__ == "__main__": 141 | def main(): 142 | path = 'dot_atlas/fback-2node.dot' 143 | graph = FBackGraph(path) 144 | cal = FBackCovMatCalculator(graph) 145 | cal.calculate_cov_mat() 146 | cal.print_initial_cov_mat(verbose=True) 147 | cal.print_growth_mat(verbose=True) 148 | 149 | 150 | main() 151 | -------------------------------------------------------------------------------- /FBackGEmanager.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from FBackGainsEstimator import * 3 | 4 | 5 | class FBackGEmanager: 6 | """ 7 | GE=Gains Estimator. The goal of this class is to create an 8 | FBackGainsEstimator for n=1,2,3, ..., n_max-1. These are stored in the 9 | dictionary self.n_to_estimator. 10 | 11 | Attributes 12 | ---------- 13 | graph: FBackGraph 14 | hidden_nds: list[str] 15 | same meaning as in FBackGainsEstimator 16 | mean_alpha_mat: np.array 17 | average over time-slices of alpha_mat 18 | mean_beta_mat: np.array 19 | average over time-slices of beta_mat 20 | n_max: int 21 | >=1 22 | n_to_estimator: dict[int, FBackGainsEstimator] 23 | solve_symbolically: bool 24 | same meaning as in FBackGainsEstimator 25 | std_of_alpha_mat: np.array 26 | standard deviation over time-slices of alpha_mat 27 | std_of_beat_mat: np.array 28 | standard deviation over time-slices of beta_mat 29 | 30 | """ 31 | 32 | def __init__(self, n_max, 33 | graph, 34 | path, 35 | solve_symbolically=False, 36 | hidden_nds=None, 37 | delta=True): 38 | """ 39 | Constructor 40 | 41 | Parameters 42 | ---------- 43 | n_max: int 44 | graph: FBackGraph 45 | path: str 46 | path to csv file with data 47 | solve_symbolically: bool 48 | hidden_nds: list[str] 49 | delta: bool 50 | """ 51 | self.n_max = n_max 52 | self.graph = graph 53 | self.solve_symbolically = solve_symbolically 54 | if hidden_nds is None: 55 | self.hidden_nds = [] 56 | else: 57 | assert set(hidden_nds).issubset(graph.ord_nodes) 58 | self.hidden_nds = hidden_nds 59 | 60 | df = pd.read_csv(path) 61 | columns = FBackRandomDataMaker.get_columns(n_max, graph) 62 | assert set(df.columns) == set(columns) 63 | # put columns in same order as graph.ord_nodes 64 | df = df[columns] 65 | dim = self.graph.num_nds 66 | self.n_to_estimator = {} 67 | for time in range(1, self.n_max): 68 | slice_n = columns[(time-1)*dim: time*dim] 69 | slice_n_plus_one = columns[time * dim: (time+1) * dim] 70 | two_slices = slice_n + slice_n_plus_one 71 | df_two_slices = df[two_slices] 72 | self.n_to_estimator[time] = FBackGainsEstimator( 73 | time, 74 | graph, 75 | df_two_slices, 76 | solve_symbolically, 77 | hidden_nds, 78 | delta 79 | ) 80 | self.n_to_estimator[time].calculate_gains() 81 | self.mean_alpha_mat = None 82 | self.std_of_alpha_mat = None 83 | self.mean_beta_mat = None 84 | self.std_of_beta_mat = None 85 | 86 | def print_greek_lists(self, name, true_greek_mat=None, verbose=False): 87 | """ 88 | This method prints the alpha_list (or the beta_list) of 89 | self.n_to_estimator[n], for n=1,2,3,..., n_max-1, by calling 90 | latexify.print_list_sb() for each n. 91 | 92 | Parameters 93 | ---------- 94 | name: str 95 | either "alpha" or "beta" 96 | true_greek_mat: np.array 97 | verbose: bool 98 | 99 | Returns 100 | ------- 101 | sp.Symbol 102 | 103 | """ 104 | assert name in ["alpha", "beta"] 105 | str0 = r"\begin{array}{l}" + "\n" 106 | for time in range(1, self.n_max): 107 | str0 += r"\text{******** time=" + str(time) + r"}\\" + "\n" 108 | gest = self.n_to_estimator[time] 109 | if name == "alpha": 110 | mat = gest.alpha_mat_estimate 111 | else: 112 | mat = gest.beta_mat_estimate 113 | eq_list = create_eq_list_from_matrix(mat, name, self.graph, 114 | time=None) 115 | comments = gest.get_greek_list_comments( 116 | name, eq_list, true_greek_mat=true_greek_mat) 117 | x = print_list_sb(eq_list, 118 | self.graph, 119 | comment_list=comments) 120 | str0 += str(x) + "\n" + r"\\" + "\n" 121 | str0 = str0[:-3] 122 | str0 += r"\end{array}" 123 | if verbose: 124 | print(str0) 125 | return sp.Symbol(str0) 126 | 127 | def print_mean_greek_list(self, name, true_greek_mat=None, 128 | verbose=False): 129 | """ 130 | This method prints the average of the alpha_mat (or the 131 | beta_mat) of self.n_to_estimator[n] for n=1,2,3,..., n_max-1. 132 | 133 | Parameters 134 | ---------- 135 | name: str 136 | either "alpha" or "beta" 137 | true_greek_mat: np.array 138 | either "true_alpha_mat" or "true_beta_mat" 139 | verbose: bool 140 | 141 | Returns 142 | ------- 143 | sp.Symbol 144 | 145 | """ 146 | assert name in ["alpha", "beta"] 147 | if name == "alpha": 148 | self.mean_alpha_mat = np.mean([self.n_to_estimator[ 149 | time].alpha_mat_estimate for 150 | time in range(1, self.n_max)], axis=0) 151 | mat = sp.Matrix(self.mean_alpha_mat) 152 | else: 153 | self.mean_beta_mat = np.mean([self.n_to_estimator[ 154 | time].beta_mat_estimate for 155 | time in range(1, self.n_max)], axis=0) 156 | mat = sp.Matrix(self.mean_beta_mat) 157 | eq_list = create_eq_list_from_matrix(mat, name, self.graph, 158 | time=None) 159 | comments = self.n_to_estimator[1].get_greek_list_comments( 160 | name, eq_list, true_greek_mat=true_greek_mat) 161 | 162 | return print_list_sb(eq_list, self.graph, 163 | comment_list=comments, verbose=verbose, 164 | prefix_str="mean of") 165 | 166 | def print_std_of_greek_list(self, name, verbose=False): 167 | """ 168 | This method prints the standard deviation of the alpha_mat ( or the 169 | beta_mat) of self.n_to_estimator[n] for n=1,2,3, ..., n_max-1. 170 | 171 | Parameters 172 | ---------- 173 | name: str 174 | either "alpha" or "beta" 175 | verbose: bool 176 | 177 | Returns 178 | ------- 179 | sp.Symbol 180 | 181 | """ 182 | assert name in ["alpha", "beta"] 183 | if name == "alpha": 184 | self.std_of_alpha_mat = np.std([self.n_to_estimator[ 185 | time].alpha_mat_estimate for 186 | time in range(1, self.n_max)], axis=0) 187 | mat = sp.Matrix(self.std_of_alpha_mat) 188 | else: 189 | self.std_of_beta_mat = np.std([self.n_to_estimator[ 190 | time].beta_mat_estimate for 191 | time in range(1, self.n_max)], axis=0) 192 | mat = sp.Matrix(self.std_of_beta_mat) 193 | eq_list = create_eq_list_from_matrix(mat, name, self.graph, 194 | time=None) 195 | 196 | return print_list_sb(eq_list, self.graph, 197 | comment_list=None, verbose=verbose, 198 | prefix_str="std of ") 199 | 200 | def print_alpha_lists(self, true_alpha_mat=None, verbose=False): 201 | """ 202 | This method prints the alpha_list of FBackGainsEstimator[n] for n=1, 203 | 2,3, ... n_max-1 by calling self.print_greek_lists(). 204 | 205 | Parameters 206 | ---------- 207 | true_alpha_mat: np.array 208 | verbose: bool 209 | 210 | Returns 211 | ------- 212 | sp.Symbol 213 | 214 | """ 215 | return self.print_greek_lists("alpha", 216 | true_greek_mat=true_alpha_mat, 217 | verbose=verbose) 218 | 219 | def print_mean_alpha_list(self, true_alpha_mat=None, verbose=False): 220 | """ 221 | This method prints the average of the alpha_list of 222 | FBackGainsEstimator[n] for n=1, 2,3, ... n_max-1. It does this by 223 | calling self.print_mean_greek_list(). 224 | 225 | Parameters 226 | ---------- 227 | true_alpha_mat: np.array 228 | verbose: bool 229 | 230 | Returns 231 | ------- 232 | sp.Symbol 233 | 234 | """ 235 | return self.print_mean_greek_list("alpha", 236 | true_greek_mat=true_alpha_mat, 237 | verbose=verbose) 238 | 239 | def print_std_of_alpha_list(self, verbose=False): 240 | """ 241 | This method prints the standard deviation of the alpha_list of 242 | FBackGainsEstimator[n] for n=1, 2,3, ... n_max-1. It does this by 243 | calling self.print_std_of_greek_list(). 244 | 245 | Parameters 246 | ---------- 247 | verbose: bool 248 | 249 | Returns 250 | ------- 251 | sp.Symbol 252 | 253 | """ 254 | return self.print_std_of_greek_list("alpha", verbose=verbose) 255 | 256 | def print_beta_lists(self, true_beta_mat=None, verbose=False): 257 | """ 258 | This method prints the beta_list of FBackGainsEstimator[n] for n=1, 259 | 2,3, ... n_max-1 by calling self.print_greek_lists(). 260 | 261 | 262 | Parameters 263 | ---------- 264 | true_beta_mat: np.array 265 | verbose: bool 266 | 267 | Returns 268 | ------- 269 | sp.Symbol 270 | 271 | """ 272 | return self.print_greek_lists("beta", 273 | true_greek_mat=true_beta_mat, 274 | verbose=verbose) 275 | 276 | def print_mean_beta_list(self, true_beta_mat=None, verbose=False): 277 | """ 278 | This method prints the average of the beta_list of 279 | FBackGainsEstimator[n] for n=1, 2,3, ... n_max-1. It does this by 280 | calling self.print_mean_greek_list(). 281 | 282 | Parameters 283 | ---------- 284 | true_beta_mat: np.array 285 | verbose: bool 286 | 287 | Returns 288 | ------- 289 | sp.Symbol 290 | 291 | """ 292 | return self.print_mean_greek_list("beta", 293 | true_greek_mat=true_beta_mat, 294 | verbose=verbose) 295 | 296 | def print_std_of_beta_list(self, verbose=False): 297 | """ 298 | This method prints the standard deviation of the beta_list of 299 | FBackGainsEstimator[n] for n=1, 2,3, ... n_max-1. It does this by 300 | calling self.print_std_of_greek_list(). 301 | 302 | Parameters 303 | ---------- 304 | verbose: bool 305 | 306 | Returns 307 | ------- 308 | sp.Symbol 309 | 310 | """ 311 | return self.print_std_of_greek_list("beta", verbose=verbose) 312 | 313 | 314 | if __name__ == "__main__": 315 | def main(): 316 | path = 'dot_atlas/fback-2node.dot' 317 | graph = FBackGraph(path) 318 | dim = graph.num_nds 319 | mean_eps = [0]*dim 320 | sig_eps = [10] * dim 321 | n_max = 4 322 | alpha_bound = 10 323 | beta_bound = 1 324 | alpha_mat, beta_mat = \ 325 | FBackRandomDataMaker.generate_random_alpha_and_beta_mats( 326 | graph, alpha_bound=alpha_bound, beta_bound=beta_bound) 327 | dmaker = FBackRandomDataMaker(n_max, graph, 328 | alpha_mat=alpha_mat, 329 | beta_mat=beta_mat, 330 | mean_eps=mean_eps, 331 | sig_eps=sig_eps) 332 | num_rows = 100 333 | data_path = "test_data.csv" 334 | dmaker.write_dataset_csv(num_rows, data_path) 335 | df = pd.read_csv(data_path) 336 | print(df) 337 | mger = FBackGEmanager(n_max, graph, data_path) 338 | mger.print_alpha_lists(true_alpha_mat=dmaker.alpha_mat, verbose=True) 339 | mger.print_mean_alpha_list(true_alpha_mat=dmaker.alpha_mat, 340 | verbose=True) 341 | mger.print_beta_lists(true_beta_mat=dmaker.beta_mat, verbose=True) 342 | mger.print_mean_beta_list(true_beta_mat=dmaker.beta_mat, 343 | verbose=True) 344 | 345 | 346 | main() 347 | -------------------------------------------------------------------------------- /FBackGainsCalculator.py: -------------------------------------------------------------------------------- 1 | from GainsCalculator import * 2 | 3 | 4 | class FBackGainsCalculator(GainsCalculator): 5 | """ 6 | This class is a subclass of 'GainsCalculator'. Whereas the parent class 7 | is for analyzing DAGs without feedback loops, this class can handle 8 | feedback loops. 9 | 10 | This class implements formulae derived in my book "Bayesuvius", in the 11 | chapter entitled "LDEN diagrams with feedback loops". In that chapter, 12 | I consider, for a graph with feedback loops, 13 | 14 | the matrix A with entries A_{i,j}=\alpha_{ i|j}= inslice arrow gains 15 | 16 | the matrix B with entries B_{i,j}=\beta_{i|j}= feedback arrow gains 17 | 18 | I show that A and B satisfy a system of 2 linear equations with two 19 | unknowns A,B. 20 | 21 | Let CM_info = the single-time covariance matrices C^n, C^{n+1} with 22 | entries C^t_{i,i}= at times t=n and t=n+1, and the 2-times 23 | covariance matrix cov2times_n with entries cov2times_n_{i,j}= 25 | 26 | To solve that system of 2 equations in (A, B), this class first solves 27 | one equation for A in terms of B and CMinfo, thus obtaining A(B, 28 | CMinfo). Then it substitutes A(B, CMinfo) into the remaining equation to 29 | obtain B(CMinfo). Finally, it substitutes B(CMinfo) into A(B, CMinfo) to 30 | get A(B(CMinfo), CMinfo). 31 | 32 | self.alpha_list_with_betas and self.alpha_mat_with_betas are used to store 33 | A(B, CM_info). 34 | 35 | self.beta_list and self.beta_mat are used to store B(CM_info). 36 | 37 | self.alpha_list and self.alpha_mat are used to store A(B(CM_info), 38 | CM_info). 39 | 40 | The value of random variable x at time n will be denoted by x^{[ n]}. We 41 | will also use the notation 42 | 43 | \Delta x^{[n]} = x^{[n+1]}- x^{[n]} 44 | 45 | Set "delta=False" if you want 2-times correlations < x_i^{[n]}, 46 | x_j^{[n+1]}> in the final result to be expressed as themselves. Set 47 | "delta=True" (recommended) if you want 2-times correlations < x_i^{[ 48 | n]}, x_j^{[n+1]}> in the final result to be replaced by 2 terms, 49 | using the identity 50 | 51 | < x_i^{[n]}, x_j^{[n+1]}>= < x_i^{[n]}, x_j^{[n]}> + < x_i^{[n]}, 52 | \Delta x_j^{[n]}> 53 | 54 | 55 | Attributes 56 | ---------- 57 | # alpha_list and alpha_mat are inherited from parent class 58 | alpha_list_with_betas: list[sp.Eq] 59 | alpha_mat_with_betas: sp.Matrix 60 | beta_list: list[sp.Eq] 61 | beta_mat: sp.Matrix 62 | delta: bool 63 | 64 | """ 65 | 66 | def __init__(self, graph, delta=True): 67 | """ 68 | Constructor 69 | 70 | Parameters 71 | ---------- 72 | graph: FBackGraph 73 | delta: bool 74 | """ 75 | GainsCalculator.__init__(self, graph) 76 | self.delta = delta 77 | 78 | # self.alpha_list and self.alpha_mat are inherited from parent class. 79 | self.alpha_list_with_betas = None 80 | self.alpha_mat_with_betas = None 81 | 82 | self.beta_list = None 83 | self.beta_mat = None 84 | 85 | def calculate_gains(self, cov_mat_list_in=None, mat_K=None, time="n"): 86 | """ 87 | This method overrides the parent method. It calls the parent method 88 | within itself. It fills in 89 | 90 | self.alpha_list and self.alpha_mat 91 | 92 | self.alpha_list_with_betas and self.alpha_mat_with_betas 93 | 94 | self.beta_list and self.beta_mat 95 | 96 | The inputs to cov_mat_list_in and mat_K do not matter as these 97 | variables are reassigned internally. 98 | 99 | Parameters 100 | ---------- 101 | cov_mat_list_in: list[sp.Matrix] 102 | mat_K: sp.Matrix 103 | time: None or str or int 104 | 105 | Returns 106 | ------- 107 | None 108 | 109 | """ 110 | dim = self.graph.num_nds 111 | 112 | if time == "n": 113 | time0 = "n" 114 | time1 = "n_plus_one" 115 | elif isinstance(time, int): 116 | time0 = time 117 | time1 = time + 1 118 | else: 119 | assert False 120 | 121 | if cov_mat_list_in is None: 122 | cov_mat0 = cov_sb_mat(dim, time=time0) 123 | cov2times = cov2times_sb_mat(dim, time=time0) 124 | cov_mat1 = cov_sb_mat(dim, time=time1) 125 | d_cov2times = cov2times_sb_mat(dim, time=time0, delta=True) 126 | else: 127 | cov_mat0, cov2times, cov_mat1 = cov_mat_list_in 128 | d_cov2times = cov2times - cov_mat0 129 | 130 | mat_B = set_to_zero_fback_gains_without_arrows(self.graph, 131 | beta_sb_mat(dim)) 132 | mat_K = mat_B * cov_mat0 133 | 134 | calc = GainsCalculator(self.graph) 135 | calc.calculate_gains(cov_mat_in=cov_mat1, mat_K=mat_K, time=time1) 136 | self.alpha_mat_with_betas = deepcopy(calc.alpha_mat) 137 | self.alpha_list_with_betas = deepcopy(calc.alpha_list) 138 | 139 | self.alpha_mat = None 140 | self.alpha_list = None 141 | 142 | self.calculate_betas(cov_mat0, cov2times, d_cov2times, time=time0) 143 | self.calculate_alphas() 144 | 145 | def calculate_betas(self, cov_mat0, cov2times, d_cov2times, time): 146 | """ 147 | This method fills in self.beta_list and self.beta_mat. It's an 148 | internal method called by calculate_gains(). 149 | 150 | Parameters 151 | ---------- 152 | cov_mat0: sp.Matrix 153 | cov2times: sp.Matrix 154 | d_cov2times: sp.Matrix 155 | time: None or str or int 156 | 157 | 158 | Returns 159 | ------- 160 | None 161 | 162 | """ 163 | dim = self.graph.num_nds 164 | 165 | mat_B = set_to_zero_fback_gains_without_arrows(self.graph, 166 | beta_sb_mat(dim)) 167 | 168 | eq_list = eq_list0 = eq_list1 = None 169 | eq_mat = eq_mat0 = eq_mat1 = None 170 | unknowns = unknowns0 = unknowns1 = None 171 | 172 | if not self.delta: 173 | eq_mat = (sp.eye(dim) - self.alpha_mat_with_betas) * \ 174 | cov2times.T - \ 175 | mat_B * cov_mat0 176 | else: 177 | # delta method consists in solving 178 | # b= A x 179 | # by solving two systems of linear equations: 180 | # b_0 = A x_0 solved for x_0 181 | # b_1 = A x_1 solved for x_1 182 | # so 183 | # b= b_0 + b_1 = A(x_0 + x_1) 184 | 185 | eq_mat0 = (sp.eye(dim) - self.alpha_mat_with_betas) * \ 186 | cov_mat0.T - mat_B * cov_mat0 187 | eq_mat1 = (sp.eye(dim) - self.alpha_mat_with_betas) * \ 188 | d_cov2times.T - mat_B * cov_mat0 189 | if not self.delta: 190 | unknowns = [] 191 | eq_list = [] 192 | else: 193 | unknowns0 = [] 194 | unknowns1 = [] 195 | eq_list0 = [] 196 | eq_list1 = [] 197 | for row, col in product(range(dim), range(dim)): 198 | row_nd = self.graph.ord_nodes[row] 199 | col_nd = self.graph.ord_nodes[col] 200 | if not self.delta: 201 | eq_list.append(eq_mat[row, col]) 202 | else: 203 | eq_list0.append(eq_mat0[row, col]) 204 | eq_list1.append(eq_mat1[row, col]) 205 | if (col_nd, row_nd) in self.graph.fback_arrows: 206 | beta_str = "beta_" + str(row) + "_L_" + str(col) 207 | if not self.delta: 208 | unknowns.append(sp.Symbol(beta_str)) 209 | else: 210 | unknowns0.append(sp.Symbol(beta_str)) 211 | unknowns1.append(sp.Symbol(beta_str)) 212 | else: 213 | if time == "n": 214 | xtra_str = "n" 215 | elif isinstance(time, int): 216 | xtra_str = "n" + str(time) 217 | else: 218 | assert False 219 | cov2times_str = "cov2times_" + xtra_str +\ 220 | "_" + str(col) + "_" + str(row) 221 | if not self.delta: 222 | unknowns.append(sp.Symbol(cov2times_str)) 223 | else: 224 | # do nothing for unknowns0 225 | unknowns1.append(sp.Symbol("d_" + cov2times_str)) 226 | 227 | # the comma does what is called sequence unpacking. 228 | # draws out item from single item list 229 | if not self.delta: 230 | sol_list, = linsolve(eq_list, unknowns) 231 | # print(str(sol_list)) 232 | sol_list = sp.factor(sol_list) 233 | else: 234 | sol_list0, = linsolve(eq_list0, unknowns0) 235 | sol_list1, = linsolve(eq_list1, unknowns1) 236 | sol_list = [] 237 | unknowns = unknowns1 238 | for i in range(len(sol_list1)): 239 | if i < len(unknowns0): 240 | sol_list.append(sol_list0[i] + sol_list1[i]) 241 | else: 242 | sol_list.append(sol_list1[i]) 243 | 244 | self.beta_list = [] 245 | self.beta_mat = sp.zeros(dim) 246 | for i in range(len(sol_list)): 247 | self.beta_list.append(sp.Eq(unknowns[i], sol_list[i])) 248 | left_str = str(unknowns[i]) 249 | 250 | if left_str[0:3] == 'beta': 251 | # print("kkkll", left_str) 252 | row_str, col_str = left_str[4:].split("_L_") 253 | self.beta_mat[int(row_str), int(col_str)] = sol_list[i] 254 | 255 | def calculate_alphas(self): 256 | """ 257 | This method fills in self.alpha_list and self.alpha_mat. It's an 258 | internal method called by calculate_gains(). 259 | 260 | Returns 261 | ------- 262 | None 263 | 264 | """ 265 | dim = self.graph.num_nds 266 | self.alpha_mat = deepcopy(self.alpha_mat_with_betas) 267 | self.alpha_list = deepcopy(self.alpha_list_with_betas) 268 | for row, col in product(range(dim), range(dim)): 269 | beta_str = "beta_" + str(row) + "_L_" + str(col) 270 | self.alpha_mat = sp.simplify( 271 | self.alpha_mat.subs(sp.Symbol(beta_str), 272 | self.beta_mat[row, col])) 273 | for i in range(len(self.alpha_list)): 274 | self.alpha_list[i] = sp.simplify( 275 | self.alpha_list[i].subs(sp.Symbol(beta_str), 276 | self.beta_mat[row, col])) 277 | # print("ccvvf", self.alpha_mat) 278 | 279 | def print_alpha_list_with_betas(self, verbose=False, time="n"): 280 | """ 281 | This method prints the info in self.alpha_list_with_betas. It does 282 | this by calling latexify:print_list_sb(). 283 | 284 | Parameters 285 | ---------- 286 | verbose: bool 287 | time: None or str or int 288 | 289 | Returns 290 | ------- 291 | sp.Symbol 292 | 293 | """ 294 | return print_list_sb(self.alpha_list_with_betas, 295 | self.graph, 296 | verbose=verbose, 297 | time=time) 298 | 299 | def print_beta_list(self, verbose=False, time="n"): 300 | """ 301 | This method prints the info in self.beta_list. It does this by 302 | calling latexify:print_list_sb(). 303 | 304 | Parameters 305 | ---------- 306 | verbose: bool 307 | time: None or str or int 308 | 309 | Returns 310 | ------- 311 | sp.Symbol 312 | 313 | """ 314 | return print_list_sb(self.beta_list, 315 | self.graph, 316 | verbose=verbose, 317 | time=time) 318 | 319 | def print_alpha_list(self, verbose=False, time="n"): 320 | """ 321 | This method prints the info in self.alpha_list. It does this by 322 | calling latexify:print_list_sb(). 323 | 324 | Parameters 325 | ---------- 326 | verbose: bool 327 | time: None or str or int 328 | 329 | Returns 330 | ------- 331 | sp.Symbol 332 | 333 | """ 334 | return print_list_sb(self.alpha_list, 335 | self.graph, 336 | verbose=verbose, 337 | time=time) 338 | 339 | 340 | if __name__ == "__main__": 341 | def main(): 342 | path = 'dot_atlas/fback-2node.dot' 343 | graph = FBackGraph(path) 344 | cal = FBackGainsCalculator(graph) 345 | cal.calculate_gains() 346 | cal.print_alpha_list_with_betas(verbose=True) 347 | cal.print_beta_list(verbose=True) 348 | cal.print_alpha_list(verbose=True) 349 | 350 | main() 351 | -------------------------------------------------------------------------------- /FBackGainsEstimator.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import numpy as np 3 | from copy import deepcopy 4 | from itertools import product 5 | import sympy as sp 6 | from FBackGraph import * 7 | from FBackRandomDataMaker import * 8 | from GainsEstimator import * 9 | from FBackGainsCalculator import * 10 | 11 | 12 | class FBackGainsEstimator(GainsEstimator): 13 | """ 14 | The goal of this class is to estimate the inslice gain \alpha_{i|j} or 15 | the feedback gain \beta_{i|j} for each arrow x_j->x_i in a linear SCM 16 | with feedback loops. The estimation algorithm requires as input a file 17 | which contains a dataset with, for each time n=1,2, \dots, n_{max}, 18 | the node names (plus string [ n]) as column labels, and with node 19 | values, at time n, as rows. 20 | 21 | The input dataset column labels must include ALL node names, and nothing 22 | else, but these column labels need not be in topological order (as they 23 | are in self.ord_nodes). 24 | 25 | A list of hidden nodes is an argument of the class constructor with None 26 | as default value. Columns of the input dataset corresponding to hidden 27 | nodes will be ignored. Hence, these column entries can be any number. 28 | Correlations where x_i or x_j is a hidden node will be 29 | expressed symbolically (sb); otherwise, they will be expressed 30 | numerically (nm). 31 | 32 | Attributes 33 | ---------- 34 | beta_cum_err: float 35 | Same as for alpha. See alpha explanation in parent class GainsEstimator 36 | beta_list: list[sp.Eq] 37 | Same as for alpha. See alpha explanation in parent class GainsEstimator 38 | beta_mat: sp.Matrix 39 | Same as for alpha. See alpha explanation in parent class GainsEstimator 40 | beta_mat_estimate: np.array 41 | Same as for alpha. See alpha explanation in parent class GainsEstimator 42 | cov_mat_list: list[sp.Matrix, sp.Matrix, sp.Matrix] 43 | [cov_mat0, cov2times, cov_mat1] where cov_mat0=covariance matrix at 44 | time n, cov2times=the 2-times covariance matrix between times n and 45 | n+1, and cov_mat1=covariance matrix at time n+1. This is an internal 46 | variable. 47 | delta: bool 48 | see explanation in docstring for class FBackGainsCalculator 49 | time: None or str or int 50 | 51 | """ 52 | 53 | def __init__(self, 54 | time, 55 | graph, 56 | df, 57 | solve_symbolically=False, 58 | hidden_nds=None, 59 | delta=True): 60 | """ 61 | Constructor 62 | 63 | Parameters 64 | ---------- 65 | time: None or str or int 66 | graph: FBackGraph 67 | df: pd.Dataframe 68 | solve_symbolically: bool 69 | solve_symbolically=True if linsolve() is called using a fully 70 | symbolic covariance matrix, and then the numeric values of the 71 | covariance matrix are substituted in the solution. 72 | solve_symbolically=False if linsolve() is called using a hybrid 73 | covariance matrix, partly symbolic, partly numeric. 74 | hidden_nds: None or list[str] 75 | delta: bool 76 | """ 77 | GainsEstimator.__init__(self, graph, path=None, 78 | solve_symbolically=solve_symbolically, 79 | hidden_nds=hidden_nds) 80 | self.time = time 81 | self.delta = delta 82 | dim = graph.num_nds 83 | # alpha version of the following already defined 84 | # by parent method 85 | self.beta_mat = None 86 | self.beta_mat_estimate = np.zeros((dim, dim)) 87 | self.beta_cum_err = 0 88 | self.beta_list = None 89 | 90 | self.cov_mat_list = None 91 | self.set_cov_mat(df) 92 | self.calculate_gains() 93 | self.fix_alpha_list() 94 | self.fix_beta_list() 95 | 96 | def set_cov_mat(self, df): 97 | """ 98 | This method sets the values of the 3 sp.Matrices in 99 | self.cov_mat_list = [cov_mat0, cov2times, cov_mat1]. Entries of 100 | these matrix that have hidden nodes in their indices, are symbolic. 101 | All other entries are numeric. 102 | 103 | Parameters 104 | ---------- 105 | df: pd.Dataframe 106 | 107 | Returns 108 | ------- 109 | None 110 | 111 | """ 112 | dim = self.graph.num_nds 113 | columns = df.columns 114 | assert len(columns) == 2*dim 115 | cov_mat_nm = df.cov().to_numpy() 116 | cov_mat_list_nm = [ 117 | cov_mat_nm[np.ix_(range(dim), range(dim))], 118 | cov_mat_nm[np.ix_(range(dim), range(dim, 2 * dim))], 119 | cov_mat_nm[np.ix_(range(dim, 2 * dim), range(dim, 2 * dim))]] 120 | 121 | cov_mat0 = cov_sb_mat(dim, time=self.time) 122 | cov2times = cov2times_sb_mat(dim, time=self.time) 123 | cov_mat1 = cov_sb_mat(dim, time=self.time + 1) 124 | self.cov_mat_list = [cov_mat0, cov2times, cov_mat1] 125 | for row, col in product(range(dim), range(dim)): 126 | row_nd = self.graph.ord_nodes[row] 127 | col_nd = self.graph.ord_nodes[col] 128 | symbolic = (row_nd in self.hidden_nds) or \ 129 | col_nd in self.hidden_nds 130 | if not symbolic: 131 | for i in range(3): 132 | self.cov_mat_list[i][row, col] = \ 133 | cov_mat_list_nm[i][row, col] 134 | 135 | def calculate_gains(self): 136 | """ 137 | This method creates an instance of FBackGainsCalculator and asks it 138 | to fill self.alpha_list and self.beta_list. 139 | 140 | Returns 141 | ------- 142 | None 143 | 144 | """ 145 | dim = self.graph.num_nds 146 | calc = FBackGainsCalculator(self.graph, delta=self.delta) 147 | if self.solve_symbolically: 148 | cov_mat0 = cov_sb_mat(dim, time=self.time) 149 | cov2times = cov2times_sb_mat(dim, time=self.time) 150 | cov_mat1 = cov_sb_mat(dim, time=self.time + 1) 151 | cov_mat_list_in = [cov_mat0, cov2times, cov_mat1] 152 | else: 153 | cov_mat_list_in = self.cov_mat_list 154 | calc.calculate_gains(cov_mat_list_in=cov_mat_list_in, 155 | mat_K=None, 156 | time=self.time) 157 | 158 | self.alpha_list = calc.alpha_list 159 | self.beta_list = calc.beta_list 160 | 161 | def fix_greek_list(self, name, 162 | greek_list, greek_mat_estimate, greek_cum_err): 163 | """ 164 | This method modifies the list "greek_list" (which must be either an 165 | "alpha_list" or a "beta_list"). For "greek_list": (1) it changes the 166 | constraint items (2) it inserts numerical values if 167 | solve_symbolically=True. It also calculates from "greek_list", 168 | the numpy matrix "greek_mat_estimate" and the float "greek_cum_err". 169 | 170 | Parameters 171 | ---------- 172 | name: str 173 | either "alpha" or "beta" 174 | greek_list: list[sp.Eq] 175 | either "alpha_list" or "beta_list" 176 | greek_mat_estimate: np.array 177 | either "alpha_mat_estimate" or "beta_mat_estimate" 178 | greek_cum_err: float 179 | either "alpha_cum_err" or "beta_cum_err" 180 | 181 | Returns 182 | ------- 183 | None 184 | 185 | """ 186 | dim = self.graph.num_nds 187 | len0 = len(name) 188 | for i in range(len(greek_list)): 189 | eq = greek_list[i] 190 | str0 = str(eq.args[0]) 191 | # last 2 terms in split("_") 192 | if str0[0:len0] != name: 193 | # print("hhgd", str0.split("_")[-2:]) 194 | row_str, col_str = str0.split("_")[-2:] 195 | str1 = "err" + "_" + row_str + "_" + col_str 196 | eq = sp.Eq(sp.Symbol(str1), 197 | eq.args[0] - eq.args[1]) 198 | for row, col in product(range(dim), range(dim)): 199 | row_nd = self.graph.ord_nodes[row] 200 | col_nd = self.graph.ord_nodes[col] 201 | symbolic = (row_nd in self.hidden_nds) or\ 202 | (col_nd in self.hidden_nds) 203 | if not symbolic: 204 | sb_str = sb_cov_str(row, col, time=self.time) 205 | eq = eq.subs(sb_str, self.cov_mat_list[0][row, col]) 206 | 207 | sb_str = sb_cov2times_str(row, col, time=self.time) 208 | eq = eq.subs(sb_str, self.cov_mat_list[1][row, col]) 209 | 210 | sb_str = sb_cov_str(row, col, time=self.time+1) 211 | eq = eq.subs(sb_str, self.cov_mat_list[2][row, col]) 212 | 213 | greek_list[i] = eq 214 | 215 | str1 = str(eq.args[1]) 216 | try: 217 | xx = float(str1) 218 | except ValueError: 219 | xx = np.nan 220 | if str0[0:len0] == name: 221 | row_str, col_str = str0[len0+1:len(str0)].split("_L_") 222 | row, col = int(row_str), int(col_str) 223 | greek_mat_estimate[row, col] = xx 224 | else: 225 | greek_cum_err += abs(xx) 226 | 227 | @staticmethod 228 | def get_greek_list_comments(name, 229 | greek_list, 230 | true_greek_mat): 231 | """ 232 | This method returns a list[str] of the same length as "greek_list". 233 | The returned list will be used as comments, to be printed to the 234 | right of each entry, when the entries of "greek_list" are printed. 235 | 236 | 237 | Parameters 238 | ---------- 239 | name: str 240 | either "alpha" or "beta" 241 | greek_list: list[sp.Eq] 242 | either "alpha_list" or "beta_list" 243 | true_greek_mat: np.array 244 | the alpha (or beta) matrix used to calculate the synthetic data. 245 | 246 | Returns 247 | ------- 248 | list[str] 249 | 250 | """ 251 | comments = [] 252 | len0 = len(name) 253 | for i in range(len(greek_list)): 254 | str0 = str(greek_list[i].args[0]) 255 | if str0[0:len0] == name: 256 | row_str, col_str = str0[len0 + 1:].split("_L_") 257 | row, col = int(row_str), int(col_str) 258 | comments.append("(true= " + 259 | ("%.6f" % true_greek_mat[row, col]) + ")") 260 | else: 261 | comments.append("") 262 | return comments 263 | 264 | def fix_alpha_list(self): 265 | """ 266 | This method modifies self.alpha_list by calling self.fix_greek_list() 267 | 268 | Returns 269 | ------- 270 | None 271 | 272 | """ 273 | self.fix_greek_list("alpha", 274 | self.alpha_list, 275 | self.alpha_mat_estimate, 276 | self.alpha_cum_err) 277 | 278 | def fix_beta_list(self): 279 | """ 280 | This method modifies self.beta_list by calling self.fix_greek_list() 281 | 282 | 283 | Returns 284 | ------- 285 | None 286 | 287 | """ 288 | self.fix_greek_list("beta", 289 | self.beta_list, 290 | self.beta_mat_estimate, 291 | self.beta_cum_err) 292 | 293 | def get_alpha_list_comments(self, true_alpha_mat): 294 | """ 295 | This method returns a list of comments about self.alpha_list. To do 296 | this, it calls self.get_greek_list_comments(). 297 | 298 | Parameters 299 | ---------- 300 | true_alpha_mat: np.array 301 | 302 | Returns 303 | ------- 304 | list[str] 305 | 306 | """ 307 | return FBackGainsEstimator.get_greek_list_comments("alpha", 308 | self.alpha_list, 309 | true_alpha_mat) 310 | 311 | def get_beta_list_comments(self, true_beta_mat): 312 | """ 313 | This method returns a list of comments about self.beta_list. To do 314 | this, it calls self.get_greek_list_comments(). 315 | 316 | Parameters 317 | ---------- 318 | true_beta_mat: np.array 319 | 320 | Returns 321 | ------- 322 | list[str] 323 | 324 | """ 325 | return FBackGainsEstimator.get_greek_list_comments("beta", 326 | self.beta_list, 327 | true_beta_mat) 328 | 329 | def print_alpha_list(self, true_alpha_mat=None, verbose=False): 330 | """ 331 | This method prints the info in self.alpha_list. It does this by 332 | calling latexify.print_list_sb() 333 | 334 | Parameters 335 | ---------- 336 | true_alpha_mat: np.array 337 | verbose: bool 338 | 339 | Returns 340 | ------- 341 | sp.Symbol 342 | 343 | """ 344 | comments = self.get_alpha_list_comments(true_alpha_mat) 345 | return print_list_sb(self.alpha_list, self.graph, 346 | verbose=verbose, time=self.time, 347 | comment_list=comments, rounded=True) 348 | 349 | def print_beta_list(self, true_beta_mat=None, verbose=False): 350 | """ 351 | This method prints the info in self.beta_list. It does this by 352 | calling latexify.print_list_sb() 353 | 354 | Parameters 355 | ---------- 356 | true_beta_mat: np.array 357 | verbose: bool 358 | 359 | Returns 360 | ------- 361 | sp.Symbol 362 | 363 | """ 364 | comments = self.get_beta_list_comments(true_beta_mat) 365 | return print_list_sb(self.beta_list, self.graph, 366 | verbose=verbose, time=self.time, 367 | comment_list=comments, rounded=True) 368 | 369 | 370 | if __name__ == "__main__": 371 | def main(): 372 | path = 'dot_atlas/fback-2node.dot' 373 | graph = FBackGraph(path) 374 | dim = graph.num_nds 375 | mean_eps = [0]*dim 376 | sig_eps = [.001] * dim 377 | n_max = 2 378 | alpha_bound = 10 379 | beta_bound = 1 380 | dmaker = FBackRandomDataMaker(n_max, graph, 381 | mean_eps=mean_eps, 382 | sig_eps=sig_eps, 383 | alpha_bound=alpha_bound, 384 | beta_bound=beta_bound) 385 | num_rows = 100 386 | data_path = "test_data.csv" 387 | dmaker.write_dataset_csv(num_rows, data_path) 388 | df = pd.read_csv(data_path) 389 | for solve_symbolically in [False, True]: 390 | print("************** solve_symbolically=", solve_symbolically) 391 | time = 1 392 | gest = FBackGainsEstimator(time, graph, df, 393 | solve_symbolically=solve_symbolically) 394 | gest.print_alpha_list(true_alpha_mat=dmaker.alpha_mat, 395 | verbose=True) 396 | print("alpha_mat_estimate=\n", gest.alpha_mat_estimate) 397 | print("alpha_cum_err=", gest.alpha_cum_err) 398 | 399 | gest.print_beta_list(true_beta_mat=dmaker.beta_mat, 400 | verbose=True) 401 | print("beta_mat_estimate=\n", gest.beta_mat_estimate) 402 | print("beta_cum_err=", gest.beta_cum_err) 403 | 404 | main() 405 | -------------------------------------------------------------------------------- /FBackGraph.py: -------------------------------------------------------------------------------- 1 | from Graph import * 2 | 3 | 4 | class FBackGraph(Graph): 5 | """ 6 | This class is a subclass of 'Graph'. Whereas the parent class is for 7 | analyzing DAGs without feedback loops, this class can handle feedback 8 | loops. 9 | 10 | If one asks an instance of this class to draw a graph with slices=1, 11 | it will draw a single time-slice and DASHED GREEN feedback arrows. If 12 | slices=j>1, it will draw j time-slices connected by SOLID GREEN feedback 13 | arrows. 14 | 15 | Attributes 16 | ---------- 17 | fback_arrows: list[(str, str)] 18 | feedback arrows that connect 2 adjacent time-slices. Their arrow 19 | gains are represented by \beta_{i|j}. 20 | inslice_arrows: list[(str, str)] 21 | arrows whose orgin and target occur at the same time. Their arrow 22 | gains are represented by \alpha_{ i|j}. 23 | 24 | 25 | """ 26 | 27 | def __init__(self, 28 | dot_file_path, 29 | amputated_arrows=None): 30 | """ 31 | Constructor 32 | 33 | Parameters 34 | ---------- 35 | dot_file_path: str 36 | amputated_arrows: list[(str, str)] 37 | """ 38 | 39 | Graph.__init__(self, 40 | dot_file_path, 41 | amputated_arrows=amputated_arrows, 42 | is_DAG=False) 43 | self.inslice_arrows, self.fback_arrows =\ 44 | self.get_inslice_and_fback_arrows() 45 | self.nx_graph = nx.DiGraph() 46 | self.nx_graph.add_edges_from(self.inslice_arrows) 47 | # this bombs if not DAG 48 | self.ord_nodes = list(nx.topological_sort(self.nx_graph)) 49 | 50 | def get_inslice_and_fback_arrows(self): 51 | """ 52 | This method returns a list of inslice arrows, and a list of feedback 53 | arrows. 54 | 55 | Returns 56 | ------- 57 | list[(str, str)], list[(str, str)] 58 | 59 | """ 60 | inslice_arrows = [] 61 | fback_arrows = [] 62 | with open(self.path) as f: 63 | in_lines = f.readlines() 64 | for line in in_lines: 65 | if "->" not in line: 66 | continue 67 | else: 68 | green_arrow = False 69 | if "green" in line: 70 | green_arrow = True 71 | pa, ch_list = FBackGraph.get_pa_and_ch_list(line) 72 | for ch in ch_list: 73 | if green_arrow: 74 | fback_arrows.append((pa, ch)) 75 | else: 76 | inslice_arrows.append((pa, ch)) 77 | # print("ccvbb---------------", fback_arrows) 78 | return inslice_arrows, fback_arrows 79 | 80 | def draw(self, jupyter=False, slices=1, point_right=False): 81 | """ 82 | This method draws the graph either on the console (jupyter=False) or 83 | in a jupyter notebook (jupyter=True). Amputated arrows are drawn in 84 | red, non-amputated ones in black. 85 | 86 | Parameters 87 | ---------- 88 | jupyter: bool 89 | slices: int 90 | number of slices=1,2,3, ... to draw. slices=1 draws one 91 | time-slice with feedback loops as dashed green arrows. slices=2 92 | draws 2 time-slices (DAGs) connected by feedback arrows in solid 93 | green. 94 | point_right: bool 95 | If point_right=False, time points down (the default 96 | orientation). If point_right=True, time points right. 97 | 98 | Returns 99 | ------- 100 | None 101 | 102 | """ 103 | new_dot = "" 104 | with open(self.path) as f: 105 | in_lines = f.readlines() 106 | for line in in_lines: 107 | if "->" not in line: 108 | new_dot += line 109 | if "{" in line and point_right: 110 | new_dot += "rankdir=LR;\n" 111 | else: 112 | green_arrow = False 113 | if "green" in line: 114 | green_arrow = True 115 | pa, ch_list = Graph.get_pa_and_ch_list(line) 116 | for ch in ch_list: 117 | if slices == 1: 118 | new_dot += pa + " -> " + ch 119 | if (pa, ch) in self.amputated_arrows: 120 | new_dot += " [color=red];\n" 121 | elif green_arrow: 122 | new_dot += " [color=green, style=dashed];\n" 123 | else: 124 | new_dot += ";\n" 125 | elif slices > 1: 126 | for sli in range(slices): 127 | long_pa = pa + "[" + str(sli+1) + "]" 128 | long_pa = '"' + long_pa + '"' 129 | if green_arrow: 130 | long_ch = ch + "[" + str(sli + 2) + "]" 131 | else: 132 | long_ch = ch + "[" + str(sli+1) + "]" 133 | long_ch = '"' + long_ch + '"' 134 | if green_arrow: 135 | if sli != slices-1: 136 | new_dot += long_pa + " -> " + long_ch 137 | new_dot += "[color=green];\n" 138 | else: 139 | new_dot += long_pa + " -> " + long_ch 140 | new_dot += ";\n" 141 | else: 142 | assert False 143 | with open("tempo1389.dot", "w") as file: 144 | file.write(new_dot) 145 | DotTool.draw('tempo1389.dot', jupyter) 146 | 147 | 148 | if __name__ == "__main__": 149 | 150 | def main(draw): 151 | path = 'dot_atlas/fback-2node.dot' 152 | g = FBackGraph(path) 153 | print('fback_arrows:', g.fback_arrows) 154 | print('inslice_arrows:', g.inslice_arrows) 155 | if draw: 156 | g.draw(jupyter=False, slices=1) 157 | g.draw(jupyter=False, slices=3, point_right=True) 158 | 159 | main(True) 160 | 161 | -------------------------------------------------------------------------------- /FBackRandomDataMaker.py: -------------------------------------------------------------------------------- 1 | from FBackGraph import * 2 | from RandomDataMaker import * 3 | import numpy as np 4 | from itertools import product 5 | import pandas as pd 6 | 7 | 8 | class FBackRandomDataMaker(RandomDataMaker): 9 | """ 10 | This purpose of this class is to generate, for a linear SCM WITH 11 | feedback loops, a synthetic dataset with: (1) column labels= the names 12 | of the nodes graph.ord_nodes followed by [n] for n=1,2,3, ...n_max, 13 | (2) node values in each row. To generate this, we require 14 | 'alpha_mat', 'beta_mat', 'graph' and 'sigma_eps'. 15 | 16 | Attributes 17 | ---------- 18 | # alpha_mat is inherited from parent class 19 | beta_mat: np.array of shape=(dim,dim), where dim = number of nodes. 20 | The matrix of betas (i.e., feedback gains \beta_{i|j}) 21 | n_max: int 22 | We consider times n=1,2,3,..., n_max 23 | 24 | """ 25 | 26 | def __init__(self, n_max, graph, mean_eps, sig_eps, alpha_mat=None, 27 | beta_mat=None, alpha_bound=1, beta_bound=1): 28 | """ 29 | Constructor. 30 | 31 | In this constructor, an alpha_mat (resp., beta_mat) not equal to 32 | 'None' can be submitted, or, if alpha_mat == None (resp., beta_mat == 33 | None), an alpha_mat (resp., beta_mat) will be generated randomly. If 34 | generated randomly, each non-zero gain \alpha_{ i|j} ( resp., 35 | \beta_{i|j}) is chosen from the uniform distribution over the 36 | interval [ -alpha_bound, alpha_bound] 37 | 38 | Parameters 39 | ---------- 40 | n_max: int 41 | We consider times n=1,2,3, ..., n_max 42 | graph: FBackGraph 43 | mean_eps: list[float] 44 | List containing the mean value of each of the gaussian external 45 | noise variables. This list has length dim, where dim is the 46 | number of nodes. 47 | sig_eps: list[float] 48 | List containing the standard deviation of each of the gaussian 49 | external noise variables. This list has length dim, where dim is 50 | the number of nodes. 51 | alpha_mat: np.array of shape=(dim, dim) 52 | beta_mat: np.array of shape=(dim, dim) 53 | alpha_bound: float 54 | must be a positive number. 55 | beta_bound: float 56 | must be a positive number. 57 | """ 58 | self.n_max = n_max 59 | dim = graph.num_nds 60 | RandomDataMaker.__init__(self, graph, mean_eps, sig_eps, 61 | alpha_mat=np.zeros((dim, dim)), 62 | alpha_bound=alpha_bound) 63 | self.alpha_mat, self.beta_mat = FBackRandomDataMaker.\ 64 | generate_random_alpha_and_beta_mats(graph, 65 | alpha_bound, 66 | beta_bound) 67 | if beta_mat is not None: 68 | assert beta_mat.shape == (dim, dim) 69 | self.beta_mat = beta_mat 70 | if alpha_mat is not None: 71 | assert alpha_mat.shape == (dim, dim) 72 | self.alpha_mat = alpha_mat 73 | 74 | @staticmethod 75 | def get_columns(n_max, graph): 76 | """ 77 | This method returns the preferred list of column labels. First, 78 | self.graph.ordered_nds with "[1]" appended to each node name. Next, 79 | the same thing with "[2]" instead of "[1]". And so on. 80 | 81 | Parameters 82 | ---------- 83 | n_max: int 84 | graph: FBackGraph 85 | 86 | Returns 87 | ------- 88 | list[str] 89 | 90 | """ 91 | dim = graph.num_nds 92 | columns = [] 93 | for n in range(1, n_max + 1): 94 | x = [graph.ord_nodes[i] + "[" + str(n) + "]" for i in 95 | range(dim)] 96 | columns += x 97 | return columns 98 | 99 | @staticmethod 100 | def generate_random_alpha_and_beta_mats(graph, alpha_bound=1, 101 | beta_bound=1): 102 | """ 103 | This static method generates randomly and returns the inslice gains 104 | \alpha_{ i|j} and the feedback gains \beta_{ i|j}. Each non-zero 105 | \alpha_{ i|j} and \beta_{ i|j} is chosen using my_random() 106 | 107 | Parameters 108 | ---------- 109 | graph: FBackGraph 110 | alpha_bound: float 111 | must be a positive number. 112 | beta_bound: float 113 | must be a positive number. 114 | 115 | Returns 116 | ------- 117 | np.array, np.array 118 | both arrays of shape=(dim, dim) 119 | 120 | """ 121 | dim = graph.num_nds 122 | alpha_mat = np.zeros((dim, dim)) 123 | beta_mat = np.zeros((dim, dim)) 124 | for row, col in product(range(dim), range(dim)): 125 | row_nd = graph.ord_nodes[row] 126 | col_nd = graph.ord_nodes[col] 127 | if row > col and (col_nd, row_nd) in graph.inslice_arrows: 128 | alpha_mat[row, col] = my_random(alpha_bound) 129 | if (col_nd, row_nd) in graph.fback_arrows: 130 | beta_mat[row, col] = my_random(beta_bound) 131 | return alpha_mat, beta_mat 132 | 133 | def generate_one_random_instance(self): 134 | """ 135 | This internal method returns a dictionary mapping time n to random 136 | values for the nodes 'graph.ord_nodes', for all n=1,2,3, ..., n_max. 137 | 138 | Returns 139 | ------- 140 | dict(int, np.array) 141 | np.array of shape=(dim,) 142 | 143 | """ 144 | 145 | dim = self.graph.num_nds 146 | n_to_nd_values = {} 147 | for n in range(1, self.n_max+1): 148 | nd_values = [0]*dim 149 | for i in range(dim): 150 | nd_values[i] += np.random.normal(loc=10, 151 | scale=self.sigma_eps[i]) 152 | if n >= 1: 153 | for j in range(dim): 154 | if i > j: 155 | nd_values[i] += self.alpha_mat[i, j]*nd_values[j] 156 | if n >= 2: 157 | for j in range(dim): 158 | nd_values[i] += self.beta_mat[i, j] *\ 159 | n_to_nd_values[n-1][j] 160 | 161 | n_to_nd_values[n] = nd_values 162 | 163 | return n_to_nd_values 164 | 165 | def write_dataset_csv(self, num_rows, path): 166 | """ 167 | This method writes a file which contains a dataset in the 168 | comma-separated-values (csv) format. The dataset has: (1) column 169 | labels= the names of the nodes graph.ord_nodes followed by [n] for 170 | n=1,2,3, ...n_max, (2) node values in each row. 171 | 172 | Parameters 173 | ---------- 174 | num_rows: int 175 | number of rows of the dataset 176 | path: str 177 | path to the destination of the output file 178 | 179 | Returns 180 | ------- 181 | None 182 | 183 | """ 184 | dim = self.graph.num_nds 185 | columns = FBackRandomDataMaker.get_columns(self.n_max, self.graph) 186 | df = pd.DataFrame(columns=columns) 187 | for row in range(num_rows): 188 | n_to_nd_values = self.generate_one_random_instance() 189 | for n, nd_position in product(range(1, self.n_max+1), range(dim)): 190 | col_name = self.graph.ord_nodes[nd_position] + \ 191 | "[" + str(n) + "]" 192 | df.loc[row, col_name] = n_to_nd_values[n][nd_position] 193 | df.to_csv(path, index=False) 194 | 195 | 196 | if __name__ == "__main__": 197 | def main(draw): 198 | path = 'dot_atlas/fback-2node.dot' 199 | graph = FBackGraph(path) 200 | if draw: 201 | graph.draw(jupyter=False) 202 | dim = graph.num_nds 203 | mean_eps = [0]*dim 204 | sig_eps = [10]*dim 205 | n_max = 4 206 | alpha_bound = 10 207 | beta_bound = 1 208 | dmaker = FBackRandomDataMaker(n_max, graph, 209 | mean_eps=mean_eps, 210 | sig_eps=sig_eps, 211 | alpha_bound=alpha_bound, 212 | beta_bound=beta_bound) 213 | data_path = "fback_test_data.csv" 214 | num_rows = 100 215 | dmaker.write_dataset_csv(num_rows, data_path) 216 | print(pd.read_csv(data_path)) 217 | print("alpha_mat=\n", dmaker.alpha_mat) 218 | print("beta_mat=\n", dmaker.beta_mat) 219 | 220 | main(False) 221 | -------------------------------------------------------------------------------- /GainsCalculator.py: -------------------------------------------------------------------------------- 1 | from core_matrices import * 2 | from numerical_subs import * 3 | from sympy.solvers.solveset import linsolve 4 | from copy import deepcopy 5 | 6 | 7 | class GainsCalculator: 8 | """ 9 | The purpose of this class is to calculate/store the gains \alpha_{i|j} 10 | expressed symbolically as a function of the covariances . Note 11 | that the nodes are in topological order so x_i happens after x_j if i>j. 12 | If the graph has N nodes, and it is fully connected, then gains \alpha_{ 13 | i|j} will be calculated for all i>j, where i, j = 1, 2 , ..., N. If the 14 | graph is not fully connected, then for each missing arrow x_j->x_i with 15 | \alpha_{ i|j}=0, instead of the equation \alpha{i|j}=0, there will be a 16 | constraint among the covariances. 17 | 18 | Attributes 19 | ---------- 20 | alpha_list: list[sp.Eq] 21 | list of symbols, where each symbol in the list is an equation of the 22 | form: 23 | 24 | \alpha_{i|j} = a function of the covariances 25 | 26 | If the gain \alpha_{i|j} of arrow x_j->x_i is zero because that 27 | arrow is missing, then \alpha_{i|j} on the left hand side of the 28 | above equation is replaced by , so the equation becomes a 29 | constraint on the covariance matrix entries. 30 | alpha_mat: sp.Matrix 31 | the symbolic solutions for the gains \alpha_{i|j} as an sp.Matrix. 32 | alpha_{i|j}=0 if arrow x_j->x_i missing. 33 | graph: Graph 34 | 35 | """ 36 | 37 | def __init__(self, graph): 38 | """ 39 | Constructor 40 | 41 | Parameters 42 | ---------- 43 | graph: Graph 44 | 45 | """ 46 | self.graph = graph 47 | self.alpha_list = None 48 | self.alpha_mat = None 49 | 50 | def calculate_gains(self, cov_mat_in=None, mat_K=None, time=None): 51 | """ 52 | This method calculates and stores in 'self.alpha_list', a list 53 | of symbolic equations. Each equation gives either the value of a 54 | gain \alpha_{i|j}, or a constraint on the covariances. 55 | 56 | Parameters 57 | ---------- 58 | cov_mat_in: sp.Matrix 59 | A numeric covariance matrix can be passed in through this 60 | variable. If this variable is None, a symbolic covariance 61 | matrix is used instead. 62 | mat_K: sp.Matrix 63 | K matrix used only for linear SCM with feedback loops 64 | time: None or str or int 65 | 66 | Returns 67 | ------- 68 | None 69 | 70 | """ 71 | dim = self.graph.num_nds 72 | 73 | if mat_K is None: 74 | mat_K = sp.zeros(dim) 75 | # print('hhgffd', mat_K) 76 | A = set_to_zero_gains_without_arrows(self.graph, 77 | alpha_sb_mat(dim)) 78 | self.alpha_list = [] 79 | self.alpha_mat = sp.zeros(dim) 80 | 81 | cov_mat = cov_sb_mat(dim, time=time) 82 | if cov_mat_in is not None: 83 | for row, col in product(range(dim), range(dim)): 84 | row_nd = self.graph.ord_nodes[row] 85 | col_nd = self.graph.ord_nodes[col] 86 | # some entries of cov_mat_in 87 | # may be symbols due to hidden nodes 88 | if cov_mat_in[row, col].is_number: 89 | if (col_nd, row_nd) in self.graph.arrows: 90 | cov_mat[row, col] = cov_mat_in[row, col] 91 | 92 | for row in range(1, dim): 93 | # cov_prod = cov_mat[0:row, 0:row].inv()*cov_mat[0:row, row] 94 | # cov_prod = sp.simplify(cov_prod) 95 | # A[row, 0:row] = cov_prod.T 96 | 97 | # sympy can't solve overdetermined system 98 | # of linear equations so fix it this way 99 | eqs_mat = cov_mat[0:row, 0:row] * \ 100 | A[row, 0:row].T - \ 101 | (cov_mat[0:row, row] - mat_K[0:row, row]) 102 | eqs = [eqs_mat[i, 0] for i in range(row)] 103 | unknowns = [] 104 | for i in range(row): 105 | row_nd = self.graph.ord_nodes[row] 106 | i_nd = self.graph.ord_nodes[i] 107 | if (i_nd, row_nd) not in self.graph.arrows: 108 | # we only use cov_mat[min(i,j), max(i,j)] 109 | # because cov_mat[i, j] is symmetric. 110 | # Since this system is overdetermined, 111 | # make some of the covariances unknowns 112 | unknowns.append(cov_mat[min(row, i), max(row, i)]) 113 | else: 114 | unknowns.append(A[row, i]) 115 | # the comma does what is called sequence unpacking 116 | # draws out item from single item list 117 | sol_list, = linsolve(eqs, unknowns) 118 | # print(str(sol_list)) 119 | for i in range(row): 120 | self.alpha_list.append(sp.Eq(unknowns[i], sol_list[i])) 121 | left_str = str(unknowns[i]) 122 | if left_str[0:5] == 'alpha': 123 | # print("kkkll", left_str) 124 | row_str, col_str = left_str[6:].split("_L_") 125 | self.alpha_mat[int(row_str), int(col_str)] = sol_list[i] 126 | 127 | def print_alpha_list(self, verbose=False, time=None): 128 | """ 129 | This method prints the info in self.alpha_list. It does this by 130 | calling latexify:print_list_sb() 131 | 132 | 133 | Parameters 134 | ---------- 135 | verbose: Bool 136 | time: None or str or int 137 | 138 | Returns 139 | ------- 140 | sp.Symbol 141 | 142 | """ 143 | return print_list_sb(self.alpha_list, self.graph, 144 | verbose=verbose, time=time) 145 | 146 | 147 | if __name__ == "__main__": 148 | def main(): 149 | dot = "digraph G {\n" \ 150 | "a->b;\n" \ 151 | "a->s;\n" \ 152 | "n->s,a,b;\n" \ 153 | "}" 154 | with open("tempo13.txt", "w") as file: 155 | file.write(dot) 156 | path = 'tempo13.txt' 157 | # path = 'dot_atlas/good_bad_trols_G1.dot' 158 | graph = Graph(path) 159 | cal = GainsCalculator(graph) 160 | cal.calculate_gains() 161 | cal.print_alpha_list(verbose=True) 162 | print(cal.alpha_mat) 163 | 164 | 165 | main() 166 | 167 | 168 | -------------------------------------------------------------------------------- /GainsEstimator.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import numpy as np 3 | from copy import deepcopy 4 | from itertools import product 5 | import sympy as sp 6 | from Graph import * 7 | from RandomDataMaker import * 8 | from GainsCalculator import * 9 | 10 | 11 | class GainsEstimator: 12 | """ 13 | The goal of this class is to estimate the gains \alpha_{i|j} from an 14 | input file that contains a dataset. The dataset has the node names 15 | graph.ord_nodes as column labels, and node values in each row. 16 | 17 | The input dataset column labels must include ALL node names, and nothing 18 | else, but these column labels need not be in topological order (as they 19 | are in self.ord_nodes). 20 | 21 | A list of hidden nodes is an argument of the class constructor with None 22 | as default value. Columns of the input dataset corresponding to hidden 23 | nodes will be ignored. Hence, these column entries can be any number. 24 | Correlations where x_i or x_j is a hidden node will be 25 | expressed symbolically (sb); otherwise, they will be expressed 26 | numerically (nm). 27 | 28 | Attributes 29 | ---------- 30 | alpha_cum_err: float 31 | cumulative error, equal to the sum of the absolute values of the 32 | errors err_i_j in gains_list. This error is calculated only if 33 | there are no hidden nodes; it's set to np.nan otherwise. 34 | alpha_list: list[sp.Eq] 35 | A list of equations of the form '\alpha_{i|j} = float' if the graph 36 | has an arrow x_j->x_i, or of the form 'err_{i,j} = float' if that 37 | arrow is missing from the graph. 'err_i_j' is an error metric equal 38 | to the difference between both sides of an equation that constrains 39 | the covariances. Caveat: if there are hidden nodes, the right 40 | hand sides of these equations may contain symbolic expressions 41 | pertaining to covariances alluding to hidden nodes. 42 | alpha_mat_estimate: np.array of shape=(dim, dim), where dim=number of nodes 43 | estimate of the alpha matrix. Contains estimates for the gains 44 | \alpha_{i|j}. If a particular \alpha_{i|j} estimate can't be 45 | converted to a float (because, for example, it depends on hidden 46 | variables), it is set to np.nan. \alpha_{i|j} estimates for 47 | non-existent arrows are set to 0. 48 | cov_mat: sp.Matrix 49 | Let cov_mat_nm be the numpy, numeric (nm) covariance matrix 50 | calculated from the input dataset. cov_mat is a sp.Matrix of the 51 | same dimension as cov_mat_nm that coincides with cov_mat_nm on those 52 | entries that do not have a hidden node as row or column index. Those 53 | entries of cov_mat that do have hidden nodes in their indices, 54 | are symbolic (sb). 55 | graph: Graph 56 | hidden_nds: list[str] or None 57 | This is a list of the nodes that are hidden. 58 | solve_symbolically: bool 59 | Estimating gains requires solving systems of linear equations. Use 60 | "solve_symbolically=True" if you want to solve the system of 61 | equations symbolically first, and then substitute numerical values. 62 | Use "solve_symbolically=False" if you want to substitute numerical 63 | values first. Both techniques should yield the same answer. 64 | """ 65 | 66 | def __init__(self, graph, 67 | path, 68 | solve_symbolically=False, 69 | hidden_nds=None): 70 | """ 71 | 72 | Parameters 73 | ---------- 74 | graph: Graph 75 | path: str 76 | path to input file containing dataset 77 | solve_symbolically: bool 78 | hidden_nds: None or list[str] 79 | """ 80 | self.graph = graph 81 | df = None 82 | if path is not None: 83 | df = pd.read_csv(path) 84 | assert set(df.columns) == set(graph.ord_nodes) 85 | # put columns in same order as graph.ord_nodes 86 | df = df[graph.ord_nodes] 87 | self.solve_symbolically = solve_symbolically 88 | if hidden_nds is None: 89 | self.hidden_nds = [] 90 | else: 91 | assert set(hidden_nds).issubset(graph.ord_nodes) 92 | self.hidden_nds = hidden_nds 93 | 94 | dim = graph.num_nds 95 | self.alpha_mat_estimate = np.zeros((dim, dim)) 96 | self.alpha_cum_err = 0 97 | self.alpha_list = None 98 | 99 | self.cov_mat = None 100 | if df is not None: 101 | self.set_cov_mat(df) 102 | self.calculate_gains() 103 | self.fix_alpha_list() 104 | 105 | def set_cov_mat(self, df): 106 | """ 107 | This method sets the value of the sp.Matrix called self.cov_mat. 108 | Entries of that matrix that have hidden nodes in their indices, 109 | are symbolic. All other entries are numeric. 110 | 111 | Parameters 112 | ---------- 113 | df: pd.Dataframe 114 | 115 | Returns 116 | ------- 117 | None 118 | 119 | """ 120 | if df is None: 121 | assert False 122 | cov_mat_nm = df.cov().to_numpy() 123 | dim = self.graph.num_nds 124 | self.cov_mat = cov_sb_mat(dim, time=None) 125 | for row, col in product(range(dim), range(dim)): 126 | row_nd = self.graph.ord_nodes[row] 127 | col_nd = self.graph.ord_nodes[col] 128 | symbolic = (row_nd in self.hidden_nds) or\ 129 | col_nd in self.hidden_nds 130 | if not symbolic: 131 | self.cov_mat[row, col] = cov_mat_nm[row, col] 132 | 133 | def calculate_gains(self): 134 | """ 135 | This method creates an instance of GainsCalculator and asks it to 136 | fill self.alpha_list. 137 | 138 | 139 | Returns 140 | ------- 141 | None 142 | 143 | """ 144 | dim = self.graph.num_nds 145 | calc = GainsCalculator(self.graph) 146 | if self.solve_symbolically: 147 | cov_mat_in = cov_sb_mat(dim, time=None) 148 | else: 149 | cov_mat_in = self.cov_mat 150 | 151 | calc.calculate_gains(cov_mat_in=cov_mat_in, 152 | mat_K=None, time=None) 153 | self.alpha_list = calc.alpha_list 154 | 155 | def fix_alpha_list(self): 156 | """ 157 | This method modifies the list "alpha_list". For "alpha_list": (1) it 158 | changes the constraint items (2) it inserts numerical values if 159 | solve_symbolically=True. It also calculates from "alpha_list", 160 | the numpy matrix "alpha_mat_estimate" and the float "alpha_cum_err". 161 | 162 | Returns 163 | ------- 164 | None 165 | 166 | """ 167 | dim = self.graph.num_nds 168 | for i in range(len(self.alpha_list)): 169 | eq = self.alpha_list[i] 170 | str0 = str(eq.args[0]) 171 | # last 2 terms in split("_") 172 | if str0[0:5] != "alpha": 173 | # print("hhgd", str0.split("_")[-2:]) 174 | row_str, col_str = str0.split("_")[-2:] 175 | str1 = "err" + "_" + row_str + "_" + col_str 176 | eq = sp.Eq(sp.Symbol(str1), 177 | eq.args[0] - eq.args[1]) 178 | for row, col in product(range(dim), range(dim)): 179 | row_nd = self.graph.ord_nodes[row] 180 | col_nd = self.graph.ord_nodes[col] 181 | symbolic = (row_nd in self.hidden_nds) or\ 182 | (col_nd in self.hidden_nds) 183 | if not symbolic: 184 | sb_str = sb_cov_str(row, col, time=None) 185 | eq = eq.subs(sb_str, self.cov_mat[row, col]) 186 | self.alpha_list[i] = eq 187 | # print("llkkl", eq) 188 | str1 = str(eq.args[1]) 189 | try: 190 | xx = float(str1) 191 | except ValueError: 192 | xx = np.nan 193 | if str0[0:5] == "alpha": 194 | row_str, col_str = str0[6:len(str0)].split("_L_") 195 | row, col = int(row_str), int(col_str) 196 | self.alpha_mat_estimate[row, col] = xx 197 | else: 198 | self.alpha_cum_err += abs(xx) 199 | 200 | def get_alpha_list_comments(self, true_alpha_mat): 201 | """ 202 | This method returns a list[str] of the same length as "alpha_list". 203 | The returned list will be used as comments, to be printed to the 204 | right of each entry, when the entries of "alpha_list" are printed. 205 | 206 | Parameters 207 | ---------- 208 | true_alpha_mat: np.array 209 | 210 | Returns 211 | ------- 212 | list[str] 213 | 214 | """ 215 | comments = [] 216 | for i in range(len(self.alpha_list)): 217 | str0 = str(self.alpha_list[i].args[0]) 218 | if str0[0:5] == "alpha": 219 | row_str, col_str = str0[6:].split("_L_") 220 | row, col = int(row_str), int(col_str) 221 | comments.append("(true= " + 222 | ("%.6f" % true_alpha_mat[row, col]) + ")") 223 | else: 224 | comments.append("") 225 | return comments 226 | 227 | def print_alpha_list(self, true_alpha_mat=None, verbose=False): 228 | """ 229 | This method prints the info in self.alpha_list. It does this by 230 | calling latexify.print_list_sb() 231 | 232 | Parameters 233 | ---------- 234 | true_alpha_mat: np.array of shape=(dim, dim) 235 | This input contains the true alpha matrix alpha_mat, if one is 236 | known. One would be known if the input dataset was generated by 237 | RandomDataMaker. 238 | verbose: bool 239 | 240 | Returns 241 | ------- 242 | None 243 | 244 | """ 245 | comments = self.get_alpha_list_comments(true_alpha_mat) 246 | return print_list_sb(self.alpha_list, self.graph, 247 | verbose=verbose, time=None, 248 | comment_list=comments, rounded=True) 249 | 250 | 251 | if __name__ == "__main__": 252 | def main(): 253 | dot = "digraph G {\n" \ 254 | "a->b;\n" \ 255 | "a->s;\n" \ 256 | "n->s,a,b;\n" \ 257 | "}" 258 | with open("tempo13.txt", "w") as file: 259 | file.write(dot) 260 | dot_path = 'tempo13.txt' 261 | # dot_path = 'dot_atlas/good_bad_trols_G1.dot' 262 | graph = Graph(dot_path) 263 | dim = graph.num_nds 264 | mean_eps = [0]*dim 265 | sig_eps = [10]*dim 266 | alpha_bound = 10 267 | dmaker = RandomDataMaker(graph, 268 | mean_eps=mean_eps, 269 | sig_eps=sig_eps, 270 | alpha_bound=alpha_bound) 271 | num_rows = 100 272 | data_path = "test_data.csv" 273 | dmaker.write_dataset_csv(num_rows, data_path) 274 | df = pd.read_csv(data_path) 275 | print(df) 276 | for solve_symbolically in [False, True]: 277 | print("************** solve_symbolically=", solve_symbolically) 278 | gest = GainsEstimator(graph, data_path, 279 | solve_symbolically=solve_symbolically) 280 | gest.print_alpha_list(true_alpha_mat=dmaker.alpha_mat, 281 | verbose=True) 282 | print("alpha_mat_estimate=\n", gest.alpha_mat_estimate) 283 | print("alpha_cum_err=", gest.alpha_cum_err) 284 | gest = GainsEstimator(graph, data_path, 285 | solve_symbolically=solve_symbolically, 286 | hidden_nds=["s"]) 287 | gest.print_alpha_list(true_alpha_mat=dmaker.alpha_mat, 288 | verbose=True) 289 | print("alpha_mat_estimate=\n", gest.alpha_mat_estimate) 290 | print("alpha_cum_err=", gest.alpha_cum_err) 291 | 292 | main() 293 | -------------------------------------------------------------------------------- /Graph.py: -------------------------------------------------------------------------------- 1 | from DotTool import * 2 | import networkx as nx 3 | 4 | 5 | class Graph: 6 | """ 7 | The purpose of this class is to store information related to a DAG.This 8 | information is derived from a dot file located at 'dot_file_path'. 9 | 10 | A dot file is a text file that describes a single (usually) DAG in the 11 | dot language. The dot language is the language used by the graph 12 | rendering language GraphViz. 13 | 14 | Attributes 15 | ---------- 16 | amputated_arrows: None or list[(str, str)] 17 | This is a list of arrows to be amputated from arrows in input dot 18 | file. We check that 'amputated_arrows' is inside the list 19 | 'full_arrows' of arrows obtained from reading the input dot file. 20 | 'self.arrows' is 'full_arrows' minus 'amputated_arrows'. 21 | arrows: list[(str, str)] 22 | This is a list of string tuples such as ('a', 'b'), which indicates 23 | that an arrow points from 'a' to 'b'. 24 | num_nds: int 25 | number of nodes 26 | nx_graph: nx.DiGraph 27 | networkx graph. 28 | ord_nodes: list[str] 29 | Ordered nodes. A list of the node names in topological order. Root 30 | nodes first. 31 | path: 32 | path to a dot file. Such files are usually placed in the "dot_atlas' 33 | directory 34 | 35 | """ 36 | def __init__(self, 37 | dot_file_path, 38 | amputated_arrows=None, 39 | is_DAG=True): 40 | """ 41 | Constructor 42 | 43 | Parameters 44 | ---------- 45 | dot_file_path: str 46 | amputated_arrows: None or list[(str,str)] 47 | is_DAG: bool 48 | Whether the graph is a DAG (directed acyclic graph) 49 | """ 50 | self.path = dot_file_path 51 | nodes, all_arrows = DotTool.read_dot_file(self.path) 52 | self.num_nds = len(nodes) 53 | 54 | if amputated_arrows is None: 55 | self.amputated_arrows = [] 56 | self.arrows = all_arrows 57 | else: 58 | self.amputated_arrows = amputated_arrows 59 | assert set(amputated_arrows).issubset(set(all_arrows)) 60 | self.arrows = [ed for ed in all_arrows if 61 | ed not in amputated_arrows] 62 | 63 | self.nx_graph = None 64 | self.ord_nodes = None 65 | if is_DAG: 66 | self.nx_graph = nx.DiGraph() 67 | self.nx_graph.add_edges_from(self.arrows) 68 | # this bombs if not DAG 69 | self.ord_nodes = list(nx.topological_sort(self.nx_graph)) 70 | 71 | @staticmethod 72 | def get_pa_and_ch_list(line): 73 | """ 74 | This is an internal utility function that extracts the name of a 75 | parent and its children from the string 'line'. 76 | 77 | Parameters 78 | ---------- 79 | line: str 80 | 81 | Returns 82 | ------- 83 | str, list[str] 84 | 85 | """ 86 | # get rid of arrow attributes 87 | line = line.split(sep="[")[0] 88 | split_list = line.split(sep="->") 89 | # pa=parent, ch=children 90 | pa = split_list[0].strip() 91 | ch_list = split_list[1].split(",") 92 | ch_list = [x.strip().strip(";").strip() for x in ch_list] 93 | return pa, ch_list 94 | 95 | def draw(self, jupyter=False): 96 | """ 97 | This method draws the graph either on the console (jupyter=False) or 98 | in a jupyter notebook (jupyter=True). Amputated arrows are drawn in 99 | red, non-amputated ones in black. 100 | 101 | Parameters 102 | ---------- 103 | jupyter: bool 104 | 105 | Returns 106 | ------- 107 | None 108 | 109 | """ 110 | new_dot = "" 111 | with open(self.path) as f: 112 | in_lines = f.readlines() 113 | for line in in_lines: 114 | if "->" not in line: 115 | new_dot += line 116 | else: 117 | pa, ch_list = Graph.get_pa_and_ch_list(line) 118 | for ch in ch_list: 119 | new_dot += pa + " -> " + ch 120 | if (pa, ch) in self.amputated_arrows: 121 | new_dot += " [color=red];\n" 122 | else: 123 | new_dot += ";\n" 124 | with open("tempo1389.dot", "w") as file: 125 | file.write(new_dot) 126 | DotTool.draw('tempo1389.dot', jupyter) 127 | 128 | def node_position(self, nd_name): 129 | """ 130 | This method returns the position of the string 'nd_name' in the 131 | list 'self.ordered_nds'. 132 | 133 | Parameters 134 | ---------- 135 | nd_name: str 136 | 137 | Returns 138 | ------- 139 | int 140 | 141 | """ 142 | for i, nd in enumerate(self.ord_nodes): 143 | if nd == nd_name: 144 | return i 145 | assert False, nd_name + " is not in " + str(self.ord_nodes) 146 | 147 | 148 | if __name__ == "__main__": 149 | 150 | def main(draw): 151 | dot = "digraph G {\n" \ 152 | "a->b;\n" \ 153 | "a->s;\n" \ 154 | "n->s,a,b;\n" \ 155 | "b->s;\n" \ 156 | "b[style = filled, color = yellow];\n"\ 157 | "}" 158 | with open("tempo13.txt", "w") as file: 159 | file.write(dot) 160 | path = 'tempo13.txt' 161 | full_g = Graph(path) 162 | amp_g = Graph(path, 163 | amputated_arrows=[('b', 's')]) 164 | for g in [full_g, amp_g]: 165 | print("++++++++++++++++++++++++++++++++++++") 166 | print("nodes in topological order:") 167 | print(g.ord_nodes) 168 | print("arrows") 169 | print(g.arrows) 170 | if draw: 171 | g.draw(jupyter=False) 172 | 173 | 174 | main(True) 175 | 176 | 177 | -------------------------------------------------------------------------------- /MIT-License.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Robert R. Tucci 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SCuMpy 2 | 3 | ![Pond SCM](pond-scum.jpg)Pond SCM 4 | 5 | Welcome to SCuMpy! The name is intended 6 | to (1) evoke the name of the amazing 7 | Python library NumPy, and (2) be a 8 | portmanteau of SCM (Structural Causal Model) 9 | and python. 10 | 11 | A linear SCM is a DAG 12 | whose arrows are labelled by path coefficients 13 | (a.k.a. gains). If you want to learn 14 | more about linear SCM, explained 15 | using my notational conventions 16 | which are also SCuMpy’s notational conventions, 17 | check out my free, open 18 | source book 19 | Bayesuvius. 20 | Look in the chapter entitled 21 | “Linear Deterministic Bnets with External Noise”. 22 | 23 | In SCuMpy, we use the following simple notation. 24 | Let $\underline{a}, \underline{b}$ be any 2 25 | random variables. (I underline random variables instead 26 | of the usual convention of capitalizing them.) Then 27 | 28 | ```math 29 | \text{Mean Value of } \underline{a}= 30 | \langle\underline{a}\rangle 31 | ``` 32 | 33 | 34 | ```math 35 | \text{Covariance}(\underline{a}, \underline{b})= 36 | \langle\underline{a}, 37 | \underline{b}\rangle 38 | ``` 39 | 40 | 41 | ```math 42 | \text{Standard Deviation of }\underline{a} = 43 | \sigma_{\underline{a}} = \sqrt{ 44 | \langle\underline{a}, 45 | \underline{a}\rangle} 46 | ``` 47 | 48 | ```math 49 | \text{Correlation}(\underline{a}, \underline{b}) = 50 | \rho_{ 51 | \underline{a}, \underline{b} 52 | }= 53 | \frac{\langle\underline{a}, 54 | \underline{b}\rangle} 55 | {\sigma_{\underline{a}}\sigma_{\underline{b}}} 56 | ``` 57 | 58 | ```math 59 | \frac{\partial\underline{b}}{ 60 | \partial\underline{a}}= 61 | \frac{\langle\underline{a}, 62 | \underline{b}\rangle} 63 | {\langle\underline{a}, 64 | \underline{a}\rangle} 65 | ``` 66 | 67 | Linear SCM are described by 68 | a system of linear equations of the form 69 | 70 | ```math 71 | \underline{x}_{i}={\sum}_{j}{\alpha}_{i|j}\underline{x}_j+\underline 72 | {\epsilon}_i 73 | ``` 74 | 75 | 76 | where the 77 | $x_i$ are the internal nodes, 78 | the $\alpha_{i|j}$ 79 | are 80 | the path coefficients 81 | (a.k.a. gains), and the 82 | $\underline{\epsilon}_i$ 83 | are the external nodes 84 | that inject noise into the system. 85 | The $\underline{\epsilon}_i$ are 86 | root nodes with 87 | zero covariance 88 | with each other. 89 | 90 | We can view this as either 91 | 92 | 1. a linear system 93 | of equations with the unknowns 94 | $\underline{x}_i$. We can solve for these 95 | unknowns using basic Linear Algebra. 96 | Once we solve for the unknowns, 97 | we can calculate $\langle\underline{x}_i, \underline{x}_k\rangle$. 98 | 2. a linear system 99 | of equations with the 100 | unknowns $\alpha_{i|j}$. We can solve for these 101 | unknowns using basic Linear Algebra. 102 | 103 | SCuMpy 104 | takes as input a DAG 105 | expressed as a dot file. A dot file 106 | is a text file 107 | describing a single (usually) DAG 108 | in the dot language. 109 | The dot language is the language used to 110 | describe DAGs by 111 | the graph rendering software GraphViz. 112 | SCuMpy stores all its dot files in a 113 | folder entitled "dot_atlas". 114 | Another term for "dot atlas" 115 | is "DAG atlas". 116 | 117 | At this point, 118 | given a DAG dot file as input, 119 | SCuMpy can 120 | do (1) and (2), symbolically, using 121 | the excellent 122 | Python symbolic manipulator 123 | SymPy. 124 | To test SCuMpy, we used the 20 DAGs 125 | defined in 126 | the "G&B-trols" paper: 127 | 128 | > A Crash Course in Good and Bad Controls, 129 | by Carlos Cinelli, Andrew Forney and Judea Pearl 130 | 131 | 132 | In the SCuMpy folder called "jupyter_notebooks", 133 | you will find 20 notebooks where (1) is done 134 | symbolically, 135 | and another 20 notebooks where (2) is done symbolically, 136 | for each of the 20 DAGs in the G&B-trols paper. 137 | 138 | In addition, we have notebooks 139 | for the 140 | [back door](https://github.com/rrtucci/scumpy/blob/master/jupyter_notebooks/back-door.ipynb), 141 | [front door](https://github.com/rrtucci/scumpy/blob/master/jupyter_notebooks/front-door.ipynb) 142 | and 143 | [napkin](https://github.com/rrtucci/scumpy/blob/master/jupyter_notebooks/napkin.ipynb) 144 | examples in 145 | Pearl's the "Book of Why". 146 | 147 | SCuMpy can also be used to prove rigorously, 148 | from its symbolic output, 149 | whether a do-query is identifiable 150 | for a particular DAG. It can do this without 151 | using the fairly complicated 152 | Do Calculus rules, which are the most general 153 | way of establishing identifiability. [See this notebook](https://github.com/rrtucci/scumpy/blob/master/jupyter_notebooks/unconfounded-children.ipynb) 154 | 155 | 156 | SCuMpy can also do some numeric 157 | calculations. It can calculate 158 | numerically the arrow 159 | gain $\alpha_{i|j}$ for each arrow 160 | $\underline{x}_j\rightarrow\underline{x}_i$ 161 | of the DAG. The estimation algorithm 162 | requires as input a file which 163 | contains a dataset with the node 164 | names as column labels, and with 165 | node values as rows. 166 | [See this notebook](https://github.com/rrtucci/scumpy/blob/master/jupyter_notebooks/estimating-gains.ipynb) 167 | 168 | Exciting news. SCuMpy can now do linear SCM 169 | with feedback loops. See [this blog post](https://qbnets.wordpress.com/2023/02/19/scumpy-can-now-do-linear-scm-with-feedback-loops/) 170 | of mine for more details. This can be used to 171 | do Causal Inference with time series (a.k.a. panel data). 172 | 173 | ## Installation Instructions 174 | See [this blog post](https://qbnets.wordpress.com/2023/01/26/first-version-of-scumpy-released-and-how-to-install-it-for-python-beginners/) of mine. 175 | 176 | 177 | 178 | -------------------------------------------------------------------------------- /RandomDataMaker.py: -------------------------------------------------------------------------------- 1 | from Graph import * 2 | import numpy as np 3 | from itertools import product 4 | import pandas as pd 5 | from random import randint, uniform 6 | import math 7 | 8 | def my_random(bound): 9 | """ 10 | For bound <=1, this method returns a float chosen randomly from the 11 | uniform distribution over the interval [-bound, bound]. For bound >1, 12 | this method returns an integer chosen randomly from the uniform integer 13 | distribution over the set {-ceil(bound), -ceil(bound) +1, ..., 14 | ceil(bound)}. 15 | 16 | Parameters 17 | ---------- 18 | bound: float 19 | 20 | Returns 21 | ------- 22 | int or float 23 | 24 | """ 25 | assert bound > 0 26 | if bound > 1: 27 | b = math.ceil(bound) 28 | return randint(-b, b) 29 | else: 30 | return uniform(-bound, bound) 31 | 32 | 33 | class RandomDataMaker: 34 | """ 35 | This purpose of this class is to generate, for a linear SCM WITHOUT 36 | feedback loops, a synthetic dataset with: (1) column labels= the names 37 | of the nodes graph.ord_nodes, (2) node values in each row. To generate 38 | this, we require 'alpha_mat', 'graph', 'mean_eps' and 'sigma_eps'. 39 | 40 | \epsilon_j is a gaussian random variable representing the external root 41 | node pointing into x_j. 42 | 43 | Attributes 44 | ---------- 45 | alpha_mat: np.array of shape=(dim,dim), where dim = number of nodes. 46 | The matrix of alphas (i.e., gains \alpha_{i|j}) 47 | graph: Graph 48 | information about DAG structure 49 | mean_eps: list[float] 50 | list of the mean values of the gaussian random variables 51 | \epsilon_j. The entries in this list are ordered according to 52 | 'graph.ord_nodes' 53 | sigma_eps: list[float] 54 | list of the standard deviations of the gaussian random variables 55 | \epsilon_j. The entries in this list are ordered according to 56 | 'graph.ord_nodes' 57 | 58 | """ 59 | 60 | def __init__(self, graph, mean_eps, sig_eps, alpha_mat=None, 61 | alpha_bound=1): 62 | """ 63 | Constructor. 64 | 65 | For this constructor, an alpha_mat not equal to 'None' can be 66 | submitted, or, if alpha_mat == None, an alpha_mat will be generated 67 | randomly using my_random() to generate each entry. 68 | 69 | Parameters 70 | ---------- 71 | graph: Graph 72 | mean_eps: list[float] 73 | sig_eps: list[float] 74 | alpha_mat: np.array of shape=(dim, dim) 75 | alpha_bound: float 76 | must be a positive number. 77 | """ 78 | self.graph = graph 79 | dim = graph.num_nds 80 | assert len(mean_eps) == dim 81 | self.mean_eps = mean_eps 82 | assert len(sig_eps) == dim 83 | self.sigma_eps = sig_eps 84 | if alpha_mat is None: 85 | self.alpha_mat = RandomDataMaker.\ 86 | generate_random_alpha_mat(graph, alpha_bound) 87 | else: 88 | assert alpha_mat.shape == (dim, dim) 89 | self.alpha_mat = alpha_mat 90 | 91 | @staticmethod 92 | def generate_random_alpha_mat(graph, alpha_bound=1): 93 | 94 | """ 95 | In this internal method, the gains \alpha_{i|j} are generated 96 | randomly using my_random() to generate each. 97 | 98 | Parameters 99 | ---------- 100 | graph: Graph 101 | alpha_bound: float 102 | must be a positive number. 103 | 104 | Returns 105 | ------- 106 | np.array of shape=(dim, dim) 107 | 108 | """ 109 | dim = graph.num_nds 110 | alpha_mat = np.zeros((dim, dim)) 111 | for row, col in product(range(dim), range(dim)): 112 | row_nd = graph.ord_nodes[row] 113 | col_nd = graph.ord_nodes[col] 114 | if row > col and (col_nd, row_nd) in graph.arrows: 115 | alpha_mat[row, col] = my_random(alpha_bound) 116 | return alpha_mat 117 | 118 | def generate_one_random_instance(self): 119 | """ 120 | This internal method returns an array with random values for the 121 | nodes 'graph.ord_nodes'. 122 | 123 | Returns 124 | ------- 125 | np.array of shape=(dim,) 126 | 127 | """ 128 | 129 | dim = self.graph.num_nds 130 | nd_values = [0]*dim 131 | for i in range(dim): 132 | nd_values[i] = np.random.normal(loc=10, scale=self.sigma_eps[i]) 133 | for j in range(dim): 134 | if i > j: 135 | nd_values[i] += self.alpha_mat[i, j]*nd_values[j] 136 | 137 | return nd_values 138 | 139 | def write_dataset_csv(self, num_rows, path): 140 | """ 141 | This method writes a file which contains a dataset in the 142 | comma-separated-values (csv) format. The dataset has (1) column 143 | labels= the names of the nodes graph.ord_nodes, (2) node values in 144 | each row. 145 | 146 | Parameters 147 | ---------- 148 | num_rows: int 149 | number of rows of the dataset 150 | path: str 151 | path to the destination of the output file 152 | 153 | Returns 154 | ------- 155 | None 156 | 157 | """ 158 | df = pd.DataFrame(columns=self.graph.ord_nodes) 159 | for row in range(num_rows): 160 | df.loc[row] = self.generate_one_random_instance() 161 | df.to_csv(path, index=False) 162 | 163 | 164 | if __name__ == "__main__": 165 | def main(draw): 166 | dot = "digraph G {\n" \ 167 | "a->b;\n" \ 168 | "a->s;\n" \ 169 | "n->s,a,b;\n" \ 170 | "}" 171 | with open("tempo13.txt", "w") as file: 172 | file.write(dot) 173 | dot_path = 'tempo13.txt' 174 | # path = 'dot_atlas/good_bad_trols_G1.dot' 175 | graph = Graph(dot_path) 176 | if draw: 177 | graph.draw(jupyter=False) 178 | dim = graph.num_nds 179 | mean_eps = [0]*dim 180 | sig_eps = [10]*dim 181 | alpha_bound = 10 182 | dmaker = RandomDataMaker(graph, 183 | mean_eps=mean_eps, 184 | sig_eps=sig_eps, 185 | alpha_bound=alpha_bound) 186 | data_path = "test_data.csv" 187 | num_rows = 100 188 | dmaker.write_dataset_csv(num_rows, data_path) 189 | print("alpha_mat=\n", dmaker.alpha_mat) 190 | print(pd.read_csv(data_path)) 191 | print("------------------------------") 192 | alpha_mat = np.zeros((dim, dim)) 193 | alpha_mat[1, 0] = 4 194 | alpha_mat[2, 0], alpha_mat[2, 1] = 2, -3 195 | alpha_mat[3, 0], alpha_mat[3, 1] = 1, -1 196 | mean_eps = [0]*dim 197 | sig_eps = [0.0]*dim 198 | dmaker = RandomDataMaker(graph, 199 | mean_eps=mean_eps, 200 | sig_eps=sig_eps, 201 | alpha_mat=alpha_mat) 202 | data_path = "test_data.csv" 203 | num_rows = 5 204 | dmaker.write_dataset_csv(num_rows, data_path) 205 | print(pd.read_csv(data_path)) 206 | 207 | main(True) 208 | -------------------------------------------------------------------------------- /core_matrices.py: -------------------------------------------------------------------------------- 1 | import sympy as sp 2 | from itertools import product 3 | from latexify import * 4 | 5 | """ 6 | 7 | The functions in this file return various core (i.e., fundamental for 8 | scumpy) matrices of the type sp.Matrix. The entries of those matrices are 9 | sp.Symbol instances. 10 | 11 | The names of the entries of the matrices created by this file, are as follows: 12 | 13 | "sigma_eps_" + str(i) 14 | "sigma_nd_" + str(i) 15 | "alpha_" + str(row) + "_L_" + str(col) 16 | "beta_" + str(row) + "_L_" + str(col) 17 | "K_" + str(row) + "_" + str(col) 18 | "cov_" + str(row) + "_" + str(col) 19 | "cov_one_" + str(row) + "_" + str(col) 20 | "cov_n_" + str(row) + "_" + str(col) 21 | "cov_n_plus_one" + str(row) + "_" + str(col) 22 | "cov_n" + str(n) + "_" + str(row) + "_" + str(col) 23 | "cov2times_n" + "_" + str(row) + "_" + str(col) 24 | "cov2times_n" + str(n) + "_" + str(row) + "_" + str(col) 25 | "d_cov2times_n" + "_" + str(row) + "_" + str(col) 26 | "d_cov2times_n" + str(n) + "_" + str(row) + "_" + str(col) 27 | "ee_" + str(row) + "_" + str(col) 28 | "rho_" + str(row) + "_" + str(col) 29 | "pder_" + str(row) + "_wrt_" + str(col) 30 | 31 | """ 32 | 33 | 34 | def make_sb_mat(dim, mat_str, mat_type="general"): 35 | """ 36 | This method returns a symbolic (sb) matrix of type sp.Matrix. 37 | 38 | Parameters 39 | ---------- 40 | dim: int 41 | dimension of square matrix = number of nodes in graph. 42 | mat_str: str 43 | the name of the matrix being returned. For example, if mat_str='cov', 44 | the returned matrix has entries cov[i, j]. 45 | mat_type: str 46 | This flag must be one of the following: "general", "symmetric", 47 | "strictly_lower_triangular", "diagonal" 48 | 49 | Returns 50 | ------- 51 | sp.Matrix 52 | 53 | """ 54 | rows = [] 55 | for i in range(dim): 56 | col = [] 57 | for j in range(dim): 58 | if mat_type == "general": 59 | col.append(sp.Symbol(mat_str % (i, j))) 60 | elif mat_type == "symmetric": 61 | # we only use cov_mat[min(i,j), max(i,j)] 62 | # because cov_mat[i, j] is symmetric 63 | col.append(sp.Symbol(mat_str % (min(i, j), max(i, j)))) 64 | elif mat_type == "strictly_lower_triangular": 65 | if i > j: 66 | col.append(sp.Symbol(mat_str % (i, j))) 67 | else: 68 | col.append(0) 69 | elif mat_type == "diagonal": 70 | if i == j: 71 | col.append(sp.Symbol(mat_str % i)) 72 | else: 73 | col.append(0) 74 | else: 75 | assert False 76 | 77 | rows.append(col) 78 | return sp.Matrix(rows) 79 | 80 | 81 | def sigma_eps_sb_mat(dim): 82 | """ 83 | This method returns a diagonal matrix (of type sp.Matrix) with diagonal 84 | entries equal to the standard deviations sigma_eps_j=\sigma_{\epsilon_j} 85 | of \epsilon_j for each j. 86 | 87 | Parameters 88 | ---------- 89 | dim: int 90 | dimension of square matrix = number of nodes in graph. 91 | 92 | Returns 93 | ------- 94 | sp.Matrix 95 | 96 | """ 97 | return make_sb_mat(dim, "sigma_eps_%d", 98 | mat_type="diagonal") 99 | 100 | 101 | def sigma_nd_sb_mat(dim): 102 | """ 103 | This method returns a diagonal matrix (of type sp.Matrix) with diagonal 104 | entries equal to the standard deviations sigma_nd_j = \sigma_{x_j} of 105 | node x_j for each j. 106 | 107 | 108 | Parameters 109 | ---------- 110 | dim: int 111 | dimension of square matrix = number of nodes in graph. 112 | 113 | Returns 114 | ------- 115 | sp.Matrix 116 | 117 | """ 118 | return make_sb_mat(dim, "sigma_nd_%d", 119 | mat_type="diagonal") 120 | 121 | 122 | def alpha_sb_mat(dim): 123 | """ 124 | This method returns a matrix (of type sp.Matrix) of gains A with entries 125 | A_{ i, j} = alpha_i_L_j=\alpha_{i|j} 126 | 127 | Parameters 128 | ---------- 129 | dim: int 130 | dimension of square matrix = number of nodes in graph. 131 | 132 | Returns 133 | ------- 134 | sp.Matrix 135 | 136 | """ 137 | return make_sb_mat(dim, "alpha_%d_L_%d", 138 | mat_type="strictly_lower_triangular") 139 | 140 | 141 | def beta_sb_mat(dim): 142 | """ 143 | This method returns a matrix (of type sp.Matrix) of feedback gains B 144 | with entries B_{ i, j} = beta_i_L_j = \beta_{i|j} 145 | 146 | Parameters 147 | ---------- 148 | dim: int 149 | dimension of square matrix = number of nodes in graph. 150 | 151 | Returns 152 | ------- 153 | sp.Matrix 154 | 155 | """ 156 | return make_sb_mat(dim, "beta_%d_L_%d", 157 | mat_type="general") 158 | 159 | 160 | def cov_sb_mat(dim, time=None): 161 | """ 162 | This method returns the covariance matrix at time t, C^t (of type 163 | sp.Matrix) with entries C^t_{i,j}= = cov_t_i_j. time can 164 | be None, "one", "n", "n_plus_one" or an int 165 | 166 | Parameters 167 | ---------- 168 | dim: int 169 | dimension of square matrix = number of nodes in graph. 170 | time: None or str or int 171 | 172 | 173 | Returns 174 | ------- 175 | sp.Matrix 176 | 177 | """ 178 | assert time in [None, "one", "n", "n_plus_one"] or \ 179 | isinstance(time, int) 180 | if time is None: 181 | mat_str = "cov_%d_%d" 182 | elif isinstance(time, int): 183 | mat_str = "cov_n" + str(time) + "_%d_%d" 184 | else: 185 | mat_str = "cov_" + time + "_%d_%d" 186 | return make_sb_mat(dim, mat_str, mat_type="symmetric") 187 | 188 | 189 | def cov2times_sb_mat(dim, time="n", delta=False): 190 | """ 191 | This method returns 2-times covariance matrix C^{n,n+1} (of type 192 | sp.Matrix) with entries C^{n,n+1}_{i,j}= = 193 | cov2times_i_j. 194 | 195 | time can be "n", or an int 196 | 197 | The value of random variable x at time n will be denoted by x^{[ n]}. We 198 | will also use the notation 199 | 200 | \Delta x^{[n]} = x^{[n+1]}- x^{[n]} 201 | 202 | Set "delta=False" if you want 2-times correlations < x_i^{[n]}, 203 | x_j^{[n+1]}> in the final result to be expressed as themselves. Set 204 | "delta=True" (recommended) if you want 2-times correlations < x_i^{[ 205 | n]}, x_j^{[n+1]}> in the final result to be replaced by 2 terms, 206 | using the identity 207 | 208 | < x_i^{[n]}, x_j^{[n+1]}>= < x_i^{[n]}, x_j^{[n]}> + < x_i^{[n]}, 209 | \Delta x_j^{[n]}> 210 | 211 | Parameters 212 | ---------- 213 | dim: int 214 | dimension of square matrix = number of nodes in graph. 215 | time: "n" or int 216 | evaluate n at time 217 | delta: bool 218 | 219 | Returns 220 | ------- 221 | sp.Matrix 222 | 223 | """ 224 | xtra_str = "" 225 | if delta: 226 | xtra_str = "d_" 227 | if isinstance(time, int): 228 | str0 = xtra_str + "cov2times_n" + str(time) 229 | elif time == "n": 230 | str0 = xtra_str + "cov2times_n" 231 | else: 232 | assert False, "time=" + str(time) 233 | str0 += "_%d_%d" 234 | 235 | return make_sb_mat(dim, str0, 236 | mat_type="general") 237 | 238 | 239 | def ee_sb_mat(dim): 240 | """ 241 | This method returns the epsilon covariance matrix ee (of type sp.Matrix) 242 | with entries ee_i_j = 243 | 244 | Parameters 245 | ---------- 246 | dim: int 247 | dimension of square matrix = number of nodes in graph. 248 | 249 | Returns 250 | ------- 251 | sp.Matrix 252 | 253 | """ 254 | return make_sb_mat(dim, "ee_%d_%d", 255 | mat_type="symmetric") 256 | 257 | 258 | def rho_sb_mat(dim): 259 | """ 260 | This method returns the correlation matrix \rho (of type sp.Matrix) with 261 | entries rho_i_j=\rho_{i, j}. 262 | 263 | Parameters 264 | ---------- 265 | dim: int 266 | dimension of square matrix = number of nodes in graph. 267 | 268 | Returns 269 | ------- 270 | sp.Matrix 271 | 272 | """ 273 | return make_sb_mat(dim, "rho_%d_%d", 274 | mat_type="symmetric") 275 | 276 | 277 | def jacobian_sb_mat(dim): 278 | """ 279 | This method returns the Jacobian matrix J (of type sp.Matrix) with 280 | entries J_{i, j} = pder_i_j = partial derivative of x_i with respect to x_j 281 | 282 | Parameters 283 | ---------- 284 | dim: int 285 | dimension of square matrix = number of nodes in graph. 286 | 287 | Returns 288 | ------- 289 | sp.Matrix 290 | 291 | """ 292 | return make_sb_mat(dim, "pder_%d_wrt_%d", 293 | mat_type="general") 294 | 295 | 296 | if __name__ == "__main__": 297 | 298 | def main(): 299 | dim = 3 300 | print(sigma_eps_sb_mat(dim)) 301 | print(sigma_nd_sb_mat(dim)) 302 | print(alpha_sb_mat(dim)) 303 | print(beta_sb_mat(dim)) 304 | print(ee_sb_mat(dim)) 305 | print(cov_sb_mat(dim, time=None)) 306 | print(cov_sb_mat(dim, time="one")) 307 | print(cov_sb_mat(dim, time=2)) 308 | print(cov2times_sb_mat(dim, time="n")) 309 | print(cov2times_sb_mat(dim, time=5)) 310 | print(ee_sb_mat(dim)) 311 | print(rho_sb_mat(dim)) 312 | print(jacobian_sb_mat(dim)) 313 | print((sp.eye(dim) - alpha_sb_mat(dim)).inv()) 314 | main() 315 | 316 | -------------------------------------------------------------------------------- /dot_atlas/README.md: -------------------------------------------------------------------------------- 1 | dot files can be drawn online at 2 | 3 | https://dreampuf.github.io/GraphvizOnline/ -------------------------------------------------------------------------------- /dot_atlas/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrtucci/scumpy/90538d79a31974da3faae58c69dd4e90ef555e65/dot_atlas/__init__.py -------------------------------------------------------------------------------- /dot_atlas/back-door.dot: -------------------------------------------------------------------------------- 1 | digraph backdoor { 2 | x->y; 3 | z->x,y; 4 | z; 5 | } 6 | -------------------------------------------------------------------------------- /dot_atlas/fback-2node.dot: -------------------------------------------------------------------------------- 1 | digraph fback_2nodes { 2 | x->y; 3 | x->y[color=green, style=dashed]; 4 | y->x[color=green, style=dashed]; 5 | y->y[color=green, style=dashed]; 6 | x->x[color=green, style=dashed]; 7 | } -------------------------------------------------------------------------------- /dot_atlas/front-door.dot: -------------------------------------------------------------------------------- 1 | digraph frontdoor { 2 | x->m; 3 | m->y; 4 | h->x,y; 5 | h[style=dashed] 6 | } -------------------------------------------------------------------------------- /dot_atlas/good_bad_controls.txt: -------------------------------------------------------------------------------- 1 | https://ftp.cs.ucla.edu/pub/stat_ser/r493.pdf 2 | https://dreampuf.github.io/GraphvizOnline/ 3 | 4 | digraph G1 { 5 | X->Y; 6 | Z->X,Y; 7 | Z[style=filled, color=pink]; 8 | } 9 | 10 | digraph G2 { 11 | X->Y; 12 | Z->X; 13 | U->Z,Y; 14 | Z[color=pink,style=filled]; 15 | U[style=dashed]; 16 | } 17 | 18 | digraph G3 { 19 | X->Y; 20 | Z->Y; 21 | U->X,Z; 22 | Z[color=pink,style=filled]; 23 | U[style=dashed]; 24 | } 25 | 26 | digraph G4 { 27 | X->M; 28 | Z->X,M; 29 | M->Y; 30 | Z[style=filled, color=pink]; 31 | } 32 | 33 | digraph G5 { 34 | X->M; 35 | Z->X; 36 | U->Z,M; 37 | M->Y; 38 | Z[color=pink,style=filled]; 39 | U[style=dashed]; 40 | } 41 | 42 | digraph G6 { 43 | X->M; 44 | Z->M; 45 | U->X,Z; 46 | M->Y; 47 | Z[color=pink,style=filled]; 48 | U[style=dashed]; 49 | } 50 | 51 | digraph G7 { 52 | X->Y; 53 | U1->X,Z; 54 | U2->Y,Z; 55 | Z[color=pink,style=filled]; 56 | U1[style=dashed]; 57 | U2[style=dashed]; 58 | } 59 | 60 | digraph G7up { 61 | X->Y; 62 | U1->X,Z; 63 | U2->Y,Z; 64 | Z->Y; 65 | Z[color=pink,style=filled]; 66 | U1[style=dashed]; 67 | U2[style=dashed]; 68 | } 69 | 70 | digraph G8 { 71 | X->Y; 72 | Z->Y; 73 | Z[color=pink,style=filled]; 74 | } 75 | 76 | digraph G9 { 77 | X->Y; 78 | Z->X; 79 | Z[color=pink,style=filled]; 80 | } 81 | 82 | digraph G10 { 83 | X->Y; 84 | Z->X; 85 | U->X,Y; 86 | Z[color=pink,style=filled]; 87 | U[style=dashed]; 88 | } 89 | 90 | digraph G11 { 91 | X->Z; 92 | Z->Y; 93 | Z[color=pink,style=filled]; 94 | } 95 | 96 | digraph G12 { 97 | X->M; 98 | M->Z,Y; 99 | Z[color=pink,style=filled]; 100 | } 101 | 102 | digraph G11u { 103 | X->Z; 104 | U->Z,Y; 105 | Z->Y; 106 | Z[color=pink,style=filled]; 107 | U[style=dashed]; 108 | } 109 | 110 | digraph G13 { 111 | X->M; 112 | Z->M; 113 | M->Y; 114 | Z[color=pink,style=filled]; 115 | } 116 | 117 | digraph G14 { 118 | X->Y,Z; 119 | Z[color=pink,style=filled]; 120 | } 121 | 122 | digraph G15 { 123 | X->Y,Z; 124 | U->W,Y; 125 | Z->W; 126 | Z[color=pink,style=filled]; 127 | U[style=dashed]; 128 | } 129 | 130 | digraph G16 { 131 | X->Y; 132 | U->Z,Y; 133 | X->Z; 134 | Z[color=pink,style=filled]; 135 | U[style=dashed]; 136 | } 137 | 138 | digraph G17 { 139 | X->Y, Z; 140 | Y->Z; 141 | Z[color=pink,style=filled]; 142 | } 143 | 144 | digraph G18 { 145 | X->Y; 146 | Y->Z; 147 | Z[color=pink,style=filled]; 148 | } -------------------------------------------------------------------------------- /dot_atlas/good_bad_trols_G1.dot: -------------------------------------------------------------------------------- 1 | digraph G1 { 2 | X->Y; 3 | Z->X,Y; 4 | Z[style=filled, color=pink]; 5 | } 6 | -------------------------------------------------------------------------------- /dot_atlas/good_bad_trols_G10.dot: -------------------------------------------------------------------------------- 1 | digraph G10 { 2 | X->Y; 3 | Z->X; 4 | U->X,Y; 5 | Z[color=pink,style=filled]; 6 | U[style=dashed]; 7 | } 8 | -------------------------------------------------------------------------------- /dot_atlas/good_bad_trols_G11.dot: -------------------------------------------------------------------------------- 1 | digraph G11 { 2 | X->Z; 3 | Z->Y; 4 | Z[color=pink,style=filled]; 5 | } -------------------------------------------------------------------------------- /dot_atlas/good_bad_trols_G11u.dot: -------------------------------------------------------------------------------- 1 | digraph G11u { 2 | X->Z; 3 | U->Z,Y; 4 | Z->Y; 5 | Z[color=pink,style=filled]; 6 | U[style=dashed]; 7 | } -------------------------------------------------------------------------------- /dot_atlas/good_bad_trols_G12.dot: -------------------------------------------------------------------------------- 1 | digraph G12 { 2 | X->M; 3 | M->Z,Y; 4 | Z[color=pink,style=filled]; 5 | } 6 | -------------------------------------------------------------------------------- /dot_atlas/good_bad_trols_G13.dot: -------------------------------------------------------------------------------- 1 | digraph G13 { 2 | X->M; 3 | Z->M; 4 | M->Y; 5 | Z[color=pink,style=filled]; 6 | } 7 | -------------------------------------------------------------------------------- /dot_atlas/good_bad_trols_G14.dot: -------------------------------------------------------------------------------- 1 | digraph G14 { 2 | X->Y,Z; 3 | Z[color=pink,style=filled]; 4 | } 5 | -------------------------------------------------------------------------------- /dot_atlas/good_bad_trols_G15.dot: -------------------------------------------------------------------------------- 1 | digraph G15 { 2 | X->Y,Z; 3 | U->W,Y; 4 | Z->W; 5 | Z[color=pink,style=filled]; 6 | U[style=dashed]; 7 | } -------------------------------------------------------------------------------- /dot_atlas/good_bad_trols_G16.dot: -------------------------------------------------------------------------------- 1 | digraph G16 { 2 | X->Y; 3 | U->Z,Y; 4 | X->Z; 5 | Z[color=pink,style=filled]; 6 | U[style=dashed]; 7 | } -------------------------------------------------------------------------------- /dot_atlas/good_bad_trols_G17.dot: -------------------------------------------------------------------------------- 1 | digraph G17 { 2 | X->Y, Z; 3 | Y->Z; 4 | Z[color=pink,style=filled]; 5 | } 6 | -------------------------------------------------------------------------------- /dot_atlas/good_bad_trols_G18.dot: -------------------------------------------------------------------------------- 1 | digraph G18 { 2 | X->Y; 3 | Y->Z; 4 | Z[color=pink,style=filled]; 5 | } -------------------------------------------------------------------------------- /dot_atlas/good_bad_trols_G2.dot: -------------------------------------------------------------------------------- 1 | digraph G2 { 2 | X->Y; 3 | Z->X; 4 | U->Z,Y; 5 | Z[color=pink,style=filled]; 6 | U[style=dashed]; 7 | } -------------------------------------------------------------------------------- /dot_atlas/good_bad_trols_G3.dot: -------------------------------------------------------------------------------- 1 | digraph G3 { 2 | X->Y; 3 | Z->Y; 4 | U->X, Z; 5 | Z[color=pink,style=filled]; 6 | U[style=dashed]; 7 | } -------------------------------------------------------------------------------- /dot_atlas/good_bad_trols_G4.dot: -------------------------------------------------------------------------------- 1 | digraph G4 { 2 | X->M; 3 | Z->X,M; 4 | M->Y; 5 | Z[style=filled, color=pink]; 6 | } -------------------------------------------------------------------------------- /dot_atlas/good_bad_trols_G5.dot: -------------------------------------------------------------------------------- 1 | digraph G5 { 2 | X->M; 3 | Z->X; 4 | U->Z,M; 5 | M->Y; 6 | Z[color=pink,style=filled]; 7 | U[style=dashed]; 8 | } -------------------------------------------------------------------------------- /dot_atlas/good_bad_trols_G6.dot: -------------------------------------------------------------------------------- 1 | digraph G6 { 2 | X->M; 3 | Z->M; 4 | U->X,Z; 5 | M->Y; 6 | Z[color=pink,style=filled]; 7 | U[style=dashed]; 8 | } -------------------------------------------------------------------------------- /dot_atlas/good_bad_trols_G7.dot: -------------------------------------------------------------------------------- 1 | digraph G7 { 2 | X->Y; 3 | U1->X,Z; 4 | U2->Y,Z; 5 | Z[color=pink,style=filled]; 6 | U1[style=dashed]; 7 | U2[style=dashed]; 8 | } 9 | -------------------------------------------------------------------------------- /dot_atlas/good_bad_trols_G7up.dot: -------------------------------------------------------------------------------- 1 | digraph G7up { 2 | X->Y; 3 | U1->X,Z; 4 | U2->Y,Z; 5 | Z->Y; 6 | Z[color=pink,style=filled]; 7 | U1[style=dashed]; 8 | U2[style=dashed]; 9 | } 10 | -------------------------------------------------------------------------------- /dot_atlas/good_bad_trols_G8.dot: -------------------------------------------------------------------------------- 1 | digraph G8 { 2 | X->Y; 3 | Z->Y; 4 | Z[color=pink,style=filled]; 5 | } -------------------------------------------------------------------------------- /dot_atlas/good_bad_trols_G9.dot: -------------------------------------------------------------------------------- 1 | digraph G9 { 2 | X->Y; 3 | Z->X; 4 | Z[color=pink,style=filled]; 5 | } -------------------------------------------------------------------------------- /dot_atlas/linear-naive-bayes-3x.dot: -------------------------------------------------------------------------------- 1 | digraph LR3 { 2 | m->x_1, x_2, x_3 3 | } 4 | -------------------------------------------------------------------------------- /dot_atlas/linear-regression-3x.dot: -------------------------------------------------------------------------------- 1 | digraph LR3 { 2 | x_1->y; 3 | x_2->y; 4 | x_3->y; 5 | x_1->x_2, x_3; 6 | x_2->x_3; 7 | } 8 | -------------------------------------------------------------------------------- /dot_atlas/napkin.dot: -------------------------------------------------------------------------------- 1 | digraph napkin { 2 | w->z; 3 | z->x; 4 | x->y; 5 | h_1->w,y; 6 | h_2->w,x; 7 | h_1, h_2[style=dashed] 8 | } -------------------------------------------------------------------------------- /dot_atlas/potential-outcomes.dot: -------------------------------------------------------------------------------- 1 | digraph PO { 2 | c->x,y; 3 | x->y; 4 | c->c[color=green, style=dashed]; 5 | x->x[color=green, style=dashed]; 6 | y->y[color=green, style=dashed]; 7 | x->c[color=green, style=dashed]; 8 | c->x[color=green, style=dashed]; 9 | x->y[color=green, style=dashed]; 10 | y->x[color=green, style=dashed]; 11 | c->y[color=green, style=dashed]; 12 | y->c[color=green, style=dashed]; 13 | } -------------------------------------------------------------------------------- /dot_atlas/table-2-fallacy-GM.dot: -------------------------------------------------------------------------------- 1 | digraph GM { 2 | X->Y,M; 3 | M->Y; 4 | } -------------------------------------------------------------------------------- /dot_atlas/table-2-fallacy-GMZ.dot: -------------------------------------------------------------------------------- 1 | digraph GMZ { 2 | X->Y,M; 3 | M->Y; 4 | Z->X,Y 5 | } -------------------------------------------------------------------------------- /dot_atlas/table-2-fallacy-GZ.dot: -------------------------------------------------------------------------------- 1 | digraph GZ { 2 | X->Y; 3 | Z->X,Y; 4 | } 5 | -------------------------------------------------------------------------------- /dot_atlas/unconfounded-children.dot: -------------------------------------------------------------------------------- 1 | digraph G { 2 | h_1->x,y; 3 | h_2->m_1,y; 4 | x->m_1, m_2; 5 | m_2->y; 6 | m_1->y; 7 | h_1[style=dashed]; 8 | h_2[style=dashed]; 9 | } -------------------------------------------------------------------------------- /jupyter_notebooks/G11-gains.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "68d1a8ef-f7dc-4d43-959a-878bb0aa2e97", 6 | "metadata": {}, 7 | "source": [ 8 | "# G11 gains\n", 9 | "Main Reference:\n", 10 | "\n", 11 | "* A Crash Course in Good and Bad Controls,\n", 12 | "by Carlos Cinelli, Andrew Forney and Judea Pearl\n", 13 | "\n", 14 | "In this notebook, we derive, using a symbolic manipulator (SymPy), the gains (i.e., path coefficients) as a function of the covariances, for \n", 15 | "\n", 16 | "## G11 \n", 17 | "\n", 18 | "If the DAG is not fully connected, there are some constraints between the covariances. There is one constraint for each arrow missing from a fully connected DAG." 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": 1, 24 | "id": "73d99326-85ad-40c0-b3ba-cc873ed5276e", 25 | "metadata": {}, 26 | "outputs": [ 27 | { 28 | "name": "stdout", 29 | "output_type": "stream", 30 | "text": [ 31 | "C:\\Users\\rrtuc\\Desktop\\backed-up\\python-projects\\scumpy\n" 32 | ] 33 | } 34 | ], 35 | "source": [ 36 | "# this makes sure it starts looking for things from the scumpy folder down.\n", 37 | "import os\n", 38 | "import sys\n", 39 | "os.chdir('../')\n", 40 | "sys.path.insert(0,os.getcwd())\n", 41 | "print(os.getcwd())" 42 | ] 43 | }, 44 | { 45 | "cell_type": "code", 46 | "execution_count": 2, 47 | "id": "bfa6c1fe-c023-4169-b42c-820512fbf67f", 48 | "metadata": {}, 49 | "outputs": [], 50 | "source": [ 51 | "from Graph import *\n", 52 | "from GainsCalculator import *" 53 | ] 54 | }, 55 | { 56 | "cell_type": "code", 57 | "execution_count": 3, 58 | "id": "c83d0949-c9a8-429a-b1a2-eda0d229b558", 59 | "metadata": {}, 60 | "outputs": [ 61 | { 62 | "data": { 63 | "image/png": "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\n", 64 | "text/plain": [ 65 | "" 66 | ] 67 | }, 68 | "metadata": {}, 69 | "output_type": "display_data" 70 | } 71 | ], 72 | "source": [ 73 | "path = 'dot_atlas/good_bad_trols_G11.dot'\n", 74 | "graph = Graph(path)\n", 75 | "graph.draw(jupyter=True)" 76 | ] 77 | }, 78 | { 79 | "cell_type": "code", 80 | "execution_count": 4, 81 | "id": "4c12a223-53ac-4d70-b124-e6f812dff43d", 82 | "metadata": {}, 83 | "outputs": [ 84 | { 85 | "data": { 86 | "text/latex": [ 87 | "$\\displaystyle \\begin{array}{l}\n", 88 | "\\alpha_{\\underline{Z}|\\underline{X}} = \\frac{\\left\\langle\\underline{X},\\underline{Z}\\right\\rangle}{\\sigma^2_{\\underline{X}}}\n", 89 | "\\\\\n", 90 | "\\left\\langle\\underline{X},\\underline{Y}\\right\\rangle = \\frac{\\left\\langle\\underline{X},\\underline{Z}\\right\\rangle \\left\\langle\\underline{Z},\\underline{Y}\\right\\rangle}{\\sigma^2_{\\underline{Z}}}\n", 91 | "\\\\\n", 92 | "\\alpha_{\\underline{Y}|\\underline{Z}} = \\frac{\\left\\langle\\underline{Z},\\underline{Y}\\right\\rangle}{\\sigma^2_{\\underline{Z}}}\n", 93 | "\\end{array}$" 94 | ], 95 | "text/plain": [ 96 | "\\begin{array}{l}\n", 97 | "\\alpha_{\\underline{Z}|\\underline{X}} = \\frac{\\left\\langle\\underline{X},\\underline{Z}\\right\\rangle}{\\sigma^2_{\\underline{X}}}\n", 98 | "\\\\\n", 99 | "\\left\\langle\\underline{X},\\underline{Y}\\right\\rangle = \\frac{\\left\\langle\\underline{X},\\underline{Z}\\right\\rangle \\left\\langle\\underline{Z},\\underline{Y}\\right\\rangle}{\\sigma^2_{\\underline{Z}}}\n", 100 | "\\\\\n", 101 | "\\alpha_{\\underline{Y}|\\underline{Z}} = \\frac{\\left\\langle\\underline{Z},\\underline{Y}\\right\\rangle}{\\sigma^2_{\\underline{Z}}}\n", 102 | "\\end{array}" 103 | ] 104 | }, 105 | "execution_count": 4, 106 | "metadata": {}, 107 | "output_type": "execute_result" 108 | } 109 | ], 110 | "source": [ 111 | "cal = GainsCalculator(graph)\n", 112 | "cal.calculate_gains()\n", 113 | "cal.print_alpha_list()" 114 | ] 115 | } 116 | ], 117 | "metadata": { 118 | "kernelspec": { 119 | "display_name": "Python 3 (ipykernel)", 120 | "language": "python", 121 | "name": "python3" 122 | }, 123 | "language_info": { 124 | "codemirror_mode": { 125 | "name": "ipython", 126 | "version": 3 127 | }, 128 | "file_extension": ".py", 129 | "mimetype": "text/x-python", 130 | "name": "python", 131 | "nbconvert_exporter": "python", 132 | "pygments_lexer": "ipython3", 133 | "version": "3.10.9" 134 | } 135 | }, 136 | "nbformat": 4, 137 | "nbformat_minor": 5 138 | } 139 | -------------------------------------------------------------------------------- /jupyter_notebooks/G14-gains.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "68d1a8ef-f7dc-4d43-959a-878bb0aa2e97", 6 | "metadata": {}, 7 | "source": [ 8 | "# G14 gains\n", 9 | "Main Reference:\n", 10 | "\n", 11 | "* A Crash Course in Good and Bad Controls,\n", 12 | "by Carlos Cinelli, Andrew Forney and Judea Pearl\n", 13 | "\n", 14 | "In this notebook, we derive, using a symbolic manipulator (SymPy), the gains (i.e., path coefficients) as a function of the covariances, for \n", 15 | "\n", 16 | "## G14 \n", 17 | "\n", 18 | "If the DAG is not fully connected, there are some constraints between the covariances. There is one constraint for each arrow missing from a fully connected DAG." 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": 1, 24 | "id": "73d99326-85ad-40c0-b3ba-cc873ed5276e", 25 | "metadata": {}, 26 | "outputs": [ 27 | { 28 | "name": "stdout", 29 | "output_type": "stream", 30 | "text": [ 31 | "C:\\Users\\rrtuc\\Desktop\\backed-up\\python-projects\\scumpy\n" 32 | ] 33 | } 34 | ], 35 | "source": [ 36 | "# this makes sure it starts looking for things from the scumpy folder down.\n", 37 | "import os\n", 38 | "import sys\n", 39 | "os.chdir('../')\n", 40 | "sys.path.insert(0,os.getcwd())\n", 41 | "print(os.getcwd())" 42 | ] 43 | }, 44 | { 45 | "cell_type": "code", 46 | "execution_count": 2, 47 | "id": "bfa6c1fe-c023-4169-b42c-820512fbf67f", 48 | "metadata": {}, 49 | "outputs": [], 50 | "source": [ 51 | "from Graph import *\n", 52 | "from GainsCalculator import *" 53 | ] 54 | }, 55 | { 56 | "cell_type": "code", 57 | "execution_count": 3, 58 | "id": "c83d0949-c9a8-429a-b1a2-eda0d229b558", 59 | "metadata": {}, 60 | "outputs": [ 61 | { 62 | "data": { 63 | "image/png": "iVBORw0KGgoAAAANSUhEUgAAALMAAACbCAYAAAAp66qoAAAABmJLR0QA/wD/AP+gvaeTAAAf80lEQVR4nO2deViTV9rG7zeEVRAJqyhqQYECitJxBrwQXKqt0wataOtQK8W2bnU6o1LrV6WKWh2pFFuQsah1KrUuUClSacGKgiAoihu4ICCLAgERZE9Icr4/IlRlETHJCW/yuy5qSULOHXLn8LznPOd5GEIIgQYN/Z8YDm0FGjTIC42ZNbAGjZk1sAYubQFsQiKRQCAQQCAQoK6uDhKJBA0NDRCLxTAwMICuri709fUxaNAgDB48GDwej7ZkVqExcx9oaWlBdnY2rl69itzcXOTl5aGwsBBVVVWQSCS9fh49PT0MHToUDg4OcHFxgbOzM9zc3ODk5ASGYRT4CtgJo1nNeDZSqRTnzp1DYmIiTp8+jezsbAiFQvB4vA4TOjg4YPDgwbC2toalpSV4PB44HA6MjIzA5XLR3NwMoVCI1tZWPHjwAOXl5aioqEBZWRmuX7+OvLw83LhxAyKRCObm5pg4cSImT54MHx8fDBs2jPavoD8QozFzD2RkZODAgQOIj49HeXk57OzsMGnSJHh7e8Pb21vuJhOLxbh8+TLS0tKQmpqK1NRU1NfX45VXXoGvry8WLFgAa2truY7JImJANDxBQ0MD2blzJxk9ejQBQMaMGUOCg4PJ1atXla5FKBSS3377jSxatIiYmZkRLpdL3nrrLZKcnKx0Lf2AIxozP6KhoYHs2LGDWFlZET09PTJ37lxy4sQJ2rI6EAqF5MiRI+TVV18lDMOQMWPGkCNHjhCpVEpbmqqgMbNEIiGRkZHE1NSUDBw4kKxbt47U1NTQltUjFy9eJD4+PoRhGPLXv/6VnD9/nrYkVUC9zXzx4kUyfvx4oq2tTQIDA1XexE+Tk5NDJk2aRDgcDlmyZAmpra2lLYkm6mlmqVRKtm3bRrS1tYmXlxfJzc2lLanPSKVSEh0dTaysrMjw4cNJRkYGbUm0UD8zP3jwgEyfPp1oa2uTkJAQ1sSc1dXV5I033iBcLpeEhITQlkMD9TJzaWkpcXZ2JjY2NqyMM6VSKQkNDSVaWlpk6dKlRCwW05akTI6ozQ5gQUEBJk+eDBMTE2RmZmLIkCG0JckdhmGwcuVK2Nraws/PD9XV1Th48CC4XPV4m9Ui0aiiogKvvfYarK2tkZaWxkojP86sWbOQlJSExMRELF68GERN9sVYb+bm5mbMmDEDOjo6OH78OAYNGkRbklKYOHEiYmJiEB0djeDgYNpylAPtQEfRLFmyhJiYmJDi4mLaUqiwa9cuwuFwyKlTp2hLUTTsvgA8duwYYRiGxMbG0pZClTlz5pChQ4eyfR36CGsTjUQiEZycnODu7o4ff/yRthyq1NbWwsHBAe+//z5CQkJoy1EU7D0DGBkZifLycmzdupW2FOqYmJggKCgI4eHhKCkpoS1HYbDSzFKpFGFhYVi6dClsbGyUMmZpaSkYhun0FRoa2vGYlJSUTvcri8WLF8PCwgI7d+5U2phKh3agowiSkpIIAHLjxg2ljltXV0dOnjxJ+Hw+AUAOHjzY6TGXL18mfD6fZGZmKlUbIYSsX7+eWFpaEpFIpPSxlQA7LwADAgKIh4cHtfEFAgHh8/mEz+eTurq6jttLSkoIn88nAoGAiq7i4mLCMAxJSkqiMr6COcLKMOPMmTOYNm0atfEtLCywadMmJCQkYM+ePR23b926FXv27IGFhQUVXcOHD8fIkSORnp5OZXxFw7p9zvv376OwsBAeHh5Udbi6uuLYsWPw8fHBuHHjUFhYiODgYGpGbmfChAnIzMykqkFRsG5mLikpASEEDg4OtKWAz+dj+/btmDp1Kuzs7KgbGQDs7e1RXFxMW4ZCYJ2Z79+/DwAwNTWlrETG3LlzwefzsWPHDjx8+JC2HJiamqKmpoa2DIXAOjO3tLQAAPT19SkrAaqqqlBQUICIiIhO8TMtDA0N0dTURFuGQmCdmU1MTADIdr1oEx8fjylTpmDYsGGIiopCYGAgsrKyqGqqqalhbSUl1pm5Pbyorq6mpuHhw4cIDQ3FRx991HHbRx99BD6fjy1btiA/P5+aturqapUJweQN68w8atQo6Onp4dKlS1TGv3LlCt577z28+uqrne6Ljo5GQkICHBwckJKSQkEdkJOTg9GjR1MZW9Gwzsy6uroYN24czp49q/SxGYbB2LFjkZCQgLFjxz5h2NLS0idyqadOnar0enKEEGRlZVFftlQUrDMzIDNKQkLCcxUxlAeEkCe+pkyZ0nHfsGHDOt1PlJywmJaWhgcPHjyhi02w0swBAQG4d+8eTpw4QVuKSrF3716MHz8eLi4utKUoBFaa2dbWFl5eXggLC6MtRWW4e/cuYmNj8eGHH9KWojBYm5yflpYGb29vJCUlYfr06bTlUCcgIACnT5/GzZs3oaurS1uOImB3SVsfHx8UFRXhwoUL0NPToy2HGllZWfD09MT+/fvh5+dHW46iYLeZy8rK4Orqivnz5+Pbb7+lLYcKjY2NcHNzg62tLX777Tc2V+Rn77EpALCxsUFkZCQiIiJw+PBh2nKUjlQqhb+/PxoaGvDDDz+w2cgAWJgC+jTz5s3DuXPn4O/vDzMzM0ydOpW2JKWxfPlyJCYmIjk5GZaWlrTlKB6lnweggEQiIe+++y4xMjIiKSkptOUoHKlUSlatWkW0tLRIXFwcbTnKgp3HprpCJBKRefPmEV1d3S7P5rEFoVBI/Pz8iI6ODjlw4ABtOcpEfcxMiGyGXrlyJWEYhnz22WesO9hZXFxMJkyYQAYOHKhSLSyUhHqZuZ29e/eSAQMGEHd3d1JQUEBbjlyIiYkhJiYmxNnZuV8XT38B1NPMhBBy/fp14urqSvT19cnGjRtJa2srbUl9oqioiLz55psEAFm0aBFpbm6mLYkW6mtmQmRxdEhICDE0NCQjR44k0dHR/aZAd1VVFfnss8+Ivr4+cXJyUofCiM9Cvc3cTllZGfH39ydcLpc4ODiQffv2kZaWFtqyuqS0tJR8+umnxNDQkFhYWJCwsDDWxf59RGPmx7l9+zZ5//33iY6ODuHxeOTf//63SsSfIpGIJCQkED6fT7S0tIiVlRXZvn07aWpqoi1NldCYuSsqKyvJli1byEsvvUQAEEdHR7J27VqSnZ2ttDCkvr6exMfHE39/f8Lj8QjDMGTq1KnkyJEjRCgUKkVDP4O9JW3lgVQqRUZGBo4ePYq4uDiUlJTA2NgYnp6e8PT0hJubG1xcXF64n7VYLMbt27eRm5uLrKwsnDlzBpcuXYJUKoWHhwdmz56N2bNnY8SIEfJ5YeyE3YlG8iY3Nxepqan4+uuvUVNT01EHg8fjwd7eHlZWVrCxsYGFhQWMjY2hq6sLAwMD6OrqoqGhAWKxGA0NDaivr0dZWRkEAgFKS0uRn58PkUgELpcLfX19eHl5wd/fH15eXuqxDS0fNGZ+XvLy8jBmzBgcPHgQU6dOxbVr15CXl4eCggJUVlbi3r17EAgEqK+vh1AoRFNTE0QiEQwNDaGtrQ0jIyMMHDgQQ4YMgZWVFYYOHQpHR0c4OzvDyckJ7777Lm7duoUrV66Aw2F1Hpi80Zj5efH19UV+fr7CzPb4h+Xtt9+W+/OzGI2Zn4ecnBz85S9/QVxcHGbOnKmwcd59911cuHABeXl5atPDTw5ozPw8vPnmm6isrER2drZCc4Nv374NJycn7N27FwsWLFDYOCxDY+bekp2djb/97W9ITEzE66+/rvDxFi5ciFOnTuHWrVvQ0dFR+HgsQGPm3jJt2jS0tLQorVB3SUkJ7O3tERER8USZLw3dojFzb0hPT8fEiRORkpKCyZMnK23cZcuW4fjx48jPz2friWp5ojFzb5g0aRK4XC7++OMPpY5bUVEBOzs7hISEYPny5Uodux/C7gOt8iApKQmpqalYv3690scePHgwFi9ejC1btqC5uVnp4/c3NDPzM3B3d4epqSmOHz9OZfzq6mrY2dnhiy++QGBgIBUN/QTNzNwTx44dw/nz56nMyu2Ym5tj2bJl2LZtGxoaGqjp6A9oZuZuIIR0FE/5+eefqWqpqamBra0tVq9ejbVr11LVosJoZubuiI2NxdWrV/HFF1/QlgJTU1OsWLEC27dvV4n2FqqKxsxdIJFIsGHDBrzzzjtwdXWlLQcAsGLFCnA4HE1l0x7QmLkLfvrpJ9y6dUslZuV2jI2NsWrVKoSFhaGqqoq2HJVEEzM/hUQigbOzMzw8PLBv3z7acp6gqakJdnZ28Pf3x7Zt22jLUTU0MfPT7Nu3D0VFRQgKCqItpRMDBgzA6tWrER4ejvLyctpyVA7NzPwYIpEIjo6OmD59Onbt2kVbTpe0trZi1KhR8PX1xY4dO2jLUSU0M/Pj7N69G+Xl5Sq9/KWnp4c1a9Zg165dKCsroy1HpdDMzI/oTzNef/gLQgHNzNxOZGQkampqsHr1atpSnomOjg4+//xzfP/99ygqKqItR2XQzMzon6sEqrzqQgnNzAwA33zzDZqamrBq1SraUnqNlpYW1q5di+joaNy8eZO2HJVA7Wfmhw8fwtbWFh9//DE2btxIW85zIZFIMGbMGLi6uuKnn36iLYc2mpk5LCwMUqkUK1asoC3ludHS0sKGDRtw+PBhXLlyhbYc6qj1zFxbWwtbW1sEBgaq9HJcT7Rn97300ks4evQobTk0Ue+Zedu2beByufjkk09oS+kzDMMgODgYv/zyC86fP09bDlXUdmZm2wkO2idiVAD1nZm3bNmCAQMGYNmyZbSlyIXg4GAkJibizJkztKVQQy1n5vZTz9u2bcM///lP2nLkBq1T5CqCes7MmzZtgrm5ORYtWkRbilzZvHkzTp48iVOnTtGWQgW1m5nZXilo2rRpaG5uRkZGBm0pykb9isB88MEHSElJYW0NN2XXxFMh1MvM6lJdU1nVSlUMdsbMd+/excWLFzvdvmHDBtja2sLPz4+CKuWxZcsWXLp0CceOHXvidolEgl9//ZWSKiUg/6Y/9Pn5558JwzDEx8eHXL16lRBCSG5uLuFwOOTQoUOU1SkHX19f4uLiQiQSCZFIJOTw4cNk5MiRRFtbu9807nxO2Nk6LSQkhHC5XMLlcgnDMOTtt98m06dP73hz1YH2D29gYCBxcnIiDMMQDodDAJCioiLa8hTBEVaGGQUFBWAYBmKxGIQQxMXF4cSJE+DxeGqTzF5RUQFra2ts374dt27dAiEEUqkUAFBYWEhZnWJgpZlv3ryJtra2ju/b2tpACEFmZiYcHR3x3nvvsdbU6enp8PT0xLRp01BZWQlAFiu3w+VyNWbuT9y+fbvL29va2iCRSHD48GE4OjoiKipKycoUh0QiwcyZMzFx4kScO3cOgKxZ5tNwOBwUFBQoW55SYJ2ZhUJhx4zUHeRR2uQ777yjJFWKR0tLC2vWrIGhoWGPj2tra0N+fr6SVCkX1pn5zp07ID0snXO5XLi7u+OPP/6AsbGxEpUpHg8PD2RlZWHQoEHdtlwjhLD2mBXrzNzTn1Aul4tp06bhxIkTz5zB+ivOzs5IT0+HqakptLW1u3xMSUlJjx/4/gorzdzVm8jhcDB79mzEx8dDT0+PgjLl4eDggHPnzsHa2rrL34VQKERFRQUFZYqFdWYuLCzstIXL4XCwcOFCHDx4sNvZim0MHz4cWVlZsLOz6/I1s3FFg3Vmzs/Ph0gk6view+FgyZIliIqKUrvG6lZWVsjIyICLi8sThtbS0mLligbr3t3HL24YhsGnn36KnTt3qlPCzRPweDycOnUKbm5uHYZm61qzSnUZl0gkEAgEEAgEqKurg0QiQUNDA8RiMQwMDKCrqwt9fX0MGjQIgwcPBo/H6/Tzj5d6/eqrr/pVYRdFYWxsjJMnT+LNN99Eeno6RCJR12vxhABtYkDUBoglsu8lEoAA4HAADiP7l6sF6GoDKtaknoqalpYWZGdn4+rVq8jNzUVeXh4KCwtRVVX1xG7Vs9DT08PQoUPh4OAAFxcXmJubQywWg2EYfPPNN6w6EvWiDBgwAElJSXj77bcRHx+PG3l5wL0qoKkFaGoGWoR/Gri3MAygow0M0AMGGAAD9AHDR/9SQClmlkqlOHfuHBITE3H69GlkZ2dDKBSCx+PBxcUFo0ePxpw5czB48GBYW1vD0tISPB4PHA4HRkZG4HK5aG5uhlAoRGtrKx48eIDy8nJUVFSgrKwM169fx++//468vDwAgJGREU6fPg2GYeDj44Nhw4Yp42WqNvWN0HnwEDFrN2F+fROSzmcBhY9K4vZ1mY4QQCiSfdU1Ao9yP8DVAowHAiZGgNkgQFc5hyAUmpyfkZGBAwcOID4+HuXl5bCzs8OkSZPg7e0Nb29vuZts9+7dqKurg5aWFlJTU5Gamor6+nq88sor8PX1xYIFC2BtbS3XMVWah41AVQ1QXSsLHzgMICWQSKVYEroV/1m8HKYDFbVxxAAMZIY3NADMeYCVqWwmVwzyP2nS2NiI/fv3Y9euXbh27RrGjBkDX19fvPXWWxg9erQ8h+pEa2vrE2vIIpEIKSkpiIuLw9GjR1FXVwc+n4+lS5di2rRpCtVCDYkUENyXhRDNrbJQoIu3mBACkbgNutpKOjrWfgHOMwaGWAAmA+U9gvzM3NjYiL179+I///lPh2kWLVqEV199VR5P/8KIRCLEx8cjKioKJ0+exOjRo7Fu3TrMmTOHHSsdEilQWQ2UVDx/7KtM2j9cBnrAiCGAuYm8nvnFzSyVSvHdd98hKCgIbW1t+OSTT7BixYpOKw2qRE5ODoKDg5GQkIDx48cjIiIC48ePpy2r75RXA3fuAmIpZEsP/QEGwKMQxH44YDTgRZ/wxc4A5uTkwN3dHf/6178QEBCAO3fuYNOmTSptZABwc3NDfHw8Ll68CAMDA7i7u2Pp0qWoq6ujLe35aGwGLl4HbpfIZuN+Y2SgQ2tTC5Bz47HX0Hf6ZGZCCEJCQuDu7g59fX1cunQJX331lcqb+GnGjRuHlJQU/PDDD/jll18wduxYnD17lras3lFWKTNBUwttJS9Ge2BQcR+4kAvUN/b5qZ7bzLW1tXj99dexbt06fPnllzh9+jScnZ37LIA2DMNg/vz5uHbtGlxcXODt7Y2vvvqKtqzuEUuAK7eAorsyI6hqbPy8ECLbrLl8S/ZB7QPPtc5cVlaGGTNmoL6+HhkZGf07znwKMzMzJCQkICwsDKtXr8adO3cQHh4OLS0t2tL+RCgCrubLNjjYCHn0nzv3gFYhMHLYn6sgvaDXZi4oKMDkyZNhYmKCzMxMDBkypA9qVRuGYbBy5cqO2hrV1dU4ePBgt4nuSqVFCFy+KVsvZsts3B2EAOX3ZTO1k12vDd2rMKOiogKvvfYarK2tkZaWxkojP86sWbOQlJSExMRELF68mH4iu6hNFlqog5E7IMD9h0B+Sa9/4plmbm5uxowZM6Cjo4Pjx49j0KBBLySxvzBx4kTExMQgOjoawcHB9IRIpbLQQtSmRkZuhwCV94Hi3vUJf6aZV61ahdLSUvz+++8wMzN7YXn9ib///e8IDw/Hpk2bcPr0aToiCsuA5hY1NPJjlJQDdQ3PfFiPmyYJCQmYOXMmYmJi4OvrK1d9/Ym5c+ciKysL165dU+5fppo6IJd9SfTPDQNAWxsY7yJLYuqa7jdNRCIRVqxYAT8/P7U2MgBERUVBKBRiy5YtyhuUEKCgFLJ3Us0hkF0vlPZ8brFbM0dGRqK8vBxbt26Vt7R+h4mJCYKCghAeHo6Skt5fkLwQ96qA1jb0r109BUIIcFcAtIq6fUiXZpZKpQgLC8PSpUthY2OjMH1PExoaCoZhnvgKDQ3t8TGlpaVK0bZ48WJYWFhg586dShlPtnGgfCMzk8Y/8yv08I9K19VBeVW3d3UZMycnJ+O1117DjRs34OjoqFBtT5Ofn4/AwEAkJCTg8uXLcHV17fSYlJQU7NixA9u3b4e9vb3StG3YsAG7du1CWVmZYk9519bLVjAowEwaj5NfR2KK25MbYg+bGvHel19giJkF/rtyDRVtAABtLuDh2tXac9cx86FDh+Dh4aF0IwOAvb099uzZAz6fj9jY2C4fExMTg2PHjinVyAAQEBCAqqoqxTfAqXrwXDtf8uYVh5c73bb9kGw2Dg6g3NSoTdztykaXZj5z5gzV5HULCwt8/vnn2Lx5Mw4dOvTEfbt376bW53r48OEYOXIk0tPTFTtQbT21pbiTX0fCeMCT1Z4OpSRjc/RebPpgKSxMKCeTMRzgYS/NfP/+fRQWFsLDw0PhunrC3d0dUVFR+Mc//tFR6O/KlSswMjJS+oz8OBMmTEBmZqbiBmgTy3IwKPF0eJF1/Rr+sXEtDn7xJVztRlFS9RhEKjsO1gWdzNxeh8zBwUHhup7FRx99BD6fj8DAQJSWliI2Nhbz5s2jqsne3h7FxcWKG6CHq3VlUyqohMeyhVj33geYN2U6bTl/0k2iVacMmvv37wMATE1NFSuol0RERGD48OFISEhQ3rJYD5iamqKmpkZxA7R1rqlMi+XfhIA/YSI2fbCEtpQn6aLuNNCFmVtaZMne+vp0ah88zeMnuFWhZIChoSGampoUN0D7cX3KBO3dhYSzZyCIS6ItpTPSrq8nOoUZJiayA4a1tbWKFdRPqampUeyJmu63a5VG+wVfZuT3XV7wlQr6ljwvN7r5HXUyc3t4UV1drVhB/ZTq6mrFhmDadHOnrxTe7rjgc3fqXBoiv6wUu3/9hYKyx+gmv7yTmUeNGgU9PT1cunRJ4Zp6Q1VVVZf/T4ucnBzF1v/Q16O2xlxV+wBjP/Dr8YIv4WwahllaKVnZYzAAjAy6vKuTxXV1dTFu3DicPXsW8+fPV7S0Hnm6noWlpSUAUEuWJ4QgKysL69evV9wgHEZ2/L5BgXF5N6zfJ2tYtDl6LzZH7+32cSe/jlSWpC5ggIFddz3ocjs7KCgI//vf/1BcXKxaZ+Aok5qaikmTJnUcflUYxfeA0kr1zmHuib84d1Wcsevt7ICAANy7dw8nTpxQvLB+xN69ezF+/HjFGhkArMw0Ru6OHqqMdmlmW1tbeHl5ISwsTKG6+hN3795FbGwsPvzwQ8UPpqcLGBtSzc9QSRgGsDbv9u5u85k3btyI5ORkJCcnK0RXfyMoKAiWlpbw9/dXzoAvDdHMzo/TXgvasvujez0em/Lx8UFRUREuXLjA+g5NPZGVlQVPT0/s378ffn5+yhv42m2grr7bTQK1gmEAx5cAi27X+HsunFhWVgZXV1fMnz8f3377rWJEqjiNjY1wc3ODra0tfvvtN+VWDBWKgOxcWYVPdYZhgEGGwJge84V6LpxoY2ODyMhIRERE4PDhw/IV2A+QSqXw9/dHQ0MDfvjhB+WXvtXVAexHKHdMlYOR7fg52j7zkc/cbpo3bx7OnTsHf39/mJmZYerUqXKR2B9Yvnw5EhMTkZyc3LHGrXQseLI153sC9TwOyABwHtmrivu9qmgUGhqKOXPm4K233lL8KQsVgBCCwMBAREVF4eDBg5g4cSJdQXY2gLmp+q1uMIysPJdx71pD98rMHA4H+/btwxtvvIEZM2Z0Ov3BJkQiEebPn4/w8HDs378fs2bNoi1JhuMIWZV5tfHzows+s97XKel1SVttbW0cOHAAH3/8Mfz8/LBmzRq0tbX1SaaqUlJSgsmTJ+PXX3/F8ePHlbty8SwYBnjZFhhCKdxRFsyjXoNjRvW0ctElz1WfmcPhIDQ0FHv27EFERAS8vLxY0+kzNjYW48aNw8OHD3H27FmV6cXSCTsbwGGELIeDbWEHA0BfF3B7uU8NfPpUOX/hwoXIzs5GS0sLRo8ejU2bNkEo7J81g+/cuQM+n4+5c+di7ty5yM7OVv3i6VZmwCvOsiY3bIg72l+ClTnwilOfm2L2uafJyy+/jOzsbAQHByMkJAQuLi748ccfn6vDKk2qq6uxZs0aODs7o6ioCKdOncJ3332nMidsnomBnuyNtx3Sz2dpBtDTA1wdZI16OH1vsyOX1ml3797FunXrcODAAdjZ2WHNmjWYN2+eSu4alpWVITw8HP/9739hYGCA//u//8PHH3+s2KIuikYoklWbr3ogm+X6w44hA0BLCxhuLesL+OIfRvk2tSwoKMCXX36Jn376CYaGhliwYAE+/PBD6n+229rakJSUhKioKCQmJsLc3ByBgYFYunQpDAy6TvTul7QIZcUFK2v+7I6qMhBZzQtAtgkybLAsaegFZuKnkH+HVgAQCAT4/vvvsXv3bty5cweOjo7w9fXFrFmzMG7cOKXkSDc0NODUqVM4evQoEhISUFtbiylTpmDx4sWYOXMmdHSU1JmUBqI2WZHu8mrZrP2ozTA12htZDjICrC1ky23yD4sUY+Z2pFIpMjIycPToUcTFxaGkpATGxsbw9PSEp6cn3Nzc4OLi8sL9rMViMW7fvo3c3FxkZWXhzJkzuHTpEqRSKTw8PDB79mzMnj0bI0aMkM8L6088bATu1wLVDwBh2yMTEcXuJj7e4njgAFnfbLNBstRWxaFYMz9Nbm4uUlNTkZaWhvT0dJSXy8r783g82Nvbw8rKCjY2NrCwsICxsTF0dXVhYGAAXV1dNDQ0QCwWo6GhAfX19SgrK4NAIEBpaSny8/MhEonA5XLx8ssvw9vbG15eXvDy8qK3Da2KNLXISlvVNcqy8dprdDCPNW1/Hjcwj362fdZnGJlheQMBYyPZzp3iGr8/jXLN/DQ1NTW4du0a8vLyUFBQgMrKSty7dw8CgQD19fUQCoVoamqCSCSCoaEhtLW1YWRkhIEDB2LIkCGwsrLC0KFD4ejoCGdnZzg5OUFXV6GffnbRJpYZvLlFFm+L2mQty4RiQCqR1fCQPuo12L5iwtGSxbx62oCOjiwZykBPtpxmoC97HB3omlmDBjnyYr2zNWhQJTRm1sAaNGbWwBq4AGJoi9CgQQ5k/T8YKL7AJpXdewAAAABJRU5ErkJggg==\n", 64 | "text/plain": [ 65 | "" 66 | ] 67 | }, 68 | "metadata": {}, 69 | "output_type": "display_data" 70 | } 71 | ], 72 | "source": [ 73 | "path = 'dot_atlas/good_bad_trols_G14.dot'\n", 74 | "graph = Graph(path)\n", 75 | "graph.draw(jupyter=True)" 76 | ] 77 | }, 78 | { 79 | "cell_type": "code", 80 | "execution_count": 4, 81 | "id": "4c12a223-53ac-4d70-b124-e6f812dff43d", 82 | "metadata": {}, 83 | "outputs": [ 84 | { 85 | "data": { 86 | "text/latex": [ 87 | "$\\displaystyle \\begin{array}{l}\n", 88 | "\\alpha_{\\underline{Y}|\\underline{X}} = \\frac{\\left\\langle\\underline{X},\\underline{Y}\\right\\rangle}{\\sigma^2_{\\underline{X}}}\n", 89 | "\\\\\n", 90 | "\\alpha_{\\underline{Z}|\\underline{X}} = \\frac{\\left\\langle\\underline{X},\\underline{Z}\\right\\rangle}{\\sigma^2_{\\underline{X}}}\n", 91 | "\\\\\n", 92 | "\\left\\langle\\underline{Y},\\underline{Z}\\right\\rangle = \\frac{\\left\\langle\\underline{X},\\underline{Y}\\right\\rangle \\left\\langle\\underline{X},\\underline{Z}\\right\\rangle}{\\sigma^2_{\\underline{X}}}\n", 93 | "\\end{array}$" 94 | ], 95 | "text/plain": [ 96 | "\\begin{array}{l}\n", 97 | "\\alpha_{\\underline{Y}|\\underline{X}} = \\frac{\\left\\langle\\underline{X},\\underline{Y}\\right\\rangle}{\\sigma^2_{\\underline{X}}}\n", 98 | "\\\\\n", 99 | "\\alpha_{\\underline{Z}|\\underline{X}} = \\frac{\\left\\langle\\underline{X},\\underline{Z}\\right\\rangle}{\\sigma^2_{\\underline{X}}}\n", 100 | "\\\\\n", 101 | "\\left\\langle\\underline{Y},\\underline{Z}\\right\\rangle = \\frac{\\left\\langle\\underline{X},\\underline{Y}\\right\\rangle \\left\\langle\\underline{X},\\underline{Z}\\right\\rangle}{\\sigma^2_{\\underline{X}}}\n", 102 | "\\end{array}" 103 | ] 104 | }, 105 | "execution_count": 4, 106 | "metadata": {}, 107 | "output_type": "execute_result" 108 | } 109 | ], 110 | "source": [ 111 | "cal = GainsCalculator(graph)\n", 112 | "cal.calculate_gains()\n", 113 | "cal.print_alpha_list()" 114 | ] 115 | } 116 | ], 117 | "metadata": { 118 | "kernelspec": { 119 | "display_name": "Python 3 (ipykernel)", 120 | "language": "python", 121 | "name": "python3" 122 | }, 123 | "language_info": { 124 | "codemirror_mode": { 125 | "name": "ipython", 126 | "version": 3 127 | }, 128 | "file_extension": ".py", 129 | "mimetype": "text/x-python", 130 | "name": "python", 131 | "nbconvert_exporter": "python", 132 | "pygments_lexer": "ipython3", 133 | "version": "3.10.9" 134 | } 135 | }, 136 | "nbformat": 4, 137 | "nbformat_minor": 5 138 | } 139 | -------------------------------------------------------------------------------- /jupyter_notebooks/G18-gains.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "68d1a8ef-f7dc-4d43-959a-878bb0aa2e97", 6 | "metadata": {}, 7 | "source": [ 8 | "# G18 gains\n", 9 | "Main Reference:\n", 10 | "\n", 11 | "* A Crash Course in Good and Bad Controls,\n", 12 | "by Carlos Cinelli, Andrew Forney and Judea Pearl\n", 13 | "\n", 14 | "In this notebook, we derive, using a symbolic manipulator (SymPy), the gains (i.e., path coefficients) as a function of the covariances, for \n", 15 | "\n", 16 | "## G18 \n", 17 | "\n", 18 | "If the DAG is not fully connected, there are some constraints between the covariances. There is one constraint for each arrow missing from a fully connected DAG." 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": 1, 24 | "id": "73d99326-85ad-40c0-b3ba-cc873ed5276e", 25 | "metadata": {}, 26 | "outputs": [ 27 | { 28 | "name": "stdout", 29 | "output_type": "stream", 30 | "text": [ 31 | "C:\\Users\\rrtuc\\Desktop\\backed-up\\python-projects\\scumpy\n" 32 | ] 33 | } 34 | ], 35 | "source": [ 36 | "# this makes sure it starts looking for things from the scumpy folder down.\n", 37 | "import os\n", 38 | "import sys\n", 39 | "os.chdir('../')\n", 40 | "sys.path.insert(0,os.getcwd())\n", 41 | "print(os.getcwd())" 42 | ] 43 | }, 44 | { 45 | "cell_type": "code", 46 | "execution_count": 2, 47 | "id": "bfa6c1fe-c023-4169-b42c-820512fbf67f", 48 | "metadata": {}, 49 | "outputs": [], 50 | "source": [ 51 | "from Graph import *\n", 52 | "from GainsCalculator import *" 53 | ] 54 | }, 55 | { 56 | "cell_type": "code", 57 | "execution_count": 3, 58 | "id": "c83d0949-c9a8-429a-b1a2-eda0d229b558", 59 | "metadata": {}, 60 | "outputs": [ 61 | { 62 | "data": { 63 | "image/png": "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\n", 64 | "text/plain": [ 65 | "" 66 | ] 67 | }, 68 | "metadata": {}, 69 | "output_type": "display_data" 70 | } 71 | ], 72 | "source": [ 73 | "path = 'dot_atlas/good_bad_trols_G18.dot'\n", 74 | "graph = Graph(path)\n", 75 | "graph.draw(jupyter=True)" 76 | ] 77 | }, 78 | { 79 | "cell_type": "code", 80 | "execution_count": 4, 81 | "id": "4c12a223-53ac-4d70-b124-e6f812dff43d", 82 | "metadata": {}, 83 | "outputs": [ 84 | { 85 | "data": { 86 | "text/latex": [ 87 | "$\\displaystyle \\begin{array}{l}\n", 88 | "\\alpha_{\\underline{Y}|\\underline{X}} = \\frac{\\left\\langle\\underline{X},\\underline{Y}\\right\\rangle}{\\sigma^2_{\\underline{X}}}\n", 89 | "\\\\\n", 90 | "\\left\\langle\\underline{X},\\underline{Z}\\right\\rangle = \\frac{\\left\\langle\\underline{X},\\underline{Y}\\right\\rangle \\left\\langle\\underline{Y},\\underline{Z}\\right\\rangle}{\\sigma^2_{\\underline{Y}}}\n", 91 | "\\\\\n", 92 | "\\alpha_{\\underline{Z}|\\underline{Y}} = \\frac{\\left\\langle\\underline{Y},\\underline{Z}\\right\\rangle}{\\sigma^2_{\\underline{Y}}}\n", 93 | "\\end{array}$" 94 | ], 95 | "text/plain": [ 96 | "\\begin{array}{l}\n", 97 | "\\alpha_{\\underline{Y}|\\underline{X}} = \\frac{\\left\\langle\\underline{X},\\underline{Y}\\right\\rangle}{\\sigma^2_{\\underline{X}}}\n", 98 | "\\\\\n", 99 | "\\left\\langle\\underline{X},\\underline{Z}\\right\\rangle = \\frac{\\left\\langle\\underline{X},\\underline{Y}\\right\\rangle \\left\\langle\\underline{Y},\\underline{Z}\\right\\rangle}{\\sigma^2_{\\underline{Y}}}\n", 100 | "\\\\\n", 101 | "\\alpha_{\\underline{Z}|\\underline{Y}} = \\frac{\\left\\langle\\underline{Y},\\underline{Z}\\right\\rangle}{\\sigma^2_{\\underline{Y}}}\n", 102 | "\\end{array}" 103 | ] 104 | }, 105 | "execution_count": 4, 106 | "metadata": {}, 107 | "output_type": "execute_result" 108 | } 109 | ], 110 | "source": [ 111 | "cal = GainsCalculator(graph)\n", 112 | "cal.calculate_gains()\n", 113 | "cal.print_alpha_list()" 114 | ] 115 | } 116 | ], 117 | "metadata": { 118 | "kernelspec": { 119 | "display_name": "Python 3 (ipykernel)", 120 | "language": "python", 121 | "name": "python3" 122 | }, 123 | "language_info": { 124 | "codemirror_mode": { 125 | "name": "ipython", 126 | "version": 3 127 | }, 128 | "file_extension": ".py", 129 | "mimetype": "text/x-python", 130 | "name": "python", 131 | "nbconvert_exporter": "python", 132 | "pygments_lexer": "ipython3", 133 | "version": "3.10.9" 134 | } 135 | }, 136 | "nbformat": 4, 137 | "nbformat_minor": 5 138 | } 139 | -------------------------------------------------------------------------------- /jupyter_notebooks/G8-gains.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "68d1a8ef-f7dc-4d43-959a-878bb0aa2e97", 6 | "metadata": {}, 7 | "source": [ 8 | "# G8 gains\n", 9 | "Main Reference:\n", 10 | "\n", 11 | "* A Crash Course in Good and Bad Controls,\n", 12 | "by Carlos Cinelli, Andrew Forney and Judea Pearl\n", 13 | "\n", 14 | "In this notebook, we derive, using a symbolic manipulator (SymPy), the gains (i.e., path coefficients) as a function of the covariances, for \n", 15 | "\n", 16 | "## G8 \n", 17 | "\n", 18 | "If the DAG is not fully connected, there are some constraints between the covariances. There is one constraint for each arrow missing from a fully connected DAG." 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": 1, 24 | "id": "73d99326-85ad-40c0-b3ba-cc873ed5276e", 25 | "metadata": {}, 26 | "outputs": [ 27 | { 28 | "name": "stdout", 29 | "output_type": "stream", 30 | "text": [ 31 | "C:\\Users\\rrtuc\\Desktop\\backed-up\\python-projects\\scumpy\n" 32 | ] 33 | } 34 | ], 35 | "source": [ 36 | "# this makes sure it starts looking for things from the scumpy folder down.\n", 37 | "import os\n", 38 | "import sys\n", 39 | "os.chdir('../')\n", 40 | "sys.path.insert(0,os.getcwd())\n", 41 | "print(os.getcwd())" 42 | ] 43 | }, 44 | { 45 | "cell_type": "code", 46 | "execution_count": 2, 47 | "id": "bfa6c1fe-c023-4169-b42c-820512fbf67f", 48 | "metadata": {}, 49 | "outputs": [], 50 | "source": [ 51 | "from Graph import *\n", 52 | "from GainsCalculator import *" 53 | ] 54 | }, 55 | { 56 | "cell_type": "code", 57 | "execution_count": 3, 58 | "id": "c83d0949-c9a8-429a-b1a2-eda0d229b558", 59 | "metadata": {}, 60 | "outputs": [ 61 | { 62 | "data": { 63 | "image/png": "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\n", 64 | "text/plain": [ 65 | "" 66 | ] 67 | }, 68 | "metadata": {}, 69 | "output_type": "display_data" 70 | } 71 | ], 72 | "source": [ 73 | "path = 'dot_atlas/good_bad_trols_G8.dot'\n", 74 | "graph = Graph(path)\n", 75 | "graph.draw(jupyter=True)" 76 | ] 77 | }, 78 | { 79 | "cell_type": "code", 80 | "execution_count": 4, 81 | "id": "4c12a223-53ac-4d70-b124-e6f812dff43d", 82 | "metadata": {}, 83 | "outputs": [ 84 | { 85 | "data": { 86 | "text/latex": [ 87 | "$\\displaystyle \\begin{array}{l}\n", 88 | "\\left\\langle\\underline{X},\\underline{Z}\\right\\rangle = 0\n", 89 | "\\\\\n", 90 | "\\alpha_{\\underline{Y}|\\underline{X}} = \\frac{\\left\\langle\\underline{X},\\underline{Y}\\right\\rangle \\sigma^2_{\\underline{Z}} - \\left\\langle\\underline{X},\\underline{Z}\\right\\rangle \\left\\langle\\underline{Z},\\underline{Y}\\right\\rangle}{- \\left\\langle\\underline{X},\\underline{Z}\\right\\rangle^{2} + \\sigma^2_{\\underline{X}} \\sigma^2_{\\underline{Z}}}\n", 91 | "\\\\\n", 92 | "\\alpha_{\\underline{Y}|\\underline{Z}} = \\frac{- \\left\\langle\\underline{X},\\underline{Y}\\right\\rangle \\left\\langle\\underline{X},\\underline{Z}\\right\\rangle + \\left\\langle\\underline{Z},\\underline{Y}\\right\\rangle \\sigma^2_{\\underline{X}}}{- \\left\\langle\\underline{X},\\underline{Z}\\right\\rangle^{2} + \\sigma^2_{\\underline{X}} \\sigma^2_{\\underline{Z}}}\n", 93 | "\\end{array}$" 94 | ], 95 | "text/plain": [ 96 | "\\begin{array}{l}\n", 97 | "\\left\\langle\\underline{X},\\underline{Z}\\right\\rangle = 0\n", 98 | "\\\\\n", 99 | "\\alpha_{\\underline{Y}|\\underline{X}} = \\frac{\\left\\langle\\underline{X},\\underline{Y}\\right\\rangle \\sigma^2_{\\underline{Z}} - \\left\\langle\\underline{X},\\underline{Z}\\right\\rangle \\left\\langle\\underline{Z},\\underline{Y}\\right\\rangle}{- \\left\\langle\\underline{X},\\underline{Z}\\right\\rangle^{2} + \\sigma^2_{\\underline{X}} \\sigma^2_{\\underline{Z}}}\n", 100 | "\\\\\n", 101 | "\\alpha_{\\underline{Y}|\\underline{Z}} = \\frac{- \\left\\langle\\underline{X},\\underline{Y}\\right\\rangle \\left\\langle\\underline{X},\\underline{Z}\\right\\rangle + \\left\\langle\\underline{Z},\\underline{Y}\\right\\rangle \\sigma^2_{\\underline{X}}}{- \\left\\langle\\underline{X},\\underline{Z}\\right\\rangle^{2} + \\sigma^2_{\\underline{X}} \\sigma^2_{\\underline{Z}}}\n", 102 | "\\end{array}" 103 | ] 104 | }, 105 | "execution_count": 4, 106 | "metadata": {}, 107 | "output_type": "execute_result" 108 | } 109 | ], 110 | "source": [ 111 | "cal = GainsCalculator(graph)\n", 112 | "cal.calculate_gains()\n", 113 | "cal.print_alpha_list()" 114 | ] 115 | } 116 | ], 117 | "metadata": { 118 | "kernelspec": { 119 | "display_name": "Python 3 (ipykernel)", 120 | "language": "python", 121 | "name": "python3" 122 | }, 123 | "language_info": { 124 | "codemirror_mode": { 125 | "name": "ipython", 126 | "version": 3 127 | }, 128 | "file_extension": ".py", 129 | "mimetype": "text/x-python", 130 | "name": "python", 131 | "nbconvert_exporter": "python", 132 | "pygments_lexer": "ipython3", 133 | "version": "3.10.9" 134 | } 135 | }, 136 | "nbformat": 4, 137 | "nbformat_minor": 5 138 | } 139 | -------------------------------------------------------------------------------- /jupyter_notebooks/G9-gains.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "68d1a8ef-f7dc-4d43-959a-878bb0aa2e97", 6 | "metadata": {}, 7 | "source": [ 8 | "# G9 gains\n", 9 | "Main Reference:\n", 10 | "\n", 11 | "* A Crash Course in Good and Bad Controls,\n", 12 | "by Carlos Cinelli, Andrew Forney and Judea Pearl\n", 13 | "\n", 14 | "In this notebook, we derive, using a symbolic manipulator (SymPy), the gains (i.e., path coefficients) as a function of the covariances, for \n", 15 | "\n", 16 | "## G9 \n", 17 | "\n", 18 | "If the DAG is not fully connected, there are some constraints between the covariances. There is one constraint for each arrow missing from a fully connected DAG." 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": 1, 24 | "id": "73d99326-85ad-40c0-b3ba-cc873ed5276e", 25 | "metadata": {}, 26 | "outputs": [ 27 | { 28 | "name": "stdout", 29 | "output_type": "stream", 30 | "text": [ 31 | "C:\\Users\\rrtuc\\Desktop\\backed-up\\python-projects\\scumpy\n" 32 | ] 33 | } 34 | ], 35 | "source": [ 36 | "# this makes sure it starts looking for things from the scumpy folder down.\n", 37 | "import os\n", 38 | "import sys\n", 39 | "os.chdir('../')\n", 40 | "sys.path.insert(0,os.getcwd())\n", 41 | "print(os.getcwd())" 42 | ] 43 | }, 44 | { 45 | "cell_type": "code", 46 | "execution_count": 2, 47 | "id": "bfa6c1fe-c023-4169-b42c-820512fbf67f", 48 | "metadata": {}, 49 | "outputs": [], 50 | "source": [ 51 | "from Graph import *\n", 52 | "from GainsCalculator import *" 53 | ] 54 | }, 55 | { 56 | "cell_type": "code", 57 | "execution_count": 3, 58 | "id": "c83d0949-c9a8-429a-b1a2-eda0d229b558", 59 | "metadata": {}, 60 | "outputs": [ 61 | { 62 | "data": { 63 | "image/png": "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\n", 64 | "text/plain": [ 65 | "" 66 | ] 67 | }, 68 | "metadata": {}, 69 | "output_type": "display_data" 70 | } 71 | ], 72 | "source": [ 73 | "path = 'dot_atlas/good_bad_trols_G9.dot'\n", 74 | "graph = Graph(path)\n", 75 | "graph.draw(jupyter=True)" 76 | ] 77 | }, 78 | { 79 | "cell_type": "code", 80 | "execution_count": 4, 81 | "id": "4c12a223-53ac-4d70-b124-e6f812dff43d", 82 | "metadata": {}, 83 | "outputs": [ 84 | { 85 | "data": { 86 | "text/latex": [ 87 | "$\\displaystyle \\begin{array}{l}\n", 88 | "\\alpha_{\\underline{X}|\\underline{Z}} = \\frac{\\left\\langle\\underline{Z},\\underline{X}\\right\\rangle}{\\sigma^2_{\\underline{Z}}}\n", 89 | "\\\\\n", 90 | "\\left\\langle\\underline{Z},\\underline{Y}\\right\\rangle = \\frac{\\left\\langle\\underline{X},\\underline{Y}\\right\\rangle \\left\\langle\\underline{Z},\\underline{X}\\right\\rangle}{\\sigma^2_{\\underline{X}}}\n", 91 | "\\\\\n", 92 | "\\alpha_{\\underline{Y}|\\underline{X}} = \\frac{\\left\\langle\\underline{X},\\underline{Y}\\right\\rangle}{\\sigma^2_{\\underline{X}}}\n", 93 | "\\end{array}$" 94 | ], 95 | "text/plain": [ 96 | "\\begin{array}{l}\n", 97 | "\\alpha_{\\underline{X}|\\underline{Z}} = \\frac{\\left\\langle\\underline{Z},\\underline{X}\\right\\rangle}{\\sigma^2_{\\underline{Z}}}\n", 98 | "\\\\\n", 99 | "\\left\\langle\\underline{Z},\\underline{Y}\\right\\rangle = \\frac{\\left\\langle\\underline{X},\\underline{Y}\\right\\rangle \\left\\langle\\underline{Z},\\underline{X}\\right\\rangle}{\\sigma^2_{\\underline{X}}}\n", 100 | "\\\\\n", 101 | "\\alpha_{\\underline{Y}|\\underline{X}} = \\frac{\\left\\langle\\underline{X},\\underline{Y}\\right\\rangle}{\\sigma^2_{\\underline{X}}}\n", 102 | "\\end{array}" 103 | ] 104 | }, 105 | "execution_count": 4, 106 | "metadata": {}, 107 | "output_type": "execute_result" 108 | } 109 | ], 110 | "source": [ 111 | "cal = GainsCalculator(graph)\n", 112 | "cal.calculate_gains()\n", 113 | "cal.print_alpha_list()" 114 | ] 115 | } 116 | ], 117 | "metadata": { 118 | "kernelspec": { 119 | "display_name": "Python 3 (ipykernel)", 120 | "language": "python", 121 | "name": "python3" 122 | }, 123 | "language_info": { 124 | "codemirror_mode": { 125 | "name": "ipython", 126 | "version": 3 127 | }, 128 | "file_extension": ".py", 129 | "mimetype": "text/x-python", 130 | "name": "python", 131 | "nbconvert_exporter": "python", 132 | "pygments_lexer": "ipython3", 133 | "version": "3.10.9" 134 | } 135 | }, 136 | "nbformat": 4, 137 | "nbformat_minor": 5 138 | } 139 | -------------------------------------------------------------------------------- /jupyter_notebooks/r493.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrtucci/scumpy/90538d79a31974da3faae58c69dd4e90ef555e65/jupyter_notebooks/r493.pdf -------------------------------------------------------------------------------- /jupyter_notebooks/scumpyREADME.md: -------------------------------------------------------------------------------- 1 | The github website currently doesn't render jupyter 2 | notebooks 100% correctly. For instance, it doesn't recognize 3 | Latex macros in the markdown cells. 4 | It also fails to display standard output. 5 | Luckily, the jupyter project 6 | offers a service for rendering notebooks found at other websites. 7 | The link below will show all our notebooks rendered properly. 8 | 9 | http://nbviewer.jupyter.org/github/rrtucci/scumpy/tree/master/jupyter_notebooks 10 | 11 | -------------------------------------------------------------------------------- /latexify.py: -------------------------------------------------------------------------------- 1 | from Graph import * 2 | import sympy as sp 3 | from itertools import product 4 | from core_matrices import * 5 | from numerical_subs import * 6 | from copy import deepcopy 7 | 8 | """ 9 | 10 | The file contains a very complete substitution function do_latex_subs() that 11 | returns any symbolic input x after substituting symbolic parts by LaTeX. x 12 | can be an sp.Syybol, sp.Matrix, sp.Eq, etc. 13 | 14 | This file also contains various functions for printing matrices (sp.Matrix) 15 | and lists (list[sp.Matrix]) in LaTeX 16 | 17 | """ 18 | 19 | 20 | def round_expr(expr, num_digits): 21 | """ 22 | This function rounds the numerical parts of any symbolic expression. 23 | 24 | Parameters 25 | ---------- 26 | expr: most Sympy expressions 27 | num_digits: int 28 | 29 | Returns 30 | ------- 31 | type(expr) 32 | 33 | """ 34 | return expr.xreplace( 35 | {n: round(n, num_digits) for n in expr.atoms(sp.Number)}) 36 | 37 | 38 | def latex_time_superscript(time): 39 | """ 40 | This method returns a LaTeX superscript for various time inputs. 41 | 42 | Parameters 43 | ---------- 44 | time: None or str or int 45 | Must belong to list [None, "one", "n", "n_plus_one"] or be an int 46 | 47 | Returns 48 | ------- 49 | str 50 | 51 | """ 52 | if time is None: 53 | superscript = "" 54 | elif time == "one": 55 | superscript = r"^{[1]}" 56 | elif time == "n": 57 | superscript = r"^{[n]}" 58 | elif time == "n_plus_one": 59 | superscript = r"^{[n+1]}" 60 | elif isinstance(time, int): 61 | superscript = r"^{[" + str(time) + r"]}" 62 | else: 63 | assert False 64 | return superscript 65 | 66 | 67 | def sb_cov_str(row, col, time): 68 | """ 69 | This method returns a symbolic string for the entry at position (row, 70 | col) of the 1-time covariance matrix at time "time". Here row and col 71 | are ints. 72 | 73 | Parameters 74 | ---------- 75 | row: int 76 | col: int 77 | time: None or str or int 78 | 79 | Returns 80 | ------- 81 | str 82 | 83 | """ 84 | if time is None: 85 | sb_str = "cov" 86 | elif time in ["one", "n", "n_plus_one"]: 87 | sb_str = "cov_" + time 88 | elif isinstance(time, int): 89 | sb_str = "cov_n" + str(time) 90 | else: 91 | assert False 92 | sb_str += "_" + str(row) + "_" + str(col) 93 | return sb_str 94 | 95 | 96 | def latex_cov_str(row_nd, col_nd, time): 97 | """ 98 | This method returns a latex string for the entry at position (row_nd, 99 | col_nd) of the 1-time covariance matrix at time "time". Here row_nd and 100 | col_nd are node names. 101 | 102 | 103 | Parameters 104 | ---------- 105 | row_nd: str 106 | col_nd: str 107 | time: None or str or int 108 | 109 | Returns 110 | ------- 111 | str 112 | 113 | """ 114 | superscript = latex_time_superscript(time) 115 | if row_nd == col_nd: 116 | latex_str = r"\sigma^2_{\underline{" + row_nd + \ 117 | r"}" + superscript + r"}" 118 | else: 119 | latex_str = r"\left\langle\underline{" + row_nd + \ 120 | r"}" + superscript + r",\underline{" + col_nd + \ 121 | r"}" + superscript + r"\right\rangle" 122 | return latex_str 123 | 124 | 125 | def sb_cov2times_str(row, col, time, delta=False): 126 | """ 127 | This method returns a symbolic string for the entry at position (row, 128 | col) of the following 2-times covariance matrices between times time and 129 | time+1. Here row_nd and col_nd are node names. 130 | 131 | if delta=False, cov2times_n 132 | 133 | if delta=True, d_cov2times_n 134 | 135 | Parameters 136 | ---------- 137 | row: int 138 | col: int 139 | time: None or str or int 140 | delta: bool 141 | 142 | Returns 143 | ------- 144 | str 145 | 146 | """ 147 | xtra_str = "" 148 | if delta: 149 | xtra_str = "d_" 150 | if time == "n": 151 | sb_str = xtra_str + "cov2times_n" 152 | elif isinstance(time, int): 153 | sb_str = xtra_str + "cov2times_n" + str(time) 154 | else: 155 | assert False 156 | sb_str += "_" + str(row) + "_" + str(col) 157 | return sb_str 158 | 159 | 160 | def latex_cov2times_str(row_nd, col_nd, time, delta=False): 161 | """ 162 | This method returns a symbolic string for the entry at position (row_nd, 163 | col_nd) of the following 2-times covariance matrices between times time 164 | and time+1. Here row_nd and col_nd are node names. 165 | 166 | if delta=False, cov2times_n 167 | 168 | if delta=True, d_cov2times_n 169 | 170 | Parameters 171 | ---------- 172 | row_nd: str 173 | col_nd: str 174 | time: None or str or int 175 | delta: bool 176 | 177 | Returns 178 | ------- 179 | str 180 | 181 | """ 182 | if time == "n": 183 | superscript = latex_time_superscript("n") 184 | superscript_plus = latex_time_superscript("n_plus_one") 185 | elif isinstance(time, int): 186 | superscript = latex_time_superscript(time) 187 | superscript_plus = latex_time_superscript(time + 1) 188 | else: 189 | assert False 190 | xtra_str = "" 191 | if delta: 192 | xtra_str = r"\Delta " 193 | latex_str = r"\left\langle\underline{" + row_nd + \ 194 | r"}" + superscript + r"," + xtra_str + \ 195 | r"\underline{" + col_nd + \ 196 | r"}" + superscript_plus + r"\right\rangle" 197 | return latex_str 198 | 199 | 200 | def do_latex_subs(graph, x, time=None): 201 | """ 202 | This method substitutes 203 | 204 | sp.Symbol("sigma_eps_" + str(i)) 205 | sp.Symbol("sigma_" + str(i)) 206 | sp.Symbol("alpha_" + str(row) + "_L_" + str(col)) 207 | sp.Symbol("beta_" + str(row) + "_L_" + str(col)) 208 | sp.Symbol("cov_" + str(row) + "_" + str(col)) 209 | sp.Symbol("cov_one_" + str(row) + "_" + str(col)) 210 | sp.Symbol("cov_n_" + str(row) + "_" + str(col)) 211 | sp.Symbol("cov_n_plus_one_" + str(row) + "_" + str(col)) 212 | sp.Symbol("cov_n" + str(time) + "_" + str(row) + "_" + str(col)) 213 | sp.Symbol("cov2times_n" + str(row) + "_" + str(col)) 214 | sp.Symbol("cov2times_n" + str(time) + str(row) + "_" + str(col)) 215 | sp.Symbol("d_cov2times_n" + str(row) + "_" + str(col)) 216 | sp.Symbol("d_cov2times_n" + str(time) + str(row) + "_" + str(col)) 217 | sp.Symbol("ee_" + str(row) + "_" + str(col)) 218 | sp.Symbol("rho_" + str(row) + "_" + str(col)) 219 | sp.Symbol("pder_" + str(row) + "_wrt_" + str(col)) 220 | sp.Symbol("G_" + str(row) + "_" + str(col)) 221 | sp.Symbol("err_" + str(row) + "_" + str(col)) 222 | 223 | by their latex counterparts. str(i), str(row) and str(col) are all 224 | replaced by a node name from the list graph.ord_nodes 225 | 226 | Parameters 227 | ---------- 228 | graph: Graph 229 | x: sp.Symbol or sp.Matrix or sp.Eq 230 | time: None or str or int 231 | 232 | Returns 233 | ------- 234 | type(x) 235 | 236 | """ 237 | num_nds = graph.num_nds 238 | for i in range(num_nds): 239 | nd = graph.ord_nodes[i] 240 | 241 | latex_str = r"\sigma_{\underline{\epsilon}" + \ 242 | r"_{\underline{" + nd + r"}}}" 243 | sb_str = "sigma_eps_" + str(i) 244 | x = x.subs(sp.Symbol(sb_str), sp.Symbol(latex_str)) 245 | 246 | latex_str = r"\sigma_{\underline{" + nd + r"}}" 247 | sb_str = "sigma_nd_" + str(i) 248 | x = x.subs(sp.Symbol(sb_str), sp.Symbol(latex_str)) 249 | 250 | for row, col in product(range(num_nds), range(num_nds)): 251 | row_nd = graph.ord_nodes[row] 252 | col_nd = graph.ord_nodes[col] 253 | 254 | latex_str = r"\alpha_{\underline{" + row_nd + \ 255 | r"}|\underline{" + col_nd + r"}}" 256 | sb_str = "alpha_" + str(row) + "_L_" + str(col) 257 | x = x.subs(sp.Symbol(sb_str), sp.Symbol(latex_str)) 258 | 259 | latex_str = r"\beta_{\underline{" + row_nd + \ 260 | r"}|\underline{" + col_nd + r"}}" 261 | sb_str = "beta_" + str(row) + "_L_" + str(col) 262 | x = x.subs(sp.Symbol(sb_str), sp.Symbol(latex_str)) 263 | 264 | for time0 in [None, "one", "n", "n_plus_one"]: 265 | latex_str = latex_cov_str(row_nd, col_nd, time0) 266 | sb_str = sb_cov_str(row, col, time0) 267 | x = x.subs(sp.Symbol(sb_str), sp.Symbol(latex_str)) 268 | 269 | for delta in [True, False]: 270 | latex_str = latex_cov2times_str(row_nd, col_nd, time="n", 271 | delta=delta) 272 | sb_str = sb_cov2times_str(row, col, time="n", 273 | delta=delta) 274 | x = x.subs(sp.Symbol(sb_str), sp.Symbol(latex_str)) 275 | 276 | # print("vvbgh", time) 277 | if isinstance(time, int): 278 | latex_str = latex_cov_str(row_nd, col_nd, time) 279 | sb_str = sb_cov_str(row, col, time) 280 | # print("vvbg", sb_str, latex_str) 281 | x = x.subs(sp.Symbol(sb_str), sp.Symbol(latex_str)) 282 | 283 | latex_str = latex_cov2times_str(row_nd, col_nd, time) 284 | sb_str = sb_cov2times_str(row, col, time) 285 | x = x.subs(sp.Symbol(sb_str), sp.Symbol(latex_str)) 286 | 287 | if row_nd == col_nd: 288 | latex_str = r"\sigma^2_{\underline{\epsilon}" + \ 289 | r"_{\underline{" + row_nd + r"}}}" 290 | else: 291 | latex_str = r"\left\langle\underline{\epsilon}_\underline{" + \ 292 | row_nd + r"},\underline{\epsilon}_\underline{" + \ 293 | col_nd + r"}\right\rangle" 294 | sb_str = "ee_" + str(row) + "_" + str(col) 295 | x = x.subs(sp.Symbol(sb_str), sp.Symbol(latex_str)) 296 | 297 | latex_str = r"\rho_{\underline{" + row_nd + \ 298 | r"},\underline{" + col_nd + r"}}" 299 | sb_str = "rho_" + str(row) + "_" + str(col) 300 | x = x.subs(sp.Symbol(sb_str), sp.Symbol(latex_str)) 301 | 302 | latex_str = r"\frac{\partial\underline{" + row_nd + \ 303 | r"}}{\partial\underline{" + col_nd + r"}}" 304 | sb_str = "pder_" + str(row) + "_wrt_" + str(col) 305 | x = x.subs(sp.Symbol(sb_str), sp.Symbol(latex_str)) 306 | 307 | latex_str = r"G_{\underline{" + row_nd + \ 308 | r"},\underline{" + col_nd + r"}}" 309 | sb_str = "G_" + str(row) + "_" + str(col) 310 | x = x.subs(sp.Symbol(sb_str), sp.Symbol(latex_str)) 311 | 312 | latex_str = r"err_{\underline{" + row_nd + \ 313 | r"},\underline{" + col_nd + r"}}" 314 | sb_str = "err_" + str(row) + "_" + str(col) 315 | x = x.subs(sp.Symbol(sb_str), sp.Symbol(latex_str)) 316 | 317 | return x 318 | 319 | 320 | def print_all_core_mats_after_latex_subs(graph): 321 | """ 322 | This method is for debugging 'do_latex_subs()'. It creates the core 323 | matrices built by the functions in file core_matrices.py. Then it passes 324 | those core matrices through 'do_latex_subs()'. Finally, it prints the 325 | changed core matrices. 326 | 327 | Parameters 328 | ---------- 329 | graph: Graph 330 | 331 | Returns 332 | ------- 333 | None 334 | 335 | """ 336 | dim = graph.num_nds 337 | 338 | def sb_mat_print(x, time=None): 339 | x = do_latex_subs(graph, x, time) 340 | print("\n", x) 341 | print(sp.latex(x)) 342 | 343 | sb_mat_print(sigma_eps_sb_mat(dim)) 344 | sb_mat_print(sigma_nd_sb_mat(dim)) 345 | sb_mat_print(alpha_sb_mat(dim)) 346 | sb_mat_print(beta_sb_mat(dim)) 347 | sb_mat_print(cov_sb_mat(dim)) 348 | sb_mat_print(cov_sb_mat(dim, time="one")) 349 | sb_mat_print(cov_sb_mat(dim, time=5)) 350 | sb_mat_print(cov2times_sb_mat(dim)) 351 | sb_mat_print(cov2times_sb_mat(dim, time=5)) 352 | sb_mat_print(cov2times_sb_mat(dim, delta=True)) 353 | sb_mat_print(cov2times_sb_mat(dim, time=5, delta=True)) 354 | sb_mat_print(ee_sb_mat(dim)) 355 | sb_mat_print(rho_sb_mat(dim)) 356 | sb_mat_print(jacobian_sb_mat(dim)) 357 | 358 | 359 | def create_eq_list_from_matrix(mat, mat_name, graph, time): 360 | """ 361 | This method takes as input an sp.Matrix mat and creates a list[sp.Eq] 362 | from it. The latter list can then be printed using latexify.print_list_sb() 363 | 364 | Parameters 365 | ---------- 366 | mat: sp.Matrix 367 | mat_name: str 368 | graph: Graph 369 | time: None or str or int 370 | 371 | Returns 372 | ------- 373 | list[sp.Eq] 374 | 375 | """ 376 | eq_list = [] 377 | dim = graph.num_nds 378 | for row, col in product(range(dim), range(dim)): 379 | if row <= col and mat_name == "alpha": 380 | continue 381 | mat_str = mat_name 382 | sep = "_" 383 | if mat_name in ["alpha", "beta"]: 384 | sep = "_L_" 385 | if mat_name == "pder": 386 | sep = "_wrt_" 387 | if isinstance(time, int): 388 | mat_str += str(time) 389 | mat_str += "_" + str(row) + sep + str(col) 390 | eq_list.append(sp.Eq(sp.Symbol(mat_str), mat[row, col])) 391 | return eq_list 392 | 393 | 394 | def print_matrix_sb(mat, mat_name, graph, verbose=False, time=None): 395 | """ 396 | This method renders in latex, in a jupyter notebook (but not in the 397 | console), the entries, one at a time, of the symbolic matrix 'mat' named 398 | 'mat_name'. Iff verbose=True, it also prints the same thing in ASCII, 399 | in both the console and jupyter notebook. 400 | 401 | Parameters 402 | ---------- 403 | mat: sp.Matrix 404 | mat_name: str 405 | graph: Graph or FBackGraph 406 | verbose: bool 407 | time: None or str or int 408 | 409 | Returns 410 | ------- 411 | sp.Symbol 412 | 413 | """ 414 | eq_list = create_eq_list_from_matrix(mat, mat_name, graph, time) 415 | return print_list_sb(eq_list, graph, verbose=verbose, time=time) 416 | 417 | 418 | def print_list_sb(eq_list, graph, verbose=False, 419 | time=None, comment_list=None, rounded=True, 420 | prefix_str=None): 421 | """ 422 | This method renders in latex, in a jupyter notebook (but not on the 423 | console), a list 'eq_list' of type list[sp.Eq]. Iff verbose=True, 424 | it also prints the same thing in ASCII, in both the console and jupyter 425 | notebook. 426 | 427 | Parameters 428 | ---------- 429 | eq_list: list[sp.Eq] 430 | graph: Graph or FBackGraph 431 | verbose: Bool 432 | time: None or str or int 433 | comment_list: list[str] 434 | This List[str] should be of the same length as eq_list 435 | rounded: bool 436 | This is True iff the numerical parts of the answer are to be rounded. 437 | prefix_str: str 438 | prefix string to start each line printed. 439 | 440 | Returns 441 | ------- 442 | sp.Symbol 443 | 444 | """ 445 | if comment_list is None: 446 | comment_list = [""]*len(eq_list) 447 | assert len(eq_list) == len(comment_list) 448 | if prefix_str is None: 449 | prefix_str = "" 450 | str0 = "" 451 | x = eq_list 452 | x_copy = deepcopy(x) 453 | # print("lllj", type(x)) 454 | if verbose: 455 | for i in range(len(x)): 456 | print(prefix_str + " " + str(x[i]) + "\t" + comment_list[i] + "\n") 457 | str0 += r"\begin{array}{l}" + "\n" 458 | for i in range(len(x)): 459 | if rounded: 460 | x_copy[i] = round_expr(x_copy[i], 6) 461 | x_copy[i] = do_latex_subs(graph, x_copy[i], time) 462 | x_copy[i] = sp.latex(x_copy[i]) 463 | if len(prefix_str) != 0: 464 | str0 += r"\text{" + prefix_str + r" } " 465 | str0 += x_copy[i] + r"\quad" + comment_list[i] + "\n" + r"\\"\ 466 | + "\n" 467 | str0 = str0[:-3] 468 | str0 += r"\end{array}" 469 | # print("lluj", str0) 470 | if verbose: 471 | print("\n", str0) 472 | # this return prints nothing on the console, but, if 473 | # inserted as the last line of a jupyter cell, it renders 474 | # the latex in str0 475 | return sp.Symbol(str0) 476 | 477 | 478 | if __name__ == "__main__": 479 | 480 | def main(): 481 | dot = "digraph G {\n" \ 482 | "a->b;\n" \ 483 | "a->s;\n" \ 484 | "n->s,a,b;\n" \ 485 | "b->s\n" \ 486 | "}" 487 | with open("tempo13.txt", "w") as file: 488 | file.write(dot) 489 | path = 'tempo13.txt' 490 | graph = Graph(path) 491 | print_all_core_mats_after_latex_subs(graph) 492 | 493 | main() 494 | -------------------------------------------------------------------------------- /numerical_subs.py: -------------------------------------------------------------------------------- 1 | from Graph import * 2 | from FBackGraph import * 3 | import sympy as sp 4 | from itertools import product 5 | 6 | """ 7 | 8 | The functions in this file return their input x after performing some 9 | numerical substitutions of some of the symbols in x. x is usually a sp.Matrix. 10 | 11 | """ 12 | 13 | 14 | def set_to_one_sigma_eps_mat(graph, x): 15 | """ 16 | This method sets \sigma_{\epsilon_j} to 0 for all j. 17 | 18 | Parameters 19 | ---------- 20 | graph: Graph 21 | x: sp.Matrix or sp.Symbol 22 | 23 | Returns 24 | ------- 25 | type(x) 26 | 27 | """ 28 | num_nds = graph.num_nds 29 | for i in range(num_nds): 30 | sigma_eps_sb = "sigma_eps_" + str(i) 31 | x = x.subs(sp.Symbol(sigma_eps_sb), 1) 32 | return x 33 | 34 | 35 | def set_to_zero_gains_without_arrows(graph, x): 36 | """ 37 | This method sets inslice gain \alpha_{i|j} to 0 for all i,j such that 38 | there is no inslice arrow x_j->x_i. 39 | 40 | Parameters 41 | ---------- 42 | graph: Graph 43 | x: sp.Matrix or sp.Symbol 44 | 45 | Returns 46 | ------- 47 | type(x) 48 | """ 49 | dim = graph.num_nds 50 | for row, col in product(range(dim), range(dim)): 51 | row_nd = graph.ord_nodes[row] 52 | col_nd = graph.ord_nodes[col] 53 | 54 | if row > col and (col_nd, row_nd) not in graph.arrows: 55 | alpha_sb = "alpha_" + str(row) + "_L_" + str(col) 56 | x = x.subs(sp.Symbol(alpha_sb), 0) 57 | return x 58 | 59 | 60 | def set_to_zero_fback_gains_without_arrows(graph, x): 61 | """ 62 | This method sets feedback gain \beta_{i|j} to 0 for all i,j such that 63 | there is no feedback arrow x_j->x_i. 64 | 65 | Parameters 66 | ---------- 67 | graph: FBackGraph 68 | x: sp.Matrix or sp.Symbol 69 | 70 | Returns 71 | ------- 72 | type(x) 73 | """ 74 | dim = graph.num_nds 75 | for row, col in product(range(dim), range(dim)): 76 | row_nd = graph.ord_nodes[row] 77 | col_nd = graph.ord_nodes[col] 78 | 79 | if (col_nd, row_nd) not in graph.fback_arrows: 80 | beta_sb = "beta_" + str(row) + "_L_" + str(col) 81 | x = x.subs(sp.Symbol(beta_sb), 0) 82 | return x 83 | 84 | 85 | if __name__ == "__main__": 86 | from core_matrices import * 87 | 88 | def main(): 89 | dot = "digraph G {\n" \ 90 | "a->b;\n" \ 91 | "a->s;\n" \ 92 | "n->s,a,b;\n" \ 93 | "}" 94 | with open("tempo13.txt", "w") as file: 95 | file.write(dot) 96 | path = 'tempo13.txt' 97 | graph = Graph(path) 98 | dim = graph.num_nds 99 | 100 | x = sigma_eps_sb_mat(dim) 101 | print("\n", x) 102 | x = set_to_one_sigma_eps_mat(graph, x) 103 | print(x) 104 | 105 | print() 106 | # dot has 5 arrows out of maximum of 6 possible 107 | # for 4 nodes. Hence, one of the gains is zero. 108 | # arrow 2->3 (i.e., "b"->"s") is missing 109 | # so alpha_3_L_2=0 110 | print(graph.ord_nodes) 111 | print(graph.arrows) 112 | 113 | x = alpha_sb_mat(dim) 114 | print("\n", x) 115 | 116 | x = set_to_zero_gains_without_arrows(graph, x) 117 | print(x) 118 | 119 | main() 120 | -------------------------------------------------------------------------------- /pond-scum.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrtucci/scumpy/90538d79a31974da3faae58c69dd4e90ef555e65/pond-scum.jpg -------------------------------------------------------------------------------- /run_all_nb.py: -------------------------------------------------------------------------------- 1 | import nbformat 2 | from nbconvert.preprocessors import ExecutePreprocessor 3 | import os 4 | 5 | ''' 6 | 7 | This script tries to run all jupyter notebooks in the jupyter_notebooks 8 | folder. The notebooks are executed but not saved (i.e., overwritten). 9 | 10 | ''' 11 | 12 | dir_name = 'jupyter_notebooks' 13 | for fname in os.listdir(dir_name): 14 | if fname[-6:] == '.ipynb': # and fname[-12:] == 'native.ipynb': 15 | print("------------", fname) 16 | try: 17 | # open() fails when reading markdown Chinese characters 18 | # and also some types of quotation marks 19 | nb = nbformat.read(dir_name + '/' + fname, 20 | as_version=4) 21 | ep = ExecutePreprocessor() 22 | ep.preprocess(nb, {'metadata': {'path': dir_name + "/"}}) 23 | except: 24 | print('error in ', fname) 25 | # #this raise will stop execution on first error 26 | # raise 27 | # finally: 28 | # nbformat.write(nb, "ERROR_" + fname)S -------------------------------------------------------------------------------- /run_all_py.py: -------------------------------------------------------------------------------- 1 | import os 2 | from subprocess import Popen, PIPE, call 3 | 4 | ''' 5 | 6 | This script tries to run all files, in all folders, that end in .py and have 7 | a main() at the end. 8 | 9 | ''' 10 | 11 | 12 | dir_whitelist = [ 13 | "./" 14 | ] 15 | file_blacklist = [ 16 | '__init__.py', 17 | 'run_all_nb.py', 18 | 'run_all_py.py', 19 | 'classgraph.py' 20 | ] 21 | for dir_name in dir_whitelist: 22 | for fname in os.listdir(dir_name): 23 | if fname[-3:] == '.py' and fname not in file_blacklist: 24 | path = dir_name + '/' + fname 25 | print('--------------------', path) 26 | pro = Popen(['python', fname], cwd=dir_name, 27 | stdout=PIPE, stderr=PIPE) 28 | stdout, stderr = pro.communicate() 29 | print(str(stderr)) 30 | --------------------------------------------------------------------------------