Source code for semi_cr.core.lab.station.graphing.operations
import networkx as nx
from collections.abc import Iterable
from typing import Any
from semi_cr.core.lab.station.graphing.models import Node, Edge, GraphRelation
from semi_cr.core.lab.station.graphing.parsing import normalize_channel
from semi_cr.core.lab.station.graphing.ids import instrument_node_id
# LOW-LEVEL GRAPH OPERATIONS
[docs]
def build_nx_graph(
nodes: Iterable[Node],
edges: Iterable[Edge],
) -> nx.MultiDiGraph:
graph = nx.MultiDiGraph()
for node in nodes:
attrs = {
"kind": node.kind,
"name": node.name,
**node.attrs,
}
if node.obj is not None:
attrs["obj"] = node.obj
graph.add_node(node.id, **attrs)
for edge in edges:
attrs = {"kind": edge.kind,
**edge.attrs}
if edge.state is not None:
attrs["state"] = edge.state
graph.add_edge(
edge.source,
edge.target,
**attrs,
)
return graph
[docs]
def add_connection(
graph: nx.MultiDiGraph,
source: str,
target: str,
kind: str,
state: str = "active",
**attrs: Any,
) -> None:
"""
Adds a directed physical edge.
Direction convention:
electronics -> matrix -> fischer -> converter -> filter -> sample -> device
Therefore, reverse BFS from a device terminal walks outward toward electronics.
"""
if source not in graph:
graph.add_node(source, kind="unknown", name=source)
if target not in graph:
graph.add_node(target, kind="unknown", name=target)
graph.add_edge(
source,
target,
kind=kind,
state=state,
**attrs,
)
[docs]
def ensure_instrument_terminal(
graph: nx.MultiDiGraph,
instrument_name: str,
terminal_name: str,
node_id: str,
) -> None:
instrument_id = instrument_node_id(
instrument_name
)
if instrument_id not in graph:
graph.add_node(
instrument_id,
kind="instrument",
config_name=instrument_name,
)
if node_id not in graph:
graph.add_node(
node_id,
kind="instrument_terminal",
instrument_name=instrument_name,
terminal_name=normalize_channel(terminal_name),
)
if not graph.has_edge(
instrument_id,
node_id,
):
graph.add_edge(
instrument_id,
node_id,
kind=GraphRelation.CONTAINS,
)