@@ -73,6 +73,10 @@ def setUp(self) -> None:
73
73
dag_dot = """digraph DAG { rankdir=LR; X1 -> Z; Z -> M; M -> Y; X2 -> Z; X3 -> M;}"""
74
74
with open (self .dag_dot_path , "w" ) as f :
75
75
f .write (dag_dot )
76
+ self .dcg_dot_path = os .path .join (self .temp_dir_path , "dcg.dot" )
77
+ dcg_dot = """digraph dct { a -> b -> c -> d; d -> c; }"""
78
+ with open (self .dcg_dot_path , "w" ) as f :
79
+ f .write (dcg_dot )
76
80
77
81
X1 = Input ("X1" , float )
78
82
X2 = Input ("X2" , float )
@@ -248,6 +252,71 @@ def test_all_metamorphic_relations_implied_by_dag(self):
248
252
self .assertEqual (extra_snc_relations , [])
249
253
self .assertEqual (missing_snc_relations , [])
250
254
255
+ def test_all_metamorphic_relations_implied_by_dag_parallel (self ):
256
+ dag = CausalDAG (self .dag_dot_path )
257
+ dag .add_edge ("Z" , "Y" ) # Add a direct path from Z to Y so M becomes a mediator
258
+ metamorphic_relations = generate_metamorphic_relations (dag , threads = 2 )
259
+ should_cause_relations = [mr for mr in metamorphic_relations if isinstance (mr , ShouldCause )]
260
+ should_not_cause_relations = [mr for mr in metamorphic_relations if isinstance (mr , ShouldNotCause )]
261
+
262
+ # Check all ShouldCause relations are present and no extra
263
+ expected_should_cause_relations = [
264
+ ShouldCause ("X1" , "Z" , [], dag ),
265
+ ShouldCause ("Z" , "M" , [], dag ),
266
+ ShouldCause ("M" , "Y" , ["Z" ], dag ),
267
+ ShouldCause ("Z" , "Y" , ["M" ], dag ),
268
+ ShouldCause ("X2" , "Z" , [], dag ),
269
+ ShouldCause ("X3" , "M" , [], dag ),
270
+ ]
271
+
272
+ extra_sc_relations = [scr for scr in should_cause_relations if scr not in expected_should_cause_relations ]
273
+ missing_sc_relations = [escr for escr in expected_should_cause_relations if escr not in should_cause_relations ]
274
+
275
+ self .assertEqual (extra_sc_relations , [])
276
+ self .assertEqual (missing_sc_relations , [])
277
+
278
+ # Check all ShouldNotCause relations are present and no extra
279
+ expected_should_not_cause_relations = [
280
+ ShouldNotCause ("X1" , "X2" , [], dag ),
281
+ ShouldNotCause ("X1" , "X3" , [], dag ),
282
+ ShouldNotCause ("X1" , "M" , ["Z" ], dag ),
283
+ ShouldNotCause ("X1" , "Y" , ["Z" ], dag ),
284
+ ShouldNotCause ("X2" , "X3" , [], dag ),
285
+ ShouldNotCause ("X2" , "M" , ["Z" ], dag ),
286
+ ShouldNotCause ("X2" , "Y" , ["Z" ], dag ),
287
+ ShouldNotCause ("X3" , "Y" , ["M" , "Z" ], dag ),
288
+ ShouldNotCause ("Z" , "X3" , [], dag ),
289
+ ]
290
+
291
+ extra_snc_relations = [
292
+ sncr for sncr in should_not_cause_relations if sncr not in expected_should_not_cause_relations
293
+ ]
294
+ missing_snc_relations = [
295
+ esncr for esncr in expected_should_not_cause_relations if esncr not in should_not_cause_relations
296
+ ]
297
+
298
+ self .assertEqual (extra_snc_relations , [])
299
+ self .assertEqual (missing_snc_relations , [])
300
+
301
+ def test_all_metamorphic_relations_implied_by_dag_ignore_cycles (self ):
302
+ dag = CausalDAG (self .dcg_dot_path , ignore_cycles = True )
303
+ metamorphic_relations = generate_metamorphic_relations (dag , threads = 2 , nodes_to_ignore = set (dag .cycle_nodes ()))
304
+ should_cause_relations = [mr for mr in metamorphic_relations if isinstance (mr , ShouldCause )]
305
+ should_not_cause_relations = [mr for mr in metamorphic_relations if isinstance (mr , ShouldNotCause )]
306
+
307
+ # Check all ShouldCause relations are present and no extra
308
+
309
+ self .assertEqual (
310
+ should_cause_relations ,
311
+ [
312
+ ShouldCause ("a" , "b" , [], dag ),
313
+ ],
314
+ )
315
+ self .assertEqual (
316
+ should_not_cause_relations ,
317
+ [],
318
+ )
319
+
251
320
def test_equivalent_metamorphic_relations (self ):
252
321
dag = CausalDAG (self .dag_dot_path )
253
322
sc_mr_a = ShouldCause ("X" , "Y" , ["A" , "B" , "C" ], dag )
0 commit comments