Run this notebook in your web browser:
The link opens rechenbeispiel-staustufendurchbildung.py as an interactive marimo notebook that runs entirely in your web browser, without installing anything. Like the Jupyter notebook, the marimo notebook is the original German version of this page.
Wait a moment until Python has loaded in the browser (this takes longer only at the first start).
Click the yellow run button (▶︎, bottom right) to run all cells.
Edit and re-run any code cell to experiment: marimo automatically updates all cells that depend on it.
Your edits remain in the browser tab only. To keep them, save the notebook through the menu (☰, top right) > Export… > Python.
Run this marimo notebook locally
To run rechenbeispiel-staustufendurchbildung.py on your own machine, get it from the marimo
Clone the repository:
git clone https://github.com/sschwindt/marimo-python-course.gitCreate and activate the course environment:
conda env create -f environment.ymlandconda activate hy-marimoIn the repository folder, start the notebook with
marimo edit rechenbeispiel-staustufendurchbildung.py
Run this notebook with Jupyter
This page is also available as the Jupyter notebook rechenbeispiel-staustufendurchbildung.ipynb in the jupyter, or install Python and JupyterLab locally to run rechenbeispiel-staustufendurchbildung.ipynb on your own machine. The Jupyter notebook is the original German version of this page.
This notebook combines two related tasks:
Preliminary hydraulic design of the weir (Poleni, (n‑1) rule)
Stilling basin design (iterative workflow according to Bollrich / Peterka)
Learning objectives:
see the calculation steps;
understand the key assumptions;
be able to check orders of magnitude;
recognize the limits of simplifications.
import math
from scipy.optimize import brentq
from pprint import pprintPart 1: Preliminary Hydraulic Design¶
Contents¶
A rough preliminary hydraulic design of a fully regulating (gated) weir is carried out:
Choice of a plausible design case
Preliminary sizing of the required clear weir width
Division into weir bays
Verification of the (n‑1) rule (DIN 19700)
Rough freeboard check
Given Data and Notation¶
Poleni equation for free (unsubmerged) overflow:
Effective overflow width (contraction due to piers):
Derivation: with pier equivalents (the two abutments count as half piers each)
| Symbol | Meaning |
|---|---|
| Discharge (m³/s) | |
| Effective weir width (m) | |
| Overflow head (m) | |
| Reduction coefficient for submerged (imperfect) overflow (–) | |
| Discharge coefficient (–) | |
| Permissible overflow head at BHQ₁ (m above weir crest) | |
| Highest permissible headwater level at BHQ₁ (m a.s.l., NHN); | |
| Required freeboard (m) | |
| Number of weir bays | |
| Width of one weir bay (m) | |
| Width of one pier (m) | |
| Pier contraction coefficient (–) |
The notation follows the German convention: BHQ₁ and BHQ₂ are the design floods 1 and 2 according to DIN 19700, the index ü stands for Überfall (overflow), and zul stands for zulässig (permissible).
Input Parameters¶
BHQ1 = 65.0 # m³/s design flood 1 (standard case)
BHQ2 = 85.0 # m³/s design flood 2 (control case)
# h_ue_zul: permissible overflow head above the weir crest at BHQ1 (= z_H - z_Kr, relative)
# Not to be confused with z_H (absolute headwater level in m a.s.l., see notation)
h_ue_zul = 1.10 # m
f = 0.50 # m freeboard (DIN 19700: f ≥ 0.5 m at BHQ1)
mu = 0.64 # discharge coefficient (movable weir, sharp-crested)
c = 1.0 # reduction coefficient (free overflow, initial value)
xi_pf = 0.10 # pier contraction coefficient (rounded pier noses)
b_F = 15.0 # m chosen weir bay width (technically constrained)
b_pf = b_F * 0.225 # m pier width (radial gate)
g = 9.81 # m/s²
print(f"Pier width b_pf = {b_pf:.2f} m")Pier width b_pf = 3.38 m
Poleni & Required Weir Width¶
Design target (DIN 19700, (n‑1) rule): With active weir bays, BHQ₁ must be discharged without .
Solving the Poleni equation for :
Solving for (for a given width):
Helper Functions for a Poleni Solver¶
c_pol = (2/3) * mu * math.sqrt(2 * g) # combined Poleni coefficient without b and h_ue
def b_eff_from_Q(Q, h_ue, c_red=1.0):
"""Required effective weir width for a given discharge."""
return Q / (c_red * c_pol * h_ue**1.5)
def h_ue_from_Q(Q, b_eff, c_red=1.0):
"""Overflow head for a given discharge and effective width."""
return (Q / (c_red * c_pol * b_eff))**(2/3)
def b_eff_n_bays(n_active, h_ue):
"""Effective width of n_active adjacent weir bays (accounting for pier contraction)."""
return n_active * (b_F - 2 * xi_pf * h_ue)Required Width and Number of Weir Bays¶
# The total b_eff for the (n-1) case must satisfy b_eff_req >= b_eff_from_Q(BHQ1, h_ue_zul)
b_eff_req = b_eff_from_Q(BHQ1, h_ue_zul, c)
b_F_eff = b_F - 2 * xi_pf * h_ue_zul # effective width per bay
n_minus1 = math.ceil(b_eff_req / b_F_eff) # required number of (n-1) bays
n = n_minus1 + 1 # total number of weir bays
print(f"Combined Poleni coefficient c_pol = {c_pol:.4f}")
print(f"Required b_eff (n-1 case) = {b_eff_req:.2f} m")
print(f"Effective bay width b_F_eff = {b_F_eff:.2f} m")
print(f"Required (n-1) bays = {n_minus1}")
print(f"Chosen number of weir bays n = {n}")Combined Poleni coefficient c_pol = 1.8899
Required b_eff (n-1 case) = 29.81 m
Effective bay width b_F_eff = 14.78 m
Required (n-1) bays = 3
Chosen number of weir bays n = 4
Weir Dimensions and Width¶
n_piers = n - 1 # intermediate piers
b_clear = n * b_F # total clear width (bays only)
b_piers = n_piers * b_pf # sum of pier widths
b_total = b_clear + b_piers # total width including piers
print(f"Number of intermediate piers : {n_piers}")
print(f"Total clear width : {b_clear:.1f} m")
print(f"Sum of pier widths : {b_piers:.2f} m")
print(f"Total structure width : {b_total:.2f} m")Number of intermediate piers : 3
Total clear width : 60.0 m
Sum of pier widths : 10.12 m
Total structure width : 70.12 m
(n‑1) -- Verification¶
Condition: With active bays, the following must hold:
In addition, BHQ₁ and BHQ₂ are checked with all bays.
cases = [
("BHQ1, all n bays", BHQ1, n),
("BHQ1, (n-1) bays", BHQ1, n - 1),
("BHQ2, all n bays", BHQ2, n),
]
print(f"{'Case':<28} {'n_act':>5} {'b_eff':>8} {'h_ü':>7} {'≤ h_ü,zul?':>10}")
print("-" * 62)
for label, Q, n_act in cases:
h_ue_iter = h_ue_zul
for _ in range(20):
beff = b_eff_n_bays(n_act, h_ue_iter)
h_ue_new = h_ue_from_Q(Q, beff, c)
if abs(h_ue_new - h_ue_iter) < 1e-6:
break
h_ue_iter = h_ue_new
ok = "OK" if h_ue_iter <= h_ue_zul else "!!"
print(f"{label:<28} {n_act:>5} {beff:>8.2f} {h_ue_iter:>7.3f} {ok:>10}")
print(f"\n h_ü,zul = {h_ue_zul:.2f} m (max. permissible overflow head at BHQ1)")Case n_act b_eff h_ü ≤ h_ü,zul?
--------------------------------------------------------------
BHQ1, all n bays 4 59.44 0.694 OK
BHQ1, (n-1) bays 3 44.49 0.842 OK
BHQ2, all n bays 4 59.33 0.831 OK
h_ü,zul = 1.10 m (max. permissible overflow head at BHQ1)
Retention Level & Freeboard Check¶
Verification: , that is, the highest permissible headwater level is respected.
The freeboard (DIN 19700: m at BHQ₁; among others for waves and wind) lies above and must not be confused with the margin up to :
Crest height (above the weir sill)
# h_ue at BHQ1, (n-1) bays (governing case of the (n-1) rule)
h_ue_n1 = h_ue_zul
for _ in range(20):
beff_n1 = b_eff_n_bays(n - 1, h_ue_n1)
h_ue_n1 = h_ue_from_Q(BHQ1, beff_n1, c)
head_margin = h_ue_zul - h_ue_n1 # > 0: permissible backwater level respected
crest_height = h_ue_zul + f
head_criterion = 'OK' if head_margin >= 0 else 'Not fulfilled'
print(f"h_ü at BHQ1, (n-1) bays : {h_ue_n1:.3f} m")
print(f"Margin up to h_ü,zul (head reserve) : {head_margin:.3f} m")
print(f"Crest height (= h_ü,zul + f) : {crest_height:.2f} m above weir sill")
print(f"Retention level check h_ü <= h_ü,zul : {head_criterion}")
print(f"Freeboard requirement f >= 0.5 m : {'OK' if f >= 0.5 else 'Not fulfilled'}")h_ü at BHQ1, (n-1) bays : 0.842 m
Margin up to h_ü,zul (head reserve) : 0.258 m
Crest height (= h_ü,zul + f) : 1.60 m above weir sill
Retention level check h_ü <= h_ü,zul : OK
Freeboard requirement f >= 0.5 m : OK
Part 2: Stilling Basin Design¶
Concept¶
The goal is to force the hydraulic jump to occur within the stilling basin. Design degree of freedom: stilling basin depression (in m).
The submergence ratio must lie in the target range:
Iterative algorithm (cf. lecture slide):
| Step | Content |
|---|---|
| A | Set (initial value) |
| B | , from the Bernoulli equation (energy head ) |
| C | |
| D | ? → No: adjust |
| E | from Manning–Strickler or measured |
| F | (Bélanger) |
| G | ? → No: adjust |
| + | Calculate and |
Additional Stilling Basin Input Parameters¶
In addition to the weir parameters, the following are required:
| Symbol | Meaning |
|---|---|
| Weir height above the tailwater bed (m) | |
| Strickler coefficient of the tailwater reach (m¹/³/s) | |
| Energy slope of the tailwater reach (= bed slope for normal flow) (–) | |
| Width of the tailwater reach (m) | |
| Overflow head in the governing load case (m) | |
| Total energy head above the stilling basin floor: |
The unit discharge for the stilling basin design is conservatively calculated from the bay case (DIN 19700, (n‑1) rule): with one bay out of service, per bay width increases, which makes it the governing load case for the stilling basin. The energy head is computed with the overflow head belonging to this load case (not with ), so that and are consistent.
w_weir = 2.5 # m weir height above the tailwater bed
k_st = 30.0 # m^1/3/s Strickler coefficient of the tailwater reach (near-natural bed)
I_E = 0.001 # -- energy slope of the tailwater reach (= bed slope for normal flow)
b_u = 40.0 # m width of the tailwater reach (rectangular cross-section)
# unit discharge for the stilling basin: conservative (n-1) bay case (DIN 19700)
n_sb = n - 1 # active bays in the governing load case
h_ue_sb = h_ue_zul # initial value for the fixed-point iteration
for _ in range(20):
beff_sb = b_eff_n_bays(n_sb, h_ue_sb)
h_ue_sb = h_ue_from_Q(BHQ1, beff_sb, c)
q = BHQ1 / beff_sb # m²/s - unit discharge in the (n-1) bay case
# h_ü at BHQ1 with all n bays (for the summary)
h_ue_n = h_ue_zul
for _ in range(20):
beff_n = b_eff_n_bays(n, h_ue_n)
h_ue_n = h_ue_from_Q(BHQ1, beff_n, c)
print(f"Governing load case: {n_sb} active bays, b_eff = {beff_sb:.2f} m, h_ü = {h_ue_sb:.3f} m")
print(f"Unit discharge q = BHQ1/b_eff = {q:.3f} m²/s")Governing load case: 3 active bays, b_eff = 44.49 m, h_ü = 0.842 m
Unit discharge q = BHQ1/b_eff = 1.461 m²/s
Tailwater Depth with Manning-Strickler (Preparation of Step E)¶
For a rectangular cross-section, the following applies:
A numerical 1d equation solver for the Manning-Strickler formula can be defined with the following function and called subsequently:
def Q_manning(h, b, k, I):
A = b * h
R = (b * h) / (b + 2 * h)
return k * A * R**(2/3) * math.sqrt(I)
# brentq searches for h in [0.01, 20] m such that Q_manning(h) = BHQ1
h_u = brentq(lambda h: Q_manning(h, b_u, k_st, I_E) - BHQ1, 0.01, 20.0)
print(f"Tailwater depth h_u = {h_u:.3f} m")
print(f"Check: Q_Manning = {Q_manning(h_u, b_u, k_st, I_E):.2f} m³/s (target: {BHQ1} m³/s)")Tailwater depth h_u = 1.420 m
Check: Q_Manning = 65.00 m³/s (target: 65.0 m³/s)
Steps A to G: Iterative Stilling Basin Design¶
Step B: Derivation of and ¶
Energy head above the stilling basin floor (Bernoulli; neglected assuming m/s, which must be checked in practice):
At the supercritical cross-section 1 (stilling basin inlet):
Coding the Workflow¶
The stilling basin design workflow and the determination of the k-factor as a function of the Froude number are defined in two functions, with as input variable:
def stilling_basin_check(e):
"""
Performs steps A-G of the stilling basin workflow for a given depression e.
Returns (Fr1, epsilon, h1, h2, ok_Fr, ok_eps).
"""
# Step B: h1 from Bernoulli (small, supercritical root);
# energy head consistent with the load case of the unit discharge q
H_ges = h_ue_sb + w_weir + e
# search h1 < h_crit (critical depth) = (q**2/g)^(1/3)
h_crit = (q**2 / g)**(1/3)
h1 = brentq(lambda h: h + q**2 / (2 * g * h**2) - H_ges, 1e-4, h_crit)
v1 = q / h1
# Step C: Froude number
Fr1 = v1 / math.sqrt(g * h1)
# Step D: Fr1 check
ok_Fr = 4.5 <= Fr1 < 9.0
# Step F: sequent (conjugate) depth h2 (Belanger)
h2 = (h1 / 2) * (math.sqrt(1 + 8 * Fr1**2) - 1)
# Step G: submergence ratio
eps = (h_u + e) / h2
ok_eps = 1.05 <= eps <= 1.15
return Fr1, eps, h1, h2, ok_Fr, ok_eps
# k-factor for the stilling basin length (cf. Peterka 1984 / USBR)
def k_factor(Fr1):
if Fr1 < 2.4: return 4.8
elif Fr1 < 4.0: return 4.8 + (Fr1 - 2.4) / (4.0 - 2.4) * (5.8 - 4.8)
elif Fr1 < 5.0: return 5.8 + (Fr1 - 4.0) * (6.0 - 5.8)
elif Fr1 < 6.0: return 6.0 + (Fr1 - 5.0) * (6.13 - 6.0)
elif Fr1 <= 11.0: return 6.13
else: return 6.0Iterating over ¶
The depression can now be calculated with the previously defined functions, based on an initial value and a search range:
e_test = 1.0 # m initial value (step A)
e_min = 0.0 # m lower limit of the search range
e_max = 3.0 # m upper limit of the search range
iterations = []
print(f"{'Iter':>4} {'e [m]':>7} {'Fr1':>6} {'Fr-OK':>6} {'h1 [m]':>8} {'h2 [m]':>8} {'ε':>6} {'ε-OK':>6}")
print("-" * 65)
for i in range(1, 15):
Fr1, eps, h1, h2, ok_Fr, ok_eps = stilling_basin_check(e_test)
iterations.append((e_test, Fr1, eps, h1, h2, ok_Fr, ok_eps))
fr_sym = "OK" if ok_Fr else "Not fulfilled"
eps_sym = "OK" if ok_eps else "Not fulfilled"
print(f"{i:>4} {e_test:>7.3f} {Fr1:>6.2f} {fr_sym:>6} {h1:>8.4f} {h2:>8.4f} {eps:>6.3f} {eps_sym:>6}")
if ok_Fr and ok_eps:
print("\n >>> Criteria fulfilled: iteration finished.")
break
# adjustment strategy (bisection logic, cf. lecture slide):
# larger e -> larger energy head -> smaller h1 -> larger Fr1
if not ok_Fr:
# Fr1 < 4.5 >>> e too small (increase e); Fr1 >= 9.0 >>> e too large (decrease e)
if Fr1 < 4.5:
e_min = e_test
else: # Fr1 >= 9.0
e_max = e_test
else:
# ok_Fr = True, but ok_eps = False
if eps > 1.15: # too much submergence >>> decrease e
e_max = e_test
else: # eps < 1.05 >>> too little submergence >>> increase e
e_min = e_test
e_test = 0.5 * (e_min + e_max) # bisection
# store final values
e_opt = e_test
Fr1_opt, eps_opt, h1_opt, h2_opt, ok_Fr_opt, ok_eps_opt = stilling_basin_check(e_opt)
if not (ok_Fr_opt and ok_eps_opt):
print("\n >>> No solution in the search range [e_min, e_max]: criteria cannot be fulfilled simultaneously.")
print(" Remedy: baffle elements (blocks, chute blocks, end sill) or check boundary conditions (cf. lecture).")Iter e [m] Fr1 Fr-OK h1 [m] h2 [m] ε ε-OK
-----------------------------------------------------------------
1 1.000 7.20 OK 0.1613 1.5637 1.547 Not fulfilled
2 0.500 6.53 OK 0.1722 1.5060 1.275 Not fulfilled
3 0.250 6.18 OK 0.1785 1.4745 1.132 OK
>>> Criteria fulfilled: iteration finished.
Stilling Basin Length and Scour Protection Length ¶
According to Peterka (1958/1984):
| 2.4 | 4 | 5 | 6–11 | 14 | |
|---|---|---|---|---|---|
| 4.8 | 5.8 | 6 | 6.13 | 6 |
The stilling basin and scour protection lengths can be calculated by calling the above-defined function for estimating the k-factor:
k = k_factor(Fr1_opt)
l_T = k * h2_opt
l_K = 3.5 * l_T
print(f"Optimized depression e = {e_opt:.3f} m")
print(f"Supercritical depth h1 = {h1_opt:.4f} m")
print(f"Froude number Fr1 = {Fr1_opt:.2f}")
print(f"Sequent depth h2 = {h2_opt:.3f} m")
print(f"Tailwater depth h_u = {h_u:.3f} m")
print(f"Submergence ratio ε = {eps_opt:.3f} (target: 1.05-1.15)")
print()
print(f"k-factor (Fr1={Fr1_opt:.1f}) k = {k:.2f}")
print(f"Stilling basin length l_T = {l_T:.2f} m")
print(f"Scour protection length l_K = {l_K:.2f} m")Optimized depression e = 0.250 m
Supercritical depth h1 = 0.1785 m
Froude number Fr1 = 6.18
Sequent depth h2 = 1.475 m
Tailwater depth h_u = 1.420 m
Submergence ratio ε = 1.132 (target: 1.05-1.15)
k-factor (Fr1=6.2) k = 6.13
Stilling basin length l_T = 9.04 m
Scour protection length l_K = 31.64 m
Summary of the Hydraulic Design Including the Stilling Basin¶
summary = {
"Weir_hydraulics": {
"BHQ1 (m3/s)": BHQ1,
"BHQ2 (m3/s)": BHQ2,
"h_ue_zul (m)": h_ue_zul,
"Freeboard (m)": f,
"n (weir bays)": n,
"bF (m)": b_F,
"Clear width (m)": round(b_clear, 2),
"Total width (m)": round(b_total, 2),
"hü at BHQ1 with n bays (m)": round(h_ue_n, 3),
"hü at BHQ1 with (n-1) bays (m)": round(h_ue_n1, 3),
"Head margin (n-1) case (m)": round(head_margin, 3),
"Check h_ü <= h_ü,zul": head_criterion,
"Crest height (m)": round(crest_height, 2),
},
"Stilling_basin": {
"Weir height w (m)": w_weir,
"Discharge per meter weir width (m²/s)": round(q, 3),
"hu (m)": round(h_u, 3),
"eopt (m)": round(e_opt, 3),
"h1 (m)": round(h1_opt, 4),
"h2 (m)": round(h2_opt, 3),
"Fr1": round(Fr1_opt, 2),
"epsilon": round(eps_opt, 3),
"k": round(k, 2),
"lT (m)": round(l_T, 2),
"lK (m)": round(l_K, 2),
"Energy dissipation in the stilling basin": 'OK' if (ok_Fr_opt and ok_eps_opt) else 'Not fulfilled',
},
}
pprint(summary){'Stilling_basin': {'Discharge per meter weir width (m²/s)': 1.461,
'Energy dissipation in the stilling basin': 'OK',
'Fr1': 6.18,
'Weir height w (m)': 2.5,
'eopt (m)': 0.25,
'epsilon': 1.132,
'h1 (m)': 0.1785,
'h2 (m)': 1.475,
'hu (m)': 1.42,
'k': 6.13,
'lK (m)': 31.64,
'lT (m)': 9.04},
'Weir_hydraulics': {'BHQ1 (m3/s)': 65.0,
'BHQ2 (m3/s)': 85.0,
'Check h_ü <= h_ü,zul': 'OK',
'Clear width (m)': 60.0,
'Crest height (m)': 1.6,
'Freeboard (m)': 0.5,
'Head margin (n-1) case (m)': 0.258,
'Total width (m)': 70.12,
'bF (m)': 15.0,
'h_ue_zul (m)': 1.1,
'hü at BHQ1 with (n-1) bays (m)': 0.842,
'hü at BHQ1 with n bays (m)': 0.694,
'n (weir bays)': 4}}
Limits of the Simplification¶
This notebook does not replace:
site-specific hydrology (determination of the design floods BHQ)
1d/2d water level calculations for multiple discharges
the rectangular cross-section assumption for the tailwater is weak and should be replaced by more accurate hydraulic and terrain data
verifications for multiple load and failure cases (a > 1, BHQ₂, ...)
geotechnical verifications
structural design according to the applicable rules and standards
verifications of fish passability, sediment management, and operational safety