The network submodule
This module models a hydraulic network as a graph and solves the resulting nonlinear flow problem from topology plus component head laws. It is based on graph theory. For a more elaborate explanation we refer to the theory. Some of the terms used here are:
Graph theory is the study of mathematical objects known as graphs, which consist of nodes or vertices (points) connected by segments or edges.
Nodes or Vertices: are the fundamental units or points in a graph. Each node represents an entity or a location in the structure being modeled.
Adjacent Nodes: Two nodes that are directly connected by a segment.
Segments or Edges: are the connections or relationships between pairs of vertices. Each segment links two nodes, indicating a relationship or path between them.
Path: is a sequence of nodes where each adjacent pair is connected by a segment. They can be simple (no repeated nodes) or general (allowing repeats). For instance, in a graph with nodes A, B, C, and D, a path could be A → B → C → D, where each node is connected to the next by a segment.
Cycle: is a path that starts and ends at the same node, with no other repetitions of nodes or segments. Cycles can be simple (no repeated segments or nodes except for the start and end) or general. Here’s an example: In a graph with nodes A, B, C, and D, a simple cycle could be A → B → C → D → A.
Connected graph: A graph is connected when there is a path between every pair of nodes. In a connected graph, there is no unreachable node.
We always presume that the input network forms a connected graph.
The properties below are useful when inspecting the internal graph build. Assume the following network:
B ────── C ────── D
| | |
| | |
A ────── F ────── E
nodes: returns all node names (sorted).
net.nodes = ('A', 'B', 'C', 'D', 'E', 'F')edges: returns segment keys, one key per component port-to-port segment.
net.edges = ['Comp_A:A-B', 'Comp_B:B-C', ...]adjacency: returns adjacency entries as
(segment_key, next_node, sense).net.adjacency['A'] = [('Comp_A:A-B', 'B', 1), ('Comp_F:F-A', 'F', -1)]spanningTree: returns tree entries as
(segment_key, node_from, node_to, sense).net.spanningTree = [('Comp_A:A-B', 'A', 'B', 1), ...]cycleBase: returns ordered loop entries as
(segment_key, node_from, node_to, sense). The stored cycle keeps the full tree path plus the closing chord instead of collapsing the loop to a smaller representation.net.cycleBase = [[('Comp_A:A-B', 'A', 'B', 1), ...], ...]
The network calculation solves a system of nonlinear equations made up of:
In every node the sum of flowrates has to be zero. For
nnodes this contributesn-1independent equations. The corresponding incidence matrix is stored innet.funcs['B']. The order of the rows follows the internal node storage['A', 'B', 'C', 'D', 'E', 'F']and every row has one position per active segment key. For the first line (node A): * The segment connected to A and leaving A contributes-1. * Segments not connected to A contribute0. * … * The segment connected to A and entering A contributes+1.
[ 1. 0. 0. 0. 0. 0. -1.]
[-1. 1. 0. 0. 0. 0. 0.]
[ 0. -1. 1. 0. 0. 1. 0.]
[ 0. 0. -1. 1. 0. 0. 0.]
[ 0. 0. 0. -1. -1. 0. 0.]
[ 0. 0. 0. 0. 1. -1. 1.]
In every cycle-base loop the sum of heads has to be zero. If there are
mcycle-base loops, this contributesmadditional equations. The corresponding loop matrix is stored innet.funcs['C']. Every cycle equation has one entry per segment key:+1if the segment is traversed in the cycle direction,-1if traversed opposite, and0if the segment is not part of that cycle.
C row i: [0, -1, +1, 0, 0, ...]
During calcNetwork() the solver rebuilds the topology, assembles matrices
B and C, evaluates segment heads through calcH(Q, sense, pin, pout),
and solves the resulting nonlinear system with scipy.optimize.root.
Some implementation details that matter when reading results:
Two-port source components are canonicalized so
sense=-1is treated as the equivalent reversed node order withsense=+1.Initial guesses are retried with both positive and negative seeds so the solve is less sensitive to segment orientation.
Most resistance components compute head from
|Q|. The network solver reapplies the solved flow sign when forming loop equations so solutions remain invariant to how a segment’s node order was entered.
Hydraulic network solver using graph topology plus component physics.
This module represents a hydraulic system as a graph:
nodes are connection points,
segments (edges) are component port-to-port links,
a spanning tree and chord set define a cycle basis.
The solver assembles a nonlinear system from two equation groups:
node continuity equations, assembled in matrix B,
loop energy equations, assembled in matrix C using component head laws.
Unknowns are segment flow rates. Component physics is delegated to each component through calcH(Q, sense). The network module is responsible for topology construction, incidence matrices, validation, and numerical solve.
Internal data artifacts exposed by the class include:
Segments: per-segment dictionaries with node endpoints, sense, ports, and owning component,Nodes/Edges: graph-level views used by solvers and diagnostics,AdjacencyandSpanningTree: traversal structures used for cycle basis generation,CycleBase: loop basis used for energy equations,Funcs: assembledBandCmatrices,Result: solved per-segment flow/head values.
Assumptions and conventions:
the topology should form a connected graph,
each component defines ports and usable internal connections,
segment direction and sense are tracked explicitly,
energy sources and resistances are validated per loop before solve.
Solve pipeline summary:
Register components and expand them into graph segments.
Build adjacency, spanning tree, and cycle basis.
Assemble incidence matrices
BandC.Solve flow unknowns with Newton-Raphson.
Back-calculate and store per-segment
QandHresults.
Typical workflow:
net = getNetwork(name='N1', components=[...])
result = net.calcNetwork()
print(net.toString(detail=1))
This design keeps topology logic centralized while allowing each component class to own its physical constitutive behavior.
- class fluidsolve.network.Network(**kwargs: Any)[source]
Bases:
objectClass representing a hydraulic network.
- Parameters:
name (str, optional) – Network label.
components (list, optional) – Initial list of components to register.
- property components: Any
Ordered list of components.
- property segments: dict[str, dict]
Return the segments of the network.
- Returns:
Segment dictionary.
- Return type:
list[dict]
- property nodes: tuple[str, ...]
Return the nodes in this network.
- Returns:
Node list.
- Return type:
list[str]
- property edges: list[tuple[str, str]]
Return the edges in this network.
- Returns:
Edge list.
- Return type:
list[tuple[str, str]]
- property adjacency: dict[str, list[str]]
Return the adjacency list.
- Returns:
Adjacency list.
- Return type:
dict[str, list[str]]
- property spanningTree: list[tuple[str, str]]
Return the spanning tree.
- Returns:
Spanning tree.
- Return type:
list[tuple[str, str]]
- property cycleBase: list[list[str]]
Return the cycle base of the graph.
- Returns:
Cycle base.
- Return type:
list[list[str]]
- property funcs: dict[str, list]
Return the incidence matrices used to build the system of equations.
- Returns:
B and C matrices.
- Return type:
dict[str, list]
- property result: list[dict]
Return the solver result.
- Returns:
Per segment: name, Q, and H.
- Return type:
list[dict]
- addComponents(components: list) None[source]
Add components to the network and rebuild the graph.
- Parameters:
components (list) – List of component dicts with keys
comp,nodes, and optionalsense.
- calcNetwork(guess: Any | None = None) Any[source]
Solve the network using Newton-Raphson.
- Parameters:
guess (float, list, tuple, or np.ndarray, optional) – Initial flow guess for all segments.
- Returns:
Per segment: name, Q, and H.
- Return type:
list[dict]
- _calcValidation() None[source]
Validate fundamental loop equations.
Raises ValueError when a loop is physically invalid.
- _sortCycle(cycle: list[list[tuple]]) list[list[tuple]][source]
Sort a cycle so it starts from the smallest node deterministically.
- __str__() str[source]
Return a compact string representation.
- Returns:
Compact network summary.
- Return type:
str
- toString(detail: int = 0) str[source]
Return a formatted multi-line network description.
- Parameters:
detail (int, optional) – Include topology and matrix details when non-zero.
- Returns:
Formatted network text.
- Return type:
str
- nodeString() str[source]
Format the node list for display.
- Returns:
Node section text.
- Return type:
str
- segmentsString() str[source]
Format the segment table for display.
- Returns:
Segment section text.
- Return type:
str
- adjacencyString() str[source]
Format the adjacency list for display.
- Returns:
Adjacency section text.
- Return type:
str
- spanningTreeString() str[source]
Format the spanning tree for display.
- Returns:
Spanning tree section text.
- Return type:
str
- cycleBaseString() str[source]
Format the cycle base for display.
- Returns:
Cycle base section text.
- Return type:
str
- functionString() str[source]
Format incidence matrices B and C for display.
- Returns:
Matrix section text.
- Return type:
str