aboutsummaryrefslogtreecommitdiff
path: root/.venv/lib/python3.12/site-packages/networkx/algorithms/d_separation.py
diff options
context:
space:
mode:
authorS. Solomon Darnell2025-03-28 21:52:21 -0500
committerS. Solomon Darnell2025-03-28 21:52:21 -0500
commit4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch)
treeee3dc5af3b6313e921cd920906356f5d4febc4ed /.venv/lib/python3.12/site-packages/networkx/algorithms/d_separation.py
parentcc961e04ba734dd72309fb548a2f97d67d578813 (diff)
downloadgn-ai-master.tar.gz
two version of R2R are hereHEADmaster
Diffstat (limited to '.venv/lib/python3.12/site-packages/networkx/algorithms/d_separation.py')
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/d_separation.py722
1 files changed, 722 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/d_separation.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/d_separation.py
new file mode 100644
index 00000000..a688eca4
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/d_separation.py
@@ -0,0 +1,722 @@
+"""
+Algorithm for testing d-separation in DAGs.
+
+*d-separation* is a test for conditional independence in probability
+distributions that can be factorized using DAGs. It is a purely
+graphical test that uses the underlying graph and makes no reference
+to the actual distribution parameters. See [1]_ for a formal
+definition.
+
+The implementation is based on the conceptually simple linear time
+algorithm presented in [2]_. Refer to [3]_, [4]_ for a couple of
+alternative algorithms.
+
+The functional interface in NetworkX consists of three functions:
+
+- `find_minimal_d_separator` returns a minimal d-separator set ``z``.
+ That is, removing any node or nodes from it makes it no longer a d-separator.
+- `is_d_separator` checks if a given set is a d-separator.
+- `is_minimal_d_separator` checks if a given set is a minimal d-separator.
+
+D-separators
+------------
+
+Here, we provide a brief overview of d-separation and related concepts that
+are relevant for understanding it:
+
+The ideas of d-separation and d-connection relate to paths being open or blocked.
+
+- A "path" is a sequence of nodes connected in order by edges. Unlike for most
+ graph theory analysis, the direction of the edges is ignored. Thus the path
+ can be thought of as a traditional path on the undirected version of the graph.
+- A "candidate d-separator" ``z`` is a set of nodes being considered as
+ possibly blocking all paths between two prescribed sets ``x`` and ``y`` of nodes.
+ We refer to each node in the candidate d-separator as "known".
+- A "collider" node on a path is a node that is a successor of its two neighbor
+ nodes on the path. That is, ``c`` is a collider if the edge directions
+ along the path look like ``... u -> c <- v ...``.
+- If a collider node or any of its descendants are "known", the collider
+ is called an "open collider". Otherwise it is a "blocking collider".
+- Any path can be "blocked" in two ways. If the path contains a "known" node
+ that is not a collider, the path is blocked. Also, if the path contains a
+ collider that is not a "known" node, the path is blocked.
+- A path is "open" if it is not blocked. That is, it is open if every node is
+ either an open collider or not a "known". Said another way, every
+ "known" in the path is a collider and every collider is open (has a
+ "known" as a inclusive descendant). The concept of "open path" is meant to
+ demonstrate a probabilistic conditional dependence between two nodes given
+ prescribed knowledge ("known" nodes).
+- Two sets ``x`` and ``y`` of nodes are "d-separated" by a set of nodes ``z``
+ if all paths between nodes in ``x`` and nodes in ``y`` are blocked. That is,
+ if there are no open paths from any node in ``x`` to any node in ``y``.
+ Such a set ``z`` is a "d-separator" of ``x`` and ``y``.
+- A "minimal d-separator" is a d-separator ``z`` for which no node or subset
+ of nodes can be removed with it still being a d-separator.
+
+The d-separator blocks some paths between ``x`` and ``y`` but opens others.
+Nodes in the d-separator block paths if the nodes are not colliders.
+But if a collider or its descendant nodes are in the d-separation set, the
+colliders are open, allowing a path through that collider.
+
+Illustration of D-separation with examples
+------------------------------------------
+
+A pair of two nodes, ``u`` and ``v``, are d-connected if there is a path
+from ``u`` to ``v`` that is not blocked. That means, there is an open
+path from ``u`` to ``v``.
+
+For example, if the d-separating set is the empty set, then the following paths are
+open between ``u`` and ``v``:
+
+- u <- n -> v
+- u -> w -> ... -> n -> v
+
+If on the other hand, ``n`` is in the d-separating set, then ``n`` blocks
+those paths between ``u`` and ``v``.
+
+Colliders block a path if they and their descendants are not included
+in the d-separating set. An example of a path that is blocked when the
+d-separating set is empty is:
+
+- u -> w -> ... -> n <- v
+
+The node ``n`` is a collider in this path and is not in the d-separating set.
+So ``n`` blocks this path. However, if ``n`` or a descendant of ``n`` is
+included in the d-separating set, then the path through the collider
+at ``n`` (... -> n <- ...) is "open".
+
+D-separation is concerned with blocking all paths between nodes from ``x`` to ``y``.
+A d-separating set between ``x`` and ``y`` is one where all paths are blocked.
+
+D-separation and its applications in probability
+------------------------------------------------
+
+D-separation is commonly used in probabilistic causal-graph models. D-separation
+connects the idea of probabilistic "dependence" with separation in a graph. If
+one assumes the causal Markov condition [5]_, (every node is conditionally
+independent of its non-descendants, given its parents) then d-separation implies
+conditional independence in probability distributions.
+Symmetrically, d-connection implies dependence.
+
+The intuition is as follows. The edges on a causal graph indicate which nodes
+influence the outcome of other nodes directly. An edge from u to v
+implies that the outcome of event ``u`` influences the probabilities for
+the outcome of event ``v``. Certainly knowing ``u`` changes predictions for ``v``.
+But also knowing ``v`` changes predictions for ``u``. The outcomes are dependent.
+Furthermore, an edge from ``v`` to ``w`` would mean that ``w`` and ``v`` are dependent
+and thus that ``u`` could indirectly influence ``w``.
+
+Without any knowledge about the system (candidate d-separating set is empty)
+a causal graph ``u -> v -> w`` allows all three nodes to be dependent. But
+if we know the outcome of ``v``, the conditional probabilities of outcomes for
+``u`` and ``w`` are independent of each other. That is, once we know the outcome
+for ```v`, the probabilities for ``w`` do not depend on the outcome for ``u``.
+This is the idea behind ``v`` blocking the path if it is "known" (in the candidate
+d-separating set).
+
+The same argument works whether the direction of the edges are both
+left-going and when both arrows head out from the middle. Having a "known"
+node on a path blocks the collider-free path because those relationships
+make the conditional probabilities independent.
+
+The direction of the causal edges does impact dependence precisely in the
+case of a collider e.g. ``u -> v <- w``. In that situation, both ``u`` and ``w``
+influence ``v```. But they do not directly influence each other. So without any
+knowledge of any outcomes, ``u`` and ``w`` are independent. That is the idea behind
+colliders blocking the path. But, if ``v`` is known, the conditional probabilities
+of ``u`` and ``w`` can be dependent. This is the heart of Berkson's Paradox [6]_.
+For example, suppose ``u`` and ``w`` are boolean events (they either happen or do not)
+and ``v`` represents the outcome "at least one of ``u`` and ``w`` occur". Then knowing
+``v`` is true makes the conditional probabilities of ``u`` and ``w`` dependent.
+Essentially, knowing that at least one of them is true raises the probability of
+each. But further knowledge that ``w`` is true (or false) change the conditional
+probability of ``u`` to either the original value or 1. So the conditional
+probability of ``u`` depends on the outcome of ``w`` even though there is no
+causal relationship between them. When a collider is known, dependence can
+occur across paths through that collider. This is the reason open colliders
+do not block paths.
+
+Furthermore, even if ``v`` is not "known", if one of its descendants is "known"
+we can use that information to know more about ``v`` which again makes
+``u`` and ``w`` potentially dependent. Suppose the chance of ``n`` occurring
+is much higher when ``v`` occurs ("at least one of ``u`` and ``w`` occur").
+Then if we know ``n`` occurred, it is more likely that ``v`` occurred and that
+makes the chance of ``u`` and ``w`` dependent. This is the idea behind why
+a collider does no block a path if any descendant of the collider is "known".
+
+When two sets of nodes ``x`` and ``y`` are d-separated by a set ``z``,
+it means that given the outcomes of the nodes in ``z``, the probabilities
+of outcomes of the nodes in ``x`` are independent of the outcomes of the
+nodes in ``y`` and vice versa.
+
+Examples
+--------
+A Hidden Markov Model with 5 observed states and 5 hidden states
+where the hidden states have causal relationships resulting in
+a path results in the following causal network. We check that
+early states along the path are separated from late state in
+the path by the d-separator of the middle hidden state.
+Thus if we condition on the middle hidden state, the early
+state probabilities are independent of the late state outcomes.
+
+>>> G = nx.DiGraph()
+>>> G.add_edges_from(
+... [
+... ("H1", "H2"),
+... ("H2", "H3"),
+... ("H3", "H4"),
+... ("H4", "H5"),
+... ("H1", "O1"),
+... ("H2", "O2"),
+... ("H3", "O3"),
+... ("H4", "O4"),
+... ("H5", "O5"),
+... ]
+... )
+>>> x, y, z = ({"H1", "O1"}, {"H5", "O5"}, {"H3"})
+>>> nx.is_d_separator(G, x, y, z)
+True
+>>> nx.is_minimal_d_separator(G, x, y, z)
+True
+>>> nx.is_minimal_d_separator(G, x, y, z | {"O3"})
+False
+>>> z = nx.find_minimal_d_separator(G, x | y, {"O2", "O3", "O4"})
+>>> z == {"H2", "H4"}
+True
+
+If no minimal_d_separator exists, `None` is returned
+
+>>> other_z = nx.find_minimal_d_separator(G, x | y, {"H2", "H3"})
+>>> other_z is None
+True
+
+
+References
+----------
+
+.. [1] Pearl, J. (2009). Causality. Cambridge: Cambridge University Press.
+
+.. [2] Darwiche, A. (2009). Modeling and reasoning with Bayesian networks.
+ Cambridge: Cambridge University Press.
+
+.. [3] Shachter, Ross D. "Bayes-ball: The rational pastime (for
+ determining irrelevance and requisite information in belief networks
+ and influence diagrams)." In Proceedings of the Fourteenth Conference
+ on Uncertainty in Artificial Intelligence (UAI), (pp. 480–487). 1998.
+
+.. [4] Koller, D., & Friedman, N. (2009).
+ Probabilistic graphical models: principles and techniques. The MIT Press.
+
+.. [5] https://en.wikipedia.org/wiki/Causal_Markov_condition
+
+.. [6] https://en.wikipedia.org/wiki/Berkson%27s_paradox
+
+"""
+
+from collections import deque
+from itertools import chain
+
+import networkx as nx
+from networkx.utils import UnionFind, not_implemented_for
+
+__all__ = [
+ "is_d_separator",
+ "is_minimal_d_separator",
+ "find_minimal_d_separator",
+ "d_separated",
+ "minimal_d_separator",
+]
+
+
+@not_implemented_for("undirected")
+@nx._dispatchable
+def is_d_separator(G, x, y, z):
+ """Return whether node sets `x` and `y` are d-separated by `z`.
+
+ Parameters
+ ----------
+ G : nx.DiGraph
+ A NetworkX DAG.
+
+ x : node or set of nodes
+ First node or set of nodes in `G`.
+
+ y : node or set of nodes
+ Second node or set of nodes in `G`.
+
+ z : node or set of nodes
+ Potential separator (set of conditioning nodes in `G`). Can be empty set.
+
+ Returns
+ -------
+ b : bool
+ A boolean that is true if `x` is d-separated from `y` given `z` in `G`.
+
+ Raises
+ ------
+ NetworkXError
+ The *d-separation* test is commonly used on disjoint sets of
+ nodes in acyclic directed graphs. Accordingly, the algorithm
+ raises a :exc:`NetworkXError` if the node sets are not
+ disjoint or if the input graph is not a DAG.
+
+ NodeNotFound
+ If any of the input nodes are not found in the graph,
+ a :exc:`NodeNotFound` exception is raised
+
+ Notes
+ -----
+ A d-separating set in a DAG is a set of nodes that
+ blocks all paths between the two sets. Nodes in `z`
+ block a path if they are part of the path and are not a collider,
+ or a descendant of a collider. Also colliders that are not in `z`
+ block a path. A collider structure along a path
+ is ``... -> c <- ...`` where ``c`` is the collider node.
+
+ https://en.wikipedia.org/wiki/Bayesian_network#d-separation
+ """
+ try:
+ x = {x} if x in G else x
+ y = {y} if y in G else y
+ z = {z} if z in G else z
+
+ intersection = x & y or x & z or y & z
+ if intersection:
+ raise nx.NetworkXError(
+ f"The sets are not disjoint, with intersection {intersection}"
+ )
+
+ set_v = x | y | z
+ if set_v - G.nodes:
+ raise nx.NodeNotFound(f"The node(s) {set_v - G.nodes} are not found in G")
+ except TypeError:
+ raise nx.NodeNotFound("One of x, y, or z is not a node or a set of nodes in G")
+
+ if not nx.is_directed_acyclic_graph(G):
+ raise nx.NetworkXError("graph should be directed acyclic")
+
+ # contains -> and <-> edges from starting node T
+ forward_deque = deque([])
+ forward_visited = set()
+
+ # contains <- and - edges from starting node T
+ backward_deque = deque(x)
+ backward_visited = set()
+
+ ancestors_or_z = set().union(*[nx.ancestors(G, node) for node in x]) | z | x
+
+ while forward_deque or backward_deque:
+ if backward_deque:
+ node = backward_deque.popleft()
+ backward_visited.add(node)
+ if node in y:
+ return False
+ if node in z:
+ continue
+
+ # add <- edges to backward deque
+ backward_deque.extend(G.pred[node].keys() - backward_visited)
+ # add -> edges to forward deque
+ forward_deque.extend(G.succ[node].keys() - forward_visited)
+
+ if forward_deque:
+ node = forward_deque.popleft()
+ forward_visited.add(node)
+ if node in y:
+ return False
+
+ # Consider if -> node <- is opened due to ancestor of node in z
+ if node in ancestors_or_z:
+ # add <- edges to backward deque
+ backward_deque.extend(G.pred[node].keys() - backward_visited)
+ if node not in z:
+ # add -> edges to forward deque
+ forward_deque.extend(G.succ[node].keys() - forward_visited)
+
+ return True
+
+
+@not_implemented_for("undirected")
+@nx._dispatchable
+def find_minimal_d_separator(G, x, y, *, included=None, restricted=None):
+ """Returns a minimal d-separating set between `x` and `y` if possible
+
+ A d-separating set in a DAG is a set of nodes that blocks all
+ paths between the two sets of nodes, `x` and `y`. This function
+ constructs a d-separating set that is "minimal", meaning no nodes can
+ be removed without it losing the d-separating property for `x` and `y`.
+ If no d-separating sets exist for `x` and `y`, this returns `None`.
+
+ In a DAG there may be more than one minimal d-separator between two
+ sets of nodes. Minimal d-separators are not always unique. This function
+ returns one minimal d-separator, or `None` if no d-separator exists.
+
+ Uses the algorithm presented in [1]_. The complexity of the algorithm
+ is :math:`O(m)`, where :math:`m` stands for the number of edges in
+ the subgraph of G consisting of only the ancestors of `x` and `y`.
+ For full details, see [1]_.
+
+ Parameters
+ ----------
+ G : graph
+ A networkx DAG.
+ x : set | node
+ A node or set of nodes in the graph.
+ y : set | node
+ A node or set of nodes in the graph.
+ included : set | node | None
+ A node or set of nodes which must be included in the found separating set,
+ default is None, which means the empty set.
+ restricted : set | node | None
+ Restricted node or set of nodes to consider. Only these nodes can be in
+ the found separating set, default is None meaning all nodes in ``G``.
+
+ Returns
+ -------
+ z : set | None
+ The minimal d-separating set, if at least one d-separating set exists,
+ otherwise None.
+
+ Raises
+ ------
+ NetworkXError
+ Raises a :exc:`NetworkXError` if the input graph is not a DAG
+ or if node sets `x`, `y`, and `included` are not disjoint.
+
+ NodeNotFound
+ If any of the input nodes are not found in the graph,
+ a :exc:`NodeNotFound` exception is raised.
+
+ References
+ ----------
+ .. [1] van der Zander, Benito, and Maciej Liśkiewicz. "Finding
+ minimal d-separators in linear time and applications." In
+ Uncertainty in Artificial Intelligence, pp. 637-647. PMLR, 2020.
+ """
+ if not nx.is_directed_acyclic_graph(G):
+ raise nx.NetworkXError("graph should be directed acyclic")
+
+ try:
+ x = {x} if x in G else x
+ y = {y} if y in G else y
+
+ if included is None:
+ included = set()
+ elif included in G:
+ included = {included}
+
+ if restricted is None:
+ restricted = set(G)
+ elif restricted in G:
+ restricted = {restricted}
+
+ set_y = x | y | included | restricted
+ if set_y - G.nodes:
+ raise nx.NodeNotFound(f"The node(s) {set_y - G.nodes} are not found in G")
+ except TypeError:
+ raise nx.NodeNotFound(
+ "One of x, y, included or restricted is not a node or set of nodes in G"
+ )
+
+ if not included <= restricted:
+ raise nx.NetworkXError(
+ f"Included nodes {included} must be in restricted nodes {restricted}"
+ )
+
+ intersection = x & y or x & included or y & included
+ if intersection:
+ raise nx.NetworkXError(
+ f"The sets x, y, included are not disjoint. Overlap: {intersection}"
+ )
+
+ nodeset = x | y | included
+ ancestors_x_y_included = nodeset.union(*[nx.ancestors(G, node) for node in nodeset])
+
+ z_init = restricted & (ancestors_x_y_included - (x | y))
+
+ x_closure = _reachable(G, x, ancestors_x_y_included, z_init)
+ if x_closure & y:
+ return None
+
+ z_updated = z_init & (x_closure | included)
+ y_closure = _reachable(G, y, ancestors_x_y_included, z_updated)
+ return z_updated & (y_closure | included)
+
+
+@not_implemented_for("undirected")
+@nx._dispatchable
+def is_minimal_d_separator(G, x, y, z, *, included=None, restricted=None):
+ """Determine if `z` is a minimal d-separator for `x` and `y`.
+
+ A d-separator, `z`, in a DAG is a set of nodes that blocks
+ all paths from nodes in set `x` to nodes in set `y`.
+ A minimal d-separator is a d-separator `z` such that removing
+ any subset of nodes makes it no longer a d-separator.
+
+ Note: This function checks whether `z` is a d-separator AND is
+ minimal. One can use the function `is_d_separator` to only check if
+ `z` is a d-separator. See examples below.
+
+ Parameters
+ ----------
+ G : nx.DiGraph
+ A NetworkX DAG.
+ x : node | set
+ A node or set of nodes in the graph.
+ y : node | set
+ A node or set of nodes in the graph.
+ z : node | set
+ The node or set of nodes to check if it is a minimal d-separating set.
+ The function :func:`is_d_separator` is called inside this function
+ to verify that `z` is in fact a d-separator.
+ included : set | node | None
+ A node or set of nodes which must be included in the found separating set,
+ default is ``None``, which means the empty set.
+ restricted : set | node | None
+ Restricted node or set of nodes to consider. Only these nodes can be in
+ the found separating set, default is ``None`` meaning all nodes in ``G``.
+
+ Returns
+ -------
+ bool
+ Whether or not the set `z` is a minimal d-separator subject to
+ `restricted` nodes and `included` node constraints.
+
+ Examples
+ --------
+ >>> G = nx.path_graph([0, 1, 2, 3], create_using=nx.DiGraph)
+ >>> G.add_node(4)
+ >>> nx.is_minimal_d_separator(G, 0, 2, {1})
+ True
+ >>> # since {1} is the minimal d-separator, {1, 3, 4} is not minimal
+ >>> nx.is_minimal_d_separator(G, 0, 2, {1, 3, 4})
+ False
+ >>> # alternatively, if we only want to check that {1, 3, 4} is a d-separator
+ >>> nx.is_d_separator(G, 0, 2, {1, 3, 4})
+ True
+
+ Raises
+ ------
+ NetworkXError
+ Raises a :exc:`NetworkXError` if the input graph is not a DAG.
+
+ NodeNotFound
+ If any of the input nodes are not found in the graph,
+ a :exc:`NodeNotFound` exception is raised.
+
+ References
+ ----------
+ .. [1] van der Zander, Benito, and Maciej Liśkiewicz. "Finding
+ minimal d-separators in linear time and applications." In
+ Uncertainty in Artificial Intelligence, pp. 637-647. PMLR, 2020.
+
+ Notes
+ -----
+ This function works on verifying that a set is minimal and
+ d-separating between two nodes. Uses criterion (a), (b), (c) on
+ page 4 of [1]_. a) closure(`x`) and `y` are disjoint. b) `z` contains
+ all nodes from `included` and is contained in the `restricted`
+ nodes and in the union of ancestors of `x`, `y`, and `included`.
+ c) the nodes in `z` not in `included` are contained in both
+ closure(x) and closure(y). The closure of a set is the set of nodes
+ connected to the set by a directed path in G.
+
+ The complexity is :math:`O(m)`, where :math:`m` stands for the
+ number of edges in the subgraph of G consisting of only the
+ ancestors of `x` and `y`.
+
+ For full details, see [1]_.
+ """
+ if not nx.is_directed_acyclic_graph(G):
+ raise nx.NetworkXError("graph should be directed acyclic")
+
+ try:
+ x = {x} if x in G else x
+ y = {y} if y in G else y
+ z = {z} if z in G else z
+
+ if included is None:
+ included = set()
+ elif included in G:
+ included = {included}
+
+ if restricted is None:
+ restricted = set(G)
+ elif restricted in G:
+ restricted = {restricted}
+
+ set_y = x | y | included | restricted
+ if set_y - G.nodes:
+ raise nx.NodeNotFound(f"The node(s) {set_y - G.nodes} are not found in G")
+ except TypeError:
+ raise nx.NodeNotFound(
+ "One of x, y, z, included or restricted is not a node or set of nodes in G"
+ )
+
+ if not included <= z:
+ raise nx.NetworkXError(
+ f"Included nodes {included} must be in proposed separating set z {x}"
+ )
+ if not z <= restricted:
+ raise nx.NetworkXError(
+ f"Separating set {z} must be contained in restricted set {restricted}"
+ )
+
+ intersection = x.intersection(y) or x.intersection(z) or y.intersection(z)
+ if intersection:
+ raise nx.NetworkXError(
+ f"The sets are not disjoint, with intersection {intersection}"
+ )
+
+ nodeset = x | y | included
+ ancestors_x_y_included = nodeset.union(*[nx.ancestors(G, n) for n in nodeset])
+
+ # criterion (a) -- check that z is actually a separator
+ x_closure = _reachable(G, x, ancestors_x_y_included, z)
+ if x_closure & y:
+ return False
+
+ # criterion (b) -- basic constraint; included and restricted already checked above
+ if not (z <= ancestors_x_y_included):
+ return False
+
+ # criterion (c) -- check that z is minimal
+ y_closure = _reachable(G, y, ancestors_x_y_included, z)
+ if not ((z - included) <= (x_closure & y_closure)):
+ return False
+ return True
+
+
+@not_implemented_for("undirected")
+def _reachable(G, x, a, z):
+ """Modified Bayes-Ball algorithm for finding d-connected nodes.
+
+ Find all nodes in `a` that are d-connected to those in `x` by
+ those in `z`. This is an implementation of the function
+ `REACHABLE` in [1]_ (which is itself a modification of the
+ Bayes-Ball algorithm [2]_) when restricted to DAGs.
+
+ Parameters
+ ----------
+ G : nx.DiGraph
+ A NetworkX DAG.
+ x : node | set
+ A node in the DAG, or a set of nodes.
+ a : node | set
+ A (set of) node(s) in the DAG containing the ancestors of `x`.
+ z : node | set
+ The node or set of nodes conditioned on when checking d-connectedness.
+
+ Returns
+ -------
+ w : set
+ The closure of `x` in `a` with respect to d-connectedness
+ given `z`.
+
+ References
+ ----------
+ .. [1] van der Zander, Benito, and Maciej Liśkiewicz. "Finding
+ minimal d-separators in linear time and applications." In
+ Uncertainty in Artificial Intelligence, pp. 637-647. PMLR, 2020.
+
+ .. [2] Shachter, Ross D. "Bayes-ball: The rational pastime
+ (for determining irrelevance and requisite information in
+ belief networks and influence diagrams)." In Proceedings of the
+ Fourteenth Conference on Uncertainty in Artificial Intelligence
+ (UAI), (pp. 480–487). 1998.
+ """
+
+ def _pass(e, v, f, n):
+ """Whether a ball entering node `v` along edge `e` passes to `n` along `f`.
+
+ Boolean function defined on page 6 of [1]_.
+
+ Parameters
+ ----------
+ e : bool
+ Directed edge by which the ball got to node `v`; `True` iff directed into `v`.
+ v : node
+ Node where the ball is.
+ f : bool
+ Directed edge connecting nodes `v` and `n`; `True` iff directed `n`.
+ n : node
+ Checking whether the ball passes to this node.
+
+ Returns
+ -------
+ b : bool
+ Whether the ball passes or not.
+
+ References
+ ----------
+ .. [1] van der Zander, Benito, and Maciej Liśkiewicz. "Finding
+ minimal d-separators in linear time and applications." In
+ Uncertainty in Artificial Intelligence, pp. 637-647. PMLR, 2020.
+ """
+ is_element_of_A = n in a
+ # almost_definite_status = True # always true for DAGs; not so for RCGs
+ collider_if_in_Z = v not in z or (e and not f)
+ return is_element_of_A and collider_if_in_Z # and almost_definite_status
+
+ queue = deque([])
+ for node in x:
+ if bool(G.pred[node]):
+ queue.append((True, node))
+ if bool(G.succ[node]):
+ queue.append((False, node))
+ processed = queue.copy()
+
+ while any(queue):
+ e, v = queue.popleft()
+ preds = ((False, n) for n in G.pred[v])
+ succs = ((True, n) for n in G.succ[v])
+ f_n_pairs = chain(preds, succs)
+ for f, n in f_n_pairs:
+ if (f, n) not in processed and _pass(e, v, f, n):
+ queue.append((f, n))
+ processed.append((f, n))
+
+ return {w for (_, w) in processed}
+
+
+# Deprecated functions:
+def d_separated(G, x, y, z):
+ """Return whether nodes sets ``x`` and ``y`` are d-separated by ``z``.
+
+ .. deprecated:: 3.3
+
+ This function is deprecated and will be removed in NetworkX v3.5.
+ Please use `is_d_separator(G, x, y, z)`.
+
+ """
+ import warnings
+
+ warnings.warn(
+ "d_separated is deprecated and will be removed in NetworkX v3.5."
+ "Please use `is_d_separator(G, x, y, z)`.",
+ category=DeprecationWarning,
+ stacklevel=2,
+ )
+ return nx.is_d_separator(G, x, y, z)
+
+
+def minimal_d_separator(G, u, v):
+ """Returns a minimal_d-separating set between `x` and `y` if possible
+
+ .. deprecated:: 3.3
+
+ minimal_d_separator is deprecated and will be removed in NetworkX v3.5.
+ Please use `find_minimal_d_separator(G, x, y)`.
+
+ """
+ import warnings
+
+ warnings.warn(
+ (
+ "This function is deprecated and will be removed in NetworkX v3.5."
+ "Please use `is_d_separator(G, x, y)`."
+ ),
+ category=DeprecationWarning,
+ stacklevel=2,
+ )
+ return nx.find_minimal_d_separator(G, u, v)