Working with networkx graphs

bnmetrics.to_graphlike accepts a networkx.DiGraph and converts it to the canonical (n_vars, endpoints, var_names) representation. Directed edges follow the DiGraph orientation convention; undirected (CPDAG-style) edges are encoded by setting the edge attribute type="undirected", in which case the adapter also requires the reverse edge to be present.

DAG input

import networkx as nx
import bnmetrics

g = nx.DiGraph()
g.add_edges_from([("A", "B"), ("A", "C"), ("B", "D"), ("C", "D")])
gl = bnmetrics.to_graphlike(g)

gl.var_names      # ('A', 'B', 'C', 'D')
gl.n_vars         # 4

The adapter infers var_names from the node iteration order of the DiGraph. Pass an explicit var_names=(...) to to_graphlike to override.

CPDAG-style input with undirected edges

The CPDAG of the diamond DAG above leaves the upper edges \(A - B\) and \(A - C\) undirected. Encode each undirected edge as a pair of directed edges marked type="undirected":

g_cpdag = nx.DiGraph()
# Undirected upper edges.
g_cpdag.add_edge("A", "B", type="undirected")
g_cpdag.add_edge("B", "A", type="undirected")
g_cpdag.add_edge("A", "C", type="undirected")
g_cpdag.add_edge("C", "A", type="undirected")
# Directed v-structure into D.
g_cpdag.add_edge("B", "D")
g_cpdag.add_edge("C", "D")
gl_cpdag = bnmetrics.to_graphlike(g_cpdag)

bnmetrics.shd(gl, gl_cpdag)   # 2 — the two upper edges differ in orientation

A BNMInputError is raised if an type="undirected" edge is added in only one direction, since silent half-undirected inputs are the most common encoding mistake.