The ManningResistance node calculates a flow rate between two Basins based on their water levels. The flow rate is calculated by conservation of energy and the Manning-Gauckler formula to estimate friction losses.
1 Tables
1.1 Static
column
type
unit
restriction
node_id
Int32
-
sorted
control_state
String
-
(optional) sorted per node_id
active
Bool
-
(optional, default true)
length
Float64
\(\text{m}\)
positive
manning_n
Float64
\(\text{s} \text{m}^{-\frac{1}{3}}\)
positive
profile_width
Float64
\(\text{m}\)
positive
profile_slope
Float64
-
-
2 Equations
ManningResistance simulates steady flow between Basins through a reach described by a trapezoidal profile and a Manning roughness coefficient.
We describe the discharge from Basin \(a\) to Basin \(b\) solely as a function of the water levels in \(a\) and \(b\).
\[
Q = f(h_a, h_b)
\]
Where:
The subscripts \(a\) and \(b\) denotes two different Basins
\(h\) is the hydraulic head, or water level
The energy equation for open channel flow is:
\[
H = h + \frac{v^2}{2g}
\]
Where:
\(H\) is total head
\(v\) is average water velocity
\(g\) is gravitational acceleration
The discharge \(Q\) is defined as:
\[
Q = Av
\]
where \(A\) is cross-sectional area.
We use conservation of energy to relate the total head at \(a\) to \(b\), with \(H_a > H_b\) as follows:
The \(\textrm{sign}(\Delta h)\) term causes the direction of the flow to reverse if the head in basin \(b\) is larger than in basin \(a\).
This expression however has a derivative which tends to \(\infty\) as \(\Delta h\) tends to \(0\), which can lead to instabilities in simulation. Therefore we use the modified expression
import numpy as npimport matplotlib.pyplot as pltdef s(x, threshold):if np.abs(x) < threshold: x_scaled = x / thresholdreturn np.sqrt(threshold) * x_scaled**3* (9-5*x_scaled**2) /4else:return np.sign(x)*np.sqrt(np.abs(x))x = np.linspace(-0.0025, 0.0025, 100)threshold =1e-3fig, ax = plt.subplots()y_o = np.sign(x)*np.sqrt(np.abs(x))y_s = [s(x_, threshold) for x_ in x]ax.plot(x, y_o, ls =":", label =r"sign$(x)\sqrt{|x|}$")ax.plot(x, y_s, color ="C0", label =r"$s\left(x; 10^{-3}\right)$")ax.legend();
Note
The computation of \(S_f\) is not exact: we base it on a representative area and hydraulic radius, rather than integrating \(S_f\) along the length of a reach. Direct analytic solutions exist for e.g. parabolic profiles (Tolkmitt), but other profiles requires relatively complicated approaches (such as approximating the profile with a polynomial).
We use the average value of the cross-sectional area, the average value of the water depth, and the average value of the hydraulic radius to compute a friction slope. The size of the resulting error will depend on the water depth difference between the upstream and downstream Basin.
The cross sectional area for a trapezoidal or rectangular profile:
\[
A = w d + \frac{\Delta y}{\Delta z} d^2
\]
Where
\(w\) is the width at \(d = 0\) (A triangular profile has \(w = 0\))
\(\frac{\Delta y}{\Delta z}\) is the slope of the profile expressed as the horizontal length for one unit in the vertical (A slope of 45 degrees has \(\frac{\Delta y}{\Delta z} = 1\); a rectangular profile 0).
Accordingly, the wetted perimeter is:
\[
B = w + 2 d \sqrt{\left(\frac{\Delta y}{\Delta z}\right)^2 + 1}
\]