The Pipeflow Procedure
In the pipeflow calculation, the fluid dynamics of all components with their hydraulic quantities are calculated, i.e. the pressures for all nodes (junctions, internal pipe nodes, … ) and the velocities for all branches (pipes, valves, … ). In addition it is possible to calculate the heat transfer through the network and from the network to external components or the environment.
The Newton-Raphson Solver
In order to do so, all different component types have to deliver their calculation bases, but some level of abstraction is required as well. The calculations are not performed with the pandas tables defined in the pandapipes network, but on the basis of an internal structure called “pandapipes internal tables” or “pit”, which can be found in net[“_pit”]. This internal structure consists of two numpy arrays, one for all network nodes and one for all branches. With the help of this internal structure, a system matrix is built up, i.e. a system of equations in order to solve for pressure and velocity based on initial guesses and formulations for the derivatives. That means that a Newton-Raphson solver (c.f. Newton-Raphson method on Wikipedia, or [DR08]) is implemented in pandapipes which works as follows:
Initial guesses for p and v are inserted in the node and branch tables for each of the components. The initial guesses might either be derived from a flat start (i.e. all values are the same or derived from values in the tables, such as the nominal pressure level of a network junction) or can be taken from the internal structure if previous calculations have been performed.
As long as the stopping criteria (usually maximum iterations as well as residual, pressure and velocity tolerances that can be set in the pipeflow options) are not reached, each component calculates the residual between given values (such as the mass flow balance for junctions and the pressure difference for pipes) and the calculated values based on the current guesses of p and v. In addition, the derivatives with respect to p and v are calculated.
The residual vectors and the derivatives of the components are stacked to a jacobian matrix and a load vector from which a linear system of equations is derived internally and solved, thus deriving the next Newton step for p and v.
In order to calculate the values correctly, some boundary conditions have to be given, which are:
The pressure value for at least one network node, which is then the slack node.
The mass flow that is extracted at every node by sinks or fed-in by sources.
Environmental conditions, such as the height of each junction, the ambient pressure, the fluid temperature and other properties (c.f. Fluid Properties).
This approach is very similar for the heat transfer calculation; the main difference is that the transferred heat is calculated on the basis of the already calculated mass flow and the magnitudes that are solved for are just temperature values. Here the node temperatures represent the mixed fluid temperatures and the branch temperatures represent the temperatures at internal nodes at the rear of the branches. The mixed fluid temperature is calculated based on all incoming flows to the node. The internal node temperature depends on the previous node’s temperature and the heat losses to the environment (c.f. also the description of the pipe component).
Connectivity Check
An important prerequisite to finding a good solution of the pipeflow is that all nodes and branches included in the internal structure are connected to different components and finally have a connection to some external grid that presets a fixed pressure or temperature level. Only if each considered node has a connection to some slack node, the pressure and thus the temperature can be calculated. Therefore a connectivity check is included in every pipeflow, unless it is switched off by the user (see Initialize Option Function for all possible options to be set). It is performed on the basis of a scipy sparse matrix with the help of the scipy csgraph functionalities . With the help of the connectivity check disconnected network areas can be set out of service automatically, reducing the error-proneness of the calculation process.
- check_connectivity(net, branch_pit, node_pit, mode='hydraulics')
Perform a connectivity check which means that network nodes are identified that don’t have any connection to an external grid component. Quick overview over the steps of this function:
Build a sparse matrix graph (scipy.sparse.csr_matrix) from all branches that are in_service (nodes of this graph are taken from FROM_NODE and TO_NODE column in pit).
Add a node that represents all external grids and connect all nodes that are connected to external grids to that node.
Perform a breadth first order search to identify all nodes that are reachable from the added external grid node.
Create masks for exisiting nodes and branches to show if they are reachable from an external grid.
Compare the reachable nodes with the initial in_service nodes.
If nodes are reachable that were set out of service by the user, they are either set in_service or an error is raised. The behavior depends on the pipeflow option quit_on_inconsistency_connectivity.
If nodes are not reachable that were set in_service by the user, they will be set out of service automatically (this is the desired functionality of the connectivity check).
- Parameters:
net (pandapipesNet) – The pandapipesNet for which to perform the check
branch_pit (np.array) – Internal array with branch entries
node_pit (np.array) – Internal array with node entries
- Returns:
(nodes_connected, branches_connected) - Lookups of np.arrays stating which of the internal nodes and branches are reachable from any of the hyd_slacks (np mask).
- Return type:
tuple(np.array)
Internal Structure
As mentioned previously, the calculation is based on the pandapipes internal tables (pit) structure. The included node and branch arrays contain all the information necessary for constructing the Jacobian matrix, such as the load vectors and their derivatives. However, it is important that only the really active parts of the network are considered. In order to simplify some of the calculations, an internal pit is created which does not contain the nodes and branches that were set out of service by the user or the connectivity check. It can be found in net[“_active_pit”].
Note
Branches always connect two nodes, the FROM_NODE and the TO_NODE. In every branch table these two values will probably look different. In the pipe table these nodes are the indices of the respective junctions. In the pit these nodes refer to the places within the node pit, and in the internal pit, these nodes are adapted once again, as all out of service nodes will be dropped thus changing the other nodes’ placement within the table.
The functions used to create the internal pit and extract results back from it are:
- reduce_pit(net, mode='hydraulics')
Create an internal (“active”) pit with all nodes and branches that are actually in_service. This is also done for different lookups (e.g. the from_to indices for this pit and the node index lookup). A specialty that needs to be considered is that from_nodes and to_nodes change to new indices.
- Parameters:
net (pandapipesNet) – The pandapipesNet for which the pit shall be reduced
node_pit (np.array) – The internal structure node array
branch_pit (np.array) – The internal structure branch array
mode (str, default "hydraulics") – the mode of the calculation (either “hydraulics” or “heat_transfer”) for storing / retrieving correct lookups
- Returns:
No output
- extract_results_active_pit(net, mode='hydraulics')
Extract the pipeflow results from the internal pit structure (“_active_pit”) to the general pit structure.
- Parameters:
net (pandapipesNet) – The pandapipes net that the internal structure belongs to
mode (str, default "hydraulics") – defines whether results from hydraulic or temperature calculation are transferred
- Returns:
No output
Constructing the Jacobian Matrix
Once the internal structure is created, the Newton steps can be performed. That means that the residual and its derivatives with respect to the estimated variables (p, v, T) are calculated and written to the branch pit. Then the system matrix is constructed which means that all the derivatives are written into one large sparse matrix in which the row indices represent the node indices followed by the branch indices and the column indices represent the indices of the solution variables (typically they also belong to nodes and branches). It contains all the derivatives. The load vector (residual vector) is constructed by summarizing all the node related residuals from the branch pit at all nodes (e.g. incoming and outgoing mass flows) and appending the residuals calculated for the branches to it. The linearized system of equations for the hydraulic magnitudes in the end looks like this:
In this formulation, F stands for the residual or load vector value, n is the number of nodes and b the number of branches in the system. So the matrix on the left is the Jacobian matrix, the vector that it is multiplied with is the vector with the estimates’ step and the vector on the right is the load vector.
Note
Normally the meshing in a network is rather low, so the coupling between nodes is rather loose which means that most entries in the Jacobian matrix are in fact 0 and it can be expressed as a sparse matrix. More information can also be found in [JHP02].
- build_system_matrix(net, branch_pit, node_pit, heat_mode)
Builds the system matrix.
- Parameters:
net (pandapipesNet) – The pandapipes network
branch_pit (numpy.ndarray) – pandapipes internal table for branching components such as pipes or valves
node_pit (numpy.ndarray) – pandapipes internal table for node components
heat_mode (bool) – Is it a heat network calculation: True or False
- Returns:
system_matrix, load_vector
- Return type:
system_matrix - scipy.sparse.csr.csr_matrix, load_vector - numpy.ndarray