@@ -228,3 +228,123 @@ def test_is_collider():
228228 S = {"A" }
229229
230230 assert pywhy_graphs .inducing_path (admg , "Z" , "Y" , L , S )[0 ]
231+
232+
233+ def test_has_adc ():
234+ # K -> H -> Z -> X -> Y -> J <- K
235+ admg = ADMG ()
236+ admg .add_edge ("Z" , "X" , admg .directed_edge_name )
237+ admg .add_edge ("X" , "Y" , admg .directed_edge_name )
238+ admg .add_edge ("Y" , "J" , admg .directed_edge_name )
239+ admg .add_edge ("H" , "Z" , admg .directed_edge_name )
240+ admg .add_edge ("K" , "H" , admg .directed_edge_name )
241+ admg .add_edge ("K" , "J" , admg .directed_edge_name )
242+
243+ assert not pywhy_graphs .has_adc (admg ) # there is no cycle completed by a bidirected edge
244+
245+ # K -> H -> Z -> X -> Y -> J <-> K
246+ admg = ADMG ()
247+ admg .add_edge ("Z" , "X" , admg .directed_edge_name )
248+ admg .add_edge ("X" , "Y" , admg .directed_edge_name )
249+ admg .add_edge ("Y" , "J" , admg .directed_edge_name )
250+ admg .add_edge ("H" , "Z" , admg .directed_edge_name )
251+ admg .add_edge ("K" , "H" , admg .directed_edge_name )
252+ admg .add_edge ("Y" , "J" , admg .directed_edge_name )
253+ admg .add_edge ("K" , "J" , admg .bidirected_edge_name )
254+
255+ assert pywhy_graphs .has_adc (admg ) # there is a bidirected edge from J to K, completing a cycle
256+
257+ # K -> H -> Z -> X -> Y <- J <-> K
258+ admg = ADMG ()
259+ admg .add_edge ("Z" , "X" , admg .directed_edge_name )
260+ admg .add_edge ("X" , "Y" , admg .directed_edge_name )
261+ admg .add_edge ("J" , "Y" , admg .directed_edge_name )
262+ admg .add_edge ("H" , "Z" , admg .directed_edge_name )
263+ admg .add_edge ("K" , "H" , admg .directed_edge_name )
264+ admg .add_edge ("K" , "J" , admg .bidirected_edge_name )
265+
266+ assert not pywhy_graphs .has_adc (admg ) # Y <- J is not correctly oriented
267+
268+ # I -> H -> Z -> X -> Y -> J <-> K
269+ # J -> I
270+ admg = ADMG ()
271+ admg .add_edge ("Z" , "X" , admg .directed_edge_name )
272+ admg .add_edge ("X" , "Y" , admg .directed_edge_name )
273+ admg .add_edge ("Y" , "J" , admg .directed_edge_name )
274+ admg .add_edge ("H" , "Z" , admg .directed_edge_name )
275+ admg .add_edge ("K" , "H" , admg .directed_edge_name )
276+ admg .add_edge ("Y" , "H" , admg .directed_edge_name )
277+ admg .add_edge ("K" , "J" , admg .bidirected_edge_name )
278+
279+ assert pywhy_graphs .has_adc (admg ) # J <-> K completes an otherwise directed cycle
280+
281+
282+ def test_valid_mag ():
283+ # K -> H -> Z -> X -> Y -> J <- K
284+ admg = ADMG ()
285+ admg .add_edge ("Z" , "X" , admg .directed_edge_name )
286+ admg .add_edge ("X" , "Y" , admg .directed_edge_name )
287+ admg .add_edge ("Y" , "J" , admg .directed_edge_name )
288+ admg .add_edge ("H" , "Z" , admg .directed_edge_name )
289+ admg .add_edge ("K" , "H" , admg .directed_edge_name )
290+ admg .add_edge ("K" , "J" , admg .directed_edge_name )
291+
292+ S = {"J" }
293+ L = {}
294+
295+ assert not pywhy_graphs .valid_mag (
296+ admg , L , S # J is in S and is a collider on the path Y -> J <- K
297+ )
298+
299+ S = {}
300+
301+ assert pywhy_graphs .valid_mag (admg , L , S ) # there are no valid inducing paths
302+
303+ # K -> H -> Z -> X -> Y -> J -> K
304+ admg = ADMG ()
305+ admg .add_edge ("Z" , "X" , admg .directed_edge_name )
306+ admg .add_edge ("X" , "Y" , admg .directed_edge_name )
307+ admg .add_edge ("Y" , "J" , admg .directed_edge_name )
308+ admg .add_edge ("H" , "Z" , admg .directed_edge_name )
309+ admg .add_edge ("K" , "H" , admg .directed_edge_name )
310+ admg .add_edge ("J" , "K" , admg .directed_edge_name )
311+
312+ L = {}
313+
314+ assert not pywhy_graphs .valid_mag (admg , L , S ) # there is a directed cycle
315+
316+ # K -> H -> Z -> X -> Y -> J <- K
317+ # H <-> J
318+ admg = ADMG ()
319+ admg .add_edge ("Z" , "X" , admg .directed_edge_name )
320+ admg .add_edge ("X" , "Y" , admg .directed_edge_name )
321+ admg .add_edge ("Y" , "J" , admg .directed_edge_name )
322+ admg .add_edge ("H" , "Z" , admg .directed_edge_name )
323+ admg .add_edge ("K" , "H" , admg .directed_edge_name )
324+ admg .add_edge ("K" , "J" , admg .directed_edge_name )
325+ admg .add_edge ("H" , "J" , admg .bidirected_edge_name )
326+
327+ assert not pywhy_graphs .valid_mag (admg ) # there is an almost directed cycle
328+
329+ admg = ADMG ()
330+ admg .add_edge ("Z" , "X" , admg .directed_edge_name )
331+ admg .add_edge ("X" , "Y" , admg .directed_edge_name )
332+ admg .add_edge ("Y" , "J" , admg .directed_edge_name )
333+ admg .add_edge ("H" , "Z" , admg .directed_edge_name )
334+ admg .add_edge ("K" , "H" , admg .directed_edge_name )
335+ admg .add_edge ("K" , "J" , admg .directed_edge_name )
336+ admg .add_edge ("H" , "J" , admg .bidirected_edge_name )
337+ admg .add_edge ("H" , "J" , admg .directed_edge_name )
338+
339+ assert not pywhy_graphs .valid_mag (admg ) # there are two edges between H and J
340+
341+ admg = ADMG ()
342+ admg .add_edge ("Z" , "X" , admg .directed_edge_name )
343+ admg .add_edge ("X" , "Y" , admg .directed_edge_name )
344+ admg .add_edge ("Y" , "J" , admg .directed_edge_name )
345+ admg .add_edge ("H" , "Z" , admg .directed_edge_name )
346+ admg .add_edge ("K" , "H" , admg .directed_edge_name )
347+ admg .add_edge ("K" , "J" , admg .directed_edge_name )
348+ admg .add_edge ("H" , "J" , admg .undirected_edge_name )
349+
350+ assert not pywhy_graphs .valid_mag (admg ) # there is an undirected edge between H and J
0 commit comments