Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin' into forceatlas2-networkx
Browse files Browse the repository at this point in the history
  • Loading branch information
cvanelteren committed Jun 26, 2024
2 parents 638051a + fe5e260 commit f7f2042
Show file tree
Hide file tree
Showing 11 changed files with 515 additions and 197 deletions.
14 changes: 9 additions & 5 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -151,16 +151,20 @@ jobs:
pip install -r requirements/extra.txt
pip install .
pip list
- name: Install packages (windows)
- name: Install Pygraphviz packages (windows)
if: runner.os == 'Windows'
run: |
echo "C:\Program Files\Graphviz\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
python -m pip install --upgrade pip
python -m pip install -r requirements/default.txt -r requirements/test.txt
python -m pip install --global-option=build_ext `
--global-option="-IC:\Program Files\Graphviz\include" `
--global-option="-LC:\Program Files\Graphviz\lib" `
pygraphviz
python -m pip install --config-settings="--global-option=build_ext" `
--config-settings="--global-option=-IC:\Program Files\Graphviz\include" `
--config-settings="--global-option=-LC:\Program Files\Graphviz\lib" `
pygraphviz
- name: Install packages (windows)
if: runner.os == 'Windows'
run: |
echo "C:\Program Files\Graphviz\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
python -m pip install -r requirements/extra.txt
python -m pip install .
python -m pip list
Expand Down
4 changes: 4 additions & 0 deletions doc/developer/deprecations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,7 @@ Version 3.5
* Remove ``total_spanning_tree_weight`` from ``linalg/laplacianmatrix.py``
* Remove ``create`` keyword argument from ``nonisomorphic_trees`` in
``generators/nonisomorphic_trees``.

Version 3.6
~~~~~~~~~~~
* Remove ``compute_v_structures`` from ``algorithms/dag.py``.
2 changes: 2 additions & 0 deletions doc/reference/algorithms/dag.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,5 @@ Directed Acyclic Graphs
dag_longest_path_length
dag_to_branching
compute_v_structures
colliders
v_structures
25 changes: 18 additions & 7 deletions doc/reference/backends.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,29 @@
Backends and Configs
********************

.. note:: Both NetworkX backend and config systems are experimental. They
let you execute an alternative backend implementation instead of NetworkX's
pure Python dictionaries implementation. Things will almost certainly
change and break in future releases!
Backends let you execute an alternative backend implementation instead of NetworkX's
pure Python dictionaries implementation. Configs provide library-level storage
of configuration settings that can also come from environment variables.

.. note:: NetworkX backend and configuration systems are receiving frequent updates
and improvements. The user interface for using backends is generally stable.
In the unlikely case where compatibility-breaking changes are necessary to the
backend or config APIs, the standard `deprecation policy <https://networkx.org/documentation/stable/developer/deprecations.html>`_
of NetworkX may not be followed. This flexibility is intended to allow us to
respond rapidly to user feedback and improve usability, and care will be taken
to avoid unnecessary disruption. Developers of NetworkX backends should regularly
monitor updates to maintain compatibility. Participating in weekly
`NX-dispatch meetings <https://scientific-python.org/calendars/networkx.ics>`_
is an excellent way to stay updated and contribute to the ongoing discussions.

Backends
--------
.. automodule:: networkx.utils.backends

Decorator: _dispatchable
------------------------
.. autodecorator:: _dispatchable
.. autosummary::
:toctree: generated/

_dispatchable

Configs
-------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
n_jobs=-1,
)
clusters = sc.fit(X)
cluster_affinity_matrix = clusters.affinity_matrix_.H
cluster_affinity_matrix = clusters.affinity_matrix_.getH()

pred_labels = clusters.labels_.astype(int)
G = nx.from_scipy_sparse_array(cluster_affinity_matrix)
Expand Down
195 changes: 175 additions & 20 deletions networkx/algorithms/dag.py
Original file line number Diff line number Diff line change
Expand Up @@ -1224,36 +1224,191 @@ def dag_to_branching(G):
@not_implemented_for("undirected")
@nx._dispatchable
def compute_v_structures(G):
"""Iterate through the graph to compute all v-structures.
"""Yields 3-node tuples that represent the v-structures in `G`.
V-structures are triples in the directed graph where
two parent nodes point to the same child and the two parent nodes
are not adjacent.
.. deprecated:: 3.4
`compute_v_structures` actually yields colliders. It will be removed in
version 3.6. Use `nx.dag.v_structures` or `nx.dag.colliders` instead.
Colliders are triples in the directed acyclic graph (DAG) where two parent nodes
point to the same child node. V-structures are colliders where the two parent
nodes are not adjacent. In a causal graph setting, the parents do not directly
depend on each other, but conditioning on the child node provides an association.
Parameters
----------
G : graph
A networkx DiGraph.
A networkx `~networkx.DiGraph`.
Returns
-------
vstructs : iterator of tuples
The v structures within the graph. Each v structure is a 3-tuple with the
parent, collider, and other parent.
Yields
------
A 3-tuple representation of a v-structure
Each v-structure is a 3-tuple with the parent, collider, and other parent.
Raises
------
NetworkXNotImplemented
If `G` is an undirected graph.
Examples
--------
>>> G = nx.DiGraph()
>>> G.add_edges_from([(1, 2), (0, 5), (3, 1), (2, 4), (3, 1), (4, 5), (1, 5)])
>>> sorted(nx.compute_v_structures(G))
[(0, 5, 1), (0, 5, 4), (1, 5, 4)]
>>> G = nx.DiGraph([(1, 2), (0, 4), (3, 1), (2, 4), (0, 5), (4, 5), (1, 5)])
>>> nx.is_directed_acyclic_graph(G)
True
>>> list(nx.compute_v_structures(G))
[(0, 4, 2), (0, 5, 4), (0, 5, 1), (4, 5, 1)]
See Also
--------
v_structures
colliders
Notes
-----
`Wikipedia: Collider in causal graphs <https://en.wikipedia.org/wiki/Collider_(statistics)>`_
This function was written to be used on DAGs, however it works on cyclic graphs
too. Since colliders are referred to in the cyclic causal graph literature
[2]_ we allow cyclic graphs in this function. It is suggested that you test if
your input graph is acyclic as in the example if you want that property.
References
----------
.. [1] `Pearl's PRIMER <https://bayes.cs.ucla.edu/PRIMER/primer-ch2.pdf>`_
Ch-2 page 50: v-structures def.
.. [2] A Hyttinen, P.O. Hoyer, F. Eberhardt, M J ̈arvisalo, (2013)
"Discovering cyclic causal models with latent variables:
a general SAT-based procedure", UAI'13: Proceedings of the Twenty-Ninth
Conference on Uncertainty in Artificial Intelligence, pg 301–310,
`doi:10.5555/3023638.3023669 <https://dl.acm.org/doi/10.5555/3023638.3023669>`_
"""
import warnings

warnings.warn(
(
"\n\n`compute_v_structures` actually yields colliders. It will be\n"
"removed in version 3.6. Use `nx.dag.v_structures` or `nx.dag.colliders`\n"
"instead.\n"
),
category=DeprecationWarning,
stacklevel=5,
)

return colliders(G)


@not_implemented_for("undirected")
@nx._dispatchable
def v_structures(G):
"""Yields 3-node tuples that represent the v-structures in `G`.
Colliders are triples in the directed acyclic graph (DAG) where two parent nodes
point to the same child node. V-structures are colliders where the two parent
nodes are not adjacent. In a causal graph setting, the parents do not directly
depend on each other, but conditioning on the child node provides an association.
Parameters
----------
G : graph
A networkx `~networkx.DiGraph`.
Yields
------
A 3-tuple representation of a v-structure
Each v-structure is a 3-tuple with the parent, collider, and other parent.
Raises
------
NetworkXNotImplemented
If `G` is an undirected graph.
Examples
--------
>>> G = nx.DiGraph([(1, 2), (0, 4), (3, 1), (2, 4), (0, 5), (4, 5), (1, 5)])
>>> nx.is_directed_acyclic_graph(G)
True
>>> list(nx.dag.v_structures(G))
[(0, 4, 2), (0, 5, 1), (4, 5, 1)]
See Also
--------
colliders
Notes
-----
This function was written to be used on DAGs, however it works on cyclic graphs
too. Since colliders are referred to in the cyclic causal graph literature
[2]_ we allow cyclic graphs in this function. It is suggested that you test if
your input graph is acyclic as in the example if you want that property.
References
----------
.. [1] `Pearl's PRIMER <https://bayes.cs.ucla.edu/PRIMER/primer-ch2.pdf>`_
Ch-2 page 50: v-structures def.
.. [2] A Hyttinen, P.O. Hoyer, F. Eberhardt, M J ̈arvisalo, (2013)
"Discovering cyclic causal models with latent variables:
a general SAT-based procedure", UAI'13: Proceedings of the Twenty-Ninth
Conference on Uncertainty in Artificial Intelligence, pg 301–310,
`doi:10.5555/3023638.3023669 <https://dl.acm.org/doi/10.5555/3023638.3023669>`_
"""
for p1, c, p2 in colliders(G):
if not (G.has_edge(p1, p2) or G.has_edge(p2, p1)):
yield (p1, c, p2)


@not_implemented_for("undirected")
@nx._dispatchable
def colliders(G):
"""Yields 3-node tuples that represent the colliders in `G`.
In a Directed Acyclic Graph (DAG), if you have three nodes A, B, and C, and
there are edges from A to C and from B to C, then C is a collider [1]_ . In
a causal graph setting, this means that both events A and B are "causing" C,
and conditioning on C provide an association between A and B even if
no direct causal relationship exists between A and B.
Parameters
----------
G : graph
A networkx `~networkx.DiGraph`.
Yields
------
A 3-tuple representation of a collider
Each collider is a 3-tuple with the parent, collider, and other parent.
Raises
------
NetworkXNotImplemented
If `G` is an undirected graph.
Examples
--------
>>> G = nx.DiGraph([(1, 2), (0, 4), (3, 1), (2, 4), (0, 5), (4, 5), (1, 5)])
>>> nx.is_directed_acyclic_graph(G)
True
>>> list(nx.dag.colliders(G))
[(0, 4, 2), (0, 5, 4), (0, 5, 1), (4, 5, 1)]
See Also
--------
v_structures
Notes
-----
This function was written to be used on DAGs, however it works on cyclic graphs
too. Since colliders are referred to in the cyclic causal graph literature
[2]_ we allow cyclic graphs in this function. It is suggested that you test if
your input graph is acyclic as in the example if you want that property.
References
----------
.. [1] `Wikipedia: Collider in causal graphs <https://en.wikipedia.org/wiki/Collider_(statistics)>`_
.. [2] A Hyttinen, P.O. Hoyer, F. Eberhardt, M J ̈arvisalo, (2013)
"Discovering cyclic causal models with latent variables:
a general SAT-based procedure", UAI'13: Proceedings of the Twenty-Ninth
Conference on Uncertainty in Artificial Intelligence, pg 301–310,
`doi:10.5555/3023638.3023669 <https://dl.acm.org/doi/10.5555/3023638.3023669>`_
"""
for collider, preds in G.pred.items():
for common_parents in combinations(preds, r=2):
# ensure that the colliders are the same
common_parents = sorted(common_parents)
yield (common_parents[0], collider, common_parents[1])
for node in G.nodes:
for p1, p2 in combinations(G.predecessors(node), 2):
yield (p1, node, p2)
11 changes: 7 additions & 4 deletions networkx/algorithms/operators/product.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
Graph products.
"""

from itertools import product

import networkx as nx
Expand Down Expand Up @@ -291,10 +292,12 @@ def strong_product(G, H):
The strong product $P$ of the graphs $G$ and $H$ has a node set that
is the Cartesian product of the node sets, $V(P)=V(G) \times V(H)$.
$P$ has an edge $((u,v), (x,y))$ if and only if
$u==v$ and $(x,y)$ is an edge in $H$, or
$x==y$ and $(u,v)$ is an edge in $G$, or
$(u,v)$ is an edge in $G$ and $(x,y)$ is an edge in $H$.
$P$ has an edge $((u,x), (v,y))$ if any of the following conditions
are met:
- $u=v$ and $(x,y)$ is an edge in $H$
- $x=y$ and $(u,v)$ is an edge in $G$
- $(u,v)$ is an edge in $G$ and $(x,y)$ is an edge in $H$
Parameters
----------
Expand Down
53 changes: 52 additions & 1 deletion networkx/algorithms/tests/test_dag.py
Original file line number Diff line number Diff line change
Expand Up @@ -760,7 +760,8 @@ def test_ancestors_descendants_undirected():

def test_compute_v_structures_raise():
G = nx.Graph()
pytest.raises(nx.NetworkXNotImplemented, nx.compute_v_structures, G)
with pytest.raises(nx.NetworkXNotImplemented, match="for undirected type"):
nx.compute_v_structures(G)


def test_compute_v_structures():
Expand All @@ -775,3 +776,53 @@ def test_compute_v_structures():
G = nx.DiGraph(edges)
v_structs = set(nx.compute_v_structures(G))
assert len(v_structs) == 2


def test_compute_v_structures_deprecated():
G = nx.DiGraph()
with pytest.deprecated_call():
nx.compute_v_structures(G)


def test_v_structures_raise():
G = nx.Graph()
with pytest.raises(nx.NetworkXNotImplemented, match="for undirected type"):
nx.dag.v_structures(G)


def test_v_structures():
edges = [(0, 1), (0, 2), (3, 2)]
G = nx.DiGraph(edges)

v_structs = set(nx.dag.v_structures(G))
assert len(v_structs) == 1
assert (0, 2, 3) in v_structs

edges = [("A", "B"), ("C", "B"), ("D", "G"), ("D", "E"), ("G", "E")]
G = nx.DiGraph(edges)
v_structs = set(nx.dag.v_structures(G))
assert v_structs == {("A", "B", "C")}

edges = [(0, 1), (2, 1), (0, 2)] # adjacent parents case: issues#7385
G = nx.DiGraph(edges)
assert set(nx.dag.v_structures(G)) == set()


def test_colliders_raise():
G = nx.Graph()
with pytest.raises(nx.NetworkXNotImplemented, match="for undirected type"):
nx.dag.colliders(G)


def test_colliders():
edges = [(0, 1), (0, 2), (3, 2)]
G = nx.DiGraph(edges)

colliders = set(nx.dag.colliders(G))
assert len(colliders) == 1
assert (0, 2, 3) in colliders

edges = [("A", "B"), ("C", "B"), ("D", "G"), ("D", "E"), ("G", "E")]
G = nx.DiGraph(edges)
colliders = set(nx.dag.colliders(G))
assert colliders == {("A", "B", "C"), ("D", "E", "G")}
3 changes: 3 additions & 0 deletions networkx/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,9 @@ def set_warnings():
warnings.filterwarnings(
"ignore", category=DeprecationWarning, message=r"\n\nThe 'create=matrix'"
)
warnings.filterwarnings(
"ignore", category=DeprecationWarning, message="\n\ncompute_v_structures"
)


@pytest.fixture(autouse=True)
Expand Down
Loading

0 comments on commit f7f2042

Please sign in to comment.