For interactive reading and executing code blocks , or install Python and JupyterLab locally to run hydraulic-jump.ipynb on your own machine.
Le barrage de Clyde, sur la rivière Clutha / Mata-Au dans le centre d’Otago, est le plus grand barrage de gravité en béton de Nouvelle-Zélande: 490 m de large, jusqu’à 60 m de haut au-dessus du vieux lit de la rivière, et la retenue du lac Dunstan. Son déversoir fermé a quatre portes radiales, chacune de 15 m de haut et 10 m de large, conçues pour parcourir un débit de 3200 m/s (0,2% AEP) à un niveau de lac de 195,1 m. L’eau passant par le déversoir descend une goulotte sur la face du barrage. Le débit d’énergie élevée à l’orteil de ce parachute doit être dissipé dans un bassin stilling, de sorte que le lit de la rivière en aval ne soit pas escarpé.
Défi de l’élève : tailler la géométrie du bassin pour que le saut hydraulique se forme à l’intérieur du bassin et y soit conservé, et éviter ainsi l’affouillement en aval du barrage.
Structure du modèle de calcul¶
Aperçu général¶
Qui fait le travail C’est le cas. *** L’écoulement arrivant au bassin : décharge unitaire, tête d’énergie à l’orteil et profondeur de l’eau de queue. II= Conception d’un bassin de retenue et d’un saut hydraulique=** étudiant - le flux de travail est donné, les équations de saut hydraulique doivent être entrées=
Détails¶
La partie I établit les quantités sur lesquelles le saut, et donc le bassin, est conçu: la décharge et la décharge de l’unité , la tête d’énergie au-dessus du bassin un-sunk inversé, et la profondeur de l’eau de queue obtenue de l’équation Manning.
La partie II contient cinq fonctions courtes, marquées Task 1 à Task 5. Chacun est une seule équation de la conférence. Les chiffres qui les entourent, c’est-à-dire la recherche de racine, la recherche sur la profondeur du bassin et les placettes, sont donnés, de sorte qu’une équation incorrecte produit un nombre incorrect plutôt qu’une trace de retour. L’équation au centre de l’exercice est l’équation de profondeur séquentielle (Bélanger) de la tâche 3; les quatre autres établissent l’état auquel elle est appliquée et les dimensions qui en découlent. Une fois la partie II remplie, vous devriez pouvoir :
exprimer l’énergie, la continuité et les relations d’élan qui fixent un saut hydraulique comme code;
déterminer si un niveau d’eau de queue donné conserve un saut dans un bassin ou le balaye en aval;
déterminer la profondeur, la longueur et la protection d’un bassin contre ces relations.
Notation¶
Les profondeurs sont indiquées et les têtes , dans la conférence et ici aussi. Chanson indique les mêmes profondeurs , et . Le taux de rétention est indiqué plutôt que le de l’exemple travaillé allemand à partir duquel ce workflow est adapté, car indique le taux de dissipation turbulente dans la conférence.
| symbol | meaning | unit |
|---|---|---|
| spillway design flow | m/s | |
| width of the stilling basin | m | |
| tailwater river width | m | |
| discharge per unit width, | m/s | |
| energy head above the un-sunk basin invert | m | |
| depth of the basin floor below the downstream bed | m | |
| energy head above the basin floor, | m | |
| , , | depth, velocity and Froude number at the basin entrance (supercritical) | m, m/s, -- |
| conjugate (sequent) depth of the jump | m | |
| tailwater depth in the downstream reach | m | |
| Manning roughness coefficient of the downstream reach | s/m | |
| retention ratio, | -- | |
| longitudinal bed slope of the downstream reach | -- | |
| head loss across the jump | m | |
| Peterka basin-length multiplier, read from | -- | |
| , | basin length and scour-protection length | m |
La cellule suivante importe les paquets Python et les couleurs utilisées pour les chiffres. Ces cellules servent au fonctionnement du modèle de calcul et ne contiennent aucun contenu d’apprentissage.
import math
from dataclasses import dataclass
import matplotlib.pyplot as plt
import numpy as np
from scipy.optimize import brentq
# Physical constants used throughout.
G = 9.81 # gravitational acceleration [m/s2]
# The lecture palette, so that figures and slides read as one piece.
NAVY, BLUE, CYAN, GREEN, WARN, GREY = (
"#0C0C48", "#00467F", "#00CAEF", "#167D61", "#C74B2A", "#4A4A4C",
)
plt.rcParams.update({
"figure.figsize": (8.4, 4.2),
"figure.dpi": 110,
"axes.spines.top": False,
"axes.spines.right": False,
"axes.titleweight": "bold",
"axes.labelcolor": NAVY,
"axes.edgecolor": NAVY,
"text.color": NAVY,
"xtick.color": NAVY,
"ytick.color": NAVY,
"font.size": 9,
})Part I: boundary conditions for the hydraulic jump design¶
Everything in this part is given. Run the cells and note the quantities passed on to Part II. No hydraulic-jump calculation is performed here.
Note: only the discharge, the associated lake level and the gate dimensions are published figures; the remainder are stated assumptions. The real structure is not this simple. A gate-controlled sluice rated at 1500 m/s adjoins the spillway, and the two together pass the largest anticipated flood of 6820 m/s. The energy dissipation at the toe was developed on physical models by the Ministry of Works and Development rather than from a closed-form calculation. This exercise therefore represents the first step that engineers typically complete before a detailed design study with physical or numerical models; it is not a substitute for such a study.
| quantity | value | remark |
|---|---|---|
| , spillway design flow | 3200 m/s | published, at a lake level of 195.1 m |
| , width of the chute and basin | 40 m | four 10 m gate bays, idealised as one rectangular chute with the piers ignored |
| , energy head above the un-sunk basin invert | 50.00 m | assumed: the design flood level stands approximately 60 m above the riverbed, of which some 10 m is taken as lost down the chute |
| , , of the Clutha downstream of the dam | 60 m, 0.0357 s/m, 0.0006 | assumed |
In New Zealand practice, catchment design floods are quoted as an annual exceedance probability, so the 100-year flood is the 1% AEP event and the 500-year flood quoted for Clyde is the 0.2% AEP event; the older average recurrence interval (ARI) denotes the same thing. A dam spillway, however, is not designed to a catchment AEP at all: under the NZSOLD New Zealand Dam Safety Guidelines the inflow design flood follows from the dam’s Potential Impact Classification, and for a high-consequence dam it extends to the probable maximum flood.
The following cell defines these hydrological, hydraulic and geometric boundary conditions.
# --- the design discharge ----------------------------------------------------
Q_design = 3200.0 # m3/s Clyde Dam spillway design flow at lake level 195.1 m
# (published; the 500-year, 0.2% AEP flood)
# --- the chute toe and the stilling basin below it ---------------------------
b_chute = 40.0 # m four 10 m gate bays, idealised as one rectangle
H0 = 50.00 # m energy head above the un-sunk basin invert (assumed)
# --- the Clutha River below the dam (assumed) --------------------------------
n_manning = 0.0357 # s/m^(1/3) Manning roughness of the downstream reach
slope = 6.0e-4 # - bed slope of the downstream reach
b_tw = 60.0 # m width of the downstream reach (rectangular idealisation)
print(f"spillway design flow Q = {Q_design:7.1f} m3/s")
print(f"chute and basin width b = {b_chute:7.2f} m")
print(f"head at the invert H0 = {H0:7.2f} m")spillway design flow Q = 3200.0 m3/s
chute and basin width b = 40.00 m
head at the invert H0 = 50.00 m
Calcul des limites¶
Décharge unitaire¶
Pour le calcul du saut hydraulique, le débit par mètre de largeur est nécessaire:
Le parachute délivre ce flux supercritiquement, qui est la condition pour un saut pour se former. La profondeur critique imprimée ci-dessous est la limite supérieure sur la profondeur d’entrée , et la partie II l’utilise comme support lors de la résolution de l’équation d’énergie.
q_unit = Q_design / b_chute
h_crit = (q_unit**2 / G)**(1 / 3)
print(f"unit discharge q = Q / b = {q_unit:7.3f} m2/s")
print(f"critical depth h_c = {h_crit:7.3f} m")
print(f"critical velocity = {q_unit / h_crit:7.3f} m/s")unit discharge q = Q / b = 80.000 m2/s
critical depth h_c = 8.673 m
critical velocity = 9.224 m/s
Profondeur de l’eau de queue ¶
La profondeur dans la portée en aval détermine si le saut est retenu ou balayé, et il est déterminé par la portée elle-même plutôt que par le déversoir. Pour un canal rectangulaire en flux uniforme, l’équation Manning donne
Ceci est résolu pour numériquement.
Note: Les canaux naturels ne sont pas rectangulaires, et entre directement le taux de rétention. En pratique, la courbe d’évaluation des eaux de queue est obtenue à partir d’un gabarit, d’un modèle numérique ou de données sur le terrain, et une gamme de niveaux d’eau de queue est testée plutôt qu’une seule valeur.
La cellule ci-dessous imprime également la profondeur critique de la portée en aval, car un saut peut se terminer dans l’eau de queue seulement alors que cette portée est sous-critique.
def manning(h, b, n, S0):
'''Discharge of a rectangular channel in uniform flow [m3/s].'''
area = b * h
hydraulic_radius = (b * h) / (b + 2 * h)
return (1 / n) * area * hydraulic_radius**(2 / 3) * math.sqrt(S0)
h_tw = brentq(lambda h: manning(h, b_tw, n_manning, slope) - Q_design, 0.01, 40.0)
h_crit_tw = ((Q_design / b_tw)**2 / G)**(1 / 3)
print(f"tailwater depth h_tw = {h_tw:.3f} m")
print(f"check: Q(h_tw) = {manning(h_tw, b_tw, n_manning, slope):.1f} m3/s"
f" (target {Q_design:.0f} m3/s)")
print(f"critical depth of the reach = {h_crit_tw:.3f} m"
f" -> {'subcritical' if h_tw > h_crit_tw else 'SUPERCRITICAL'}")tailwater depth h_tw = 16.193 m
check: Q(h_tw) = 3200.0 m3/s (target 3200 m3/s)
critical depth of the reach = 6.619 m -> subcritical
Définir les structures de données et les valeurs par défaut¶
Le bloc de code suivant construit les structures de données requises et définit les valeurs par défaut pour la partie principale II.
@dataclass(frozen=True)
class DesignData:
Q: float # spillway design flow [m3/s]
q: float # discharge per unit width [m2/s]
H0: float # energy head above the un-sunk basin invert [m]
h_tw: float # tailwater depth [m]
b: float # width the basin has to cover [m]
DESIGN = DesignData(
Q=Q_design,
q=q_unit,
H0=H0,
h_tw=h_tw,
b=b_chute,
)
print(f"Q = {DESIGN.Q:8.1f} m3/s spillway design flow")
print(f"q = {DESIGN.q:8.3f} m2/s unit discharge")
print(f"H0 = {DESIGN.H0:8.2f} m energy head above the un-sunk invert")
print(f"h_tw = {DESIGN.h_tw:8.3f} m tailwater depth")
print(f"b = {DESIGN.b:8.2f} m width the basin has to cover")Q = 3200.0 m3/s spillway design flow
q = 80.000 m2/s unit discharge
H0 = 50.00 m energy head above the un-sunk invert
h_tw = 16.193 m tailwater depth
b = 40.00 m width the basin has to cover
Part II: Stilling basin & hydraulic jump design¶
The sequent depth (Bélanger) equation¶
For a horizontal, rectangular channel, the position and size of the jump follow from momentum conservation, the hydrostatic pressure forces and continuity. Given that the specific force is equal upstream (1) and downstream (2) of the jump, that is, with , the depth ratio is calculated by the sequent-depth equation of Bélanger:
Notably, and are the sequent (conjugate) depths. Every subsequent quantity in this exercise is obtained from : the retention ratio that indicates whether the jump stays in the basin, the basin length, and the required length of downstream scour protection.
Design problem¶
The position of the jump is not fixed by the spillway. It is controlled by the tailwater depth: a jump forms where the downstream depth matches the conjugate depth belonging to the incoming supercritical state. Where the tailwater is too shallow the jump is swept out onto unprotected downstream riverbed; where it is too deep the jump is drowned and dissipates less.
The single design freedom is the basin depth , by which the basin floor is set below the downstream riverbed. Increasing acts in two opposing directions:
it increases the energy head above the floor, so the entry flow is faster and shallower, which raises and hence ;
it increases the depth available over the floor, namely .
An acceptable design is a basin depth at which both acceptance windows are satisfied simultaneously:
The first acceptance window is the steady jump that leads to 45% to 70% head loss, and no oscillating surge to fatigue the structure. The second acceptance window requires the basin to hold slightly more water than the jump needs, so that the jump is retained with a small margin without being drowned.
The workflow¶
| step | content | hint |
|---|---|---|
| A | choose a trial basin depth | here: 2.0 m (given) |
| B | and from the energy equation at the entrance | Task 1 + given solver |
| C | Task 2 | |
| D | is ? if not, adjust | given |
| E | tailwater depth | given (Part I) |
| F | conjugate depth from the Bélanger equation | Task 3 |
| G | is ? if not, adjust | Task 4 + given |
| + | basin length and scour-protection length | Task 5 |
| + | head loss | given |
The five tasks¶
Each task is one equation from the lecture.
Replace the raise NotImplementedError(...) line with a return statement.
The function names and their arguments must not be changed, because the workflow below calls them.
Important: On completing each task, run the self-check cell. It marks each function
[OK]or[XX]against the lecture values.
Tâche 1: tête d’énergie au-dessus du plancher du bassin¶
Négligeant la vitesse d’approche, la tête d’énergie disponible au-dessus du plancher du bassin est . À l’entrée du bassin, c’est-à-dire la section 1, la tête est divisée entre la profondeur et la tête de vitesse. Avec continuité pour une section rectangulaire,
Mettre en œuvre le côté droit en fonction de et . Le solveur donné entry_depth ci-dessous sélectionne la racine superficielle et supercritique de energy_head(h, q) == H, qui est la branche livrée par le parachute.
def energy_head(h, q, g=G):
'''
Task 1: specific energy head of a rectangular section [m].
Parameters
----------
h : flow depth [m]
q : discharge per unit width [m2/s]
Returns
-------
the energy head h + q^2 / (2 g h^2) [m]
'''
# >>> YOUR CODE HERE
raise NotImplementedError("Task 1: return the specific energy head")Tâche 2: Numéro de bord¶
Le numéro Froude compare la vitesse d’écoulement avec la vitesse des vagues d’eau peu profonde :
Rappelons que indique un flux supercritique et subcritic. Donc l’étape D du workflow ne calcule que la valeur du numéro Froude (ici : ) pour la comparaison ultérieure avec la fenêtre de saut stationnaire :
def froude_number(v, h, g=G):
'''
Task 2: Froude number of a rectangular section [-].
Parameters
----------
v : depth-averaged velocity [m/s]
h : flow depth [m]
'''
# >>> YOUR CODE HERE
raise NotImplementedError("Task 2: return the Froude number")Tâche 3: équation de profondeur séquentielle (Bélanger)¶
C’est l’équation centrale de l’exercice. Mettre en oeuvre le ratio de profondeur aux sections 2 () et 1 ():
Note: Cette équation ne décrit ni le rouleau entre les sections transversales ni la structure interne du saut. Tout ce que la conception exige après ce point, c’est-à-dire le taux de rétention, la longueur du bassin et la longueur de protection de l’affouillement, suit l’équation séquent.
def conjugate_depth(h1, Fr1):
'''
Task 3: conjugate (sequent) depth downstream of the jump [m].
Parameters
----------
h1 : supercritical depth upstream of the jump [m]
Fr1 : Froude number at section 1 [-]
'''
# >>> YOUR CODE HERE
raise NotImplementedError("Task 3: return the conjugate depth h2")Tâche 4: taux de rétention¶
La profondeur de l’eau sur le plancher du bassin est la profondeur de l’eau de queue plus la profondeur du bassin. La comparaison avec la profondeur que le saut exige conduit au taux de rétention :
signifie que le bassin ne peut fournir la profondeur conjuguée et le saut est balayé en aval; bien au-dessus de 1 signifie que le saut est noyé. Étape G demande .
def retention_ratio(h_tw, d_b, h2):
'''
Task 4: retention ratio of the basin [-].
Parameters
----------
h_tw : tailwater depth in the downstream reach [m]
d_b : depth of the basin floor below the downstream bed [m]
h2 : conjugate depth required by the jump [m]
'''
# >>> YOUR CODE HERE
raise NotImplementedError("Task 4: return the retention ratio")Tâche 5 : longueurs du bassin et de la protection contre l’affouillement¶
Pour calculer empiriquement la longueur requise du bassin stalinien , Peterka (USBR Engineering Monographie 25) a mesuré la longueur du saut dans six flumes d’essai et l’a tracée contre le numéro Froude comme un compteur prêt (sa figure 7, saut libre sur un tablier horizontal). La courbe donne un facteur (ici: ), qui est lu au numéro Froude de la section transversale 1 () et multiplié par la profondeur conjuguée ():
| 2.4 | 4 | 5 | 6 to 11 | 14 | |
|---|---|---|---|---|---|
| 4.8 | 5.8 | 6.0 | 6.13 | 6.0 |
La fonction peterka_f ci-dessous lit ce tableau (aucune intervention nécessaire) et retourne . Il ne correspond à rien : np.interp interpole linéairement entre les points tabulés d’une courbe mesurée, et cette courbe n’est pas une ligne droite.
def peterka_f(Fr1):
'''Given: basin-length multiplier after Peterka (USBR EM 25), interpolated [-].'''
knots_Fr = [2.4, 4.0, 5.0, 6.0, 11.0, 14.0]
knots_f = [4.8, 5.8, 6.0, 6.13, 6.13, 6.0]
return float(np.interp(Fr1, knots_Fr, knots_f))
print(f"f_l at Fr1 = 5.0 : {peterka_f(5.0):.3f}")
print(f"f_l at Fr1 = 8.0 : {peterka_f(8.0):.3f}")f_l at Fr1 = 5.0 : 6.000
f_l at Fr1 = 8.0 : 6.130
Ensuite, calculez la longueur de bassin requise pour contenir le saut avec la fonction peterka_f. Comme le débit qui quitte le saut (et le seuil de l’extrémité du bassin) est encore turbulent, le lit de la rivière immédiatement en aval du bassin doit être blindé sur une longueur :
Tâche 5 : utiliser peterka_f pour le multiplicateur et retourner et .
def basin_lengths(h2, Fr1):
'''
Task 5: basin length and scour-protection length [m].
Parameters
----------
h2 : conjugate depth downstream of the jump [m]
Fr1 : Froude number at section 1, which selects the multiplier [-]
Returns
-------
(l_b, l_s) : basin length and scour-protection length [m]
'''
# >>> YOUR CODE HERE
raise NotImplementedError("Task 5: return the basin and scour lengths")Note: Pour un saut régulier, est proche de la règle du pouce 6 , et les cellules dans ce cahier impriment à la fois et . Pourtant, d’autres manuels de conception donneraient différents facteurs, ce qui est l’une des raisons pour lesquelles une conception détaillée avec un modèle physique ou numérique est critique.
Autocontrôle¶
Exécutez cette cellule après chaque tâche. Les valeurs de référence sont les résultats approximatifs présentés dans les diapositives de conférence, donc [OK] sur chaque ligne indique que les cinq tâches reproduisent les diapositives.
CHECKS = [
("Task 1 energy_head(2.4061, 80.000)",
lambda: energy_head(2.4061, 80.000), (58.751,), "m"),
("Task 2 froude_number(33.249, 2.4061)",
lambda: froude_number(33.249, 2.4061), (6.844,), "-"),
("Task 3 conjugate_depth(2.4061, 6.844)",
lambda: conjugate_depth(2.4061, 6.844), (22.116,), "m"),
("Task 4 retention_ratio(16.193, 8.75, 22.115)",
lambda: retention_ratio(16.193, 8.75, 22.115), (1.128,), "-"),
("Task 5 basin_lengths(22.115, 6.844)",
lambda: basin_lengths(22.115, 6.844), (135.565, 474.477), "m"),
]
def check_tasks(tolerance=5e-3, verbose=True):
'''Compare each task against the worked values from the lecture.'''
passed = 0
for label, call, expected, unit in CHECKS:
try:
value = call()
except NotImplementedError:
if verbose:
print(f"[ ] {label:<46} not implemented yet")
continue
got = value if isinstance(value, tuple) else (value,)
ok = (len(got) == len(expected) and
all(abs(g - e) <= tolerance * max(1.0, abs(e))
for g, e in zip(got, expected)))
passed += ok
if verbose:
mark = "OK" if ok else "XX"
shown = ", ".join(f"{g:.3f}" for g in got)
wanted = ", ".join(f"{e:.3f}" for e in expected)
print(f"[{mark}] {label:<46} {shown:>19} {unit:<3}"
f" (expected {wanted})")
if verbose:
print("-" * 78)
print(f"{passed} of {len(CHECKS)} tasks correct.")
return passed == len(CHECKS)
TASKS_DONE = check_tasks()[ ] Task 1 energy_head(2.4061, 80.000) not implemented yet
[ ] Task 2 froude_number(33.249, 2.4061) not implemented yet
[ ] Task 3 conjugate_depth(2.4061, 6.844) not implemented yet
[ ] Task 4 retention_ratio(16.193, 8.75, 22.115) not implemented yet
[ ] Task 5 basin_lengths(22.115, 6.844) not implemented yet
------------------------------------------------------------------------------
0 of 5 tasks correct.
Mise en œuvre du flux de travail : la recherche sur la profondeur du bassin¶
Les autres calculs de la partie II sont donnés (les réponses aux questions de suivi ne le sont pas).
La fonction basin_state évalue les étapes B, C, F et G pour une profondeur de bassin d’essai et appelle les fonctions écrites ci-dessus; search_basin_depth effectue les étapes A et D, ajustant par bisection jusqu’à ce que les deux fenêtres d’acceptation “ferment”. La logique de bisection doit être lue avant l’exécution de la cellule.
et augmentent avec , ce qui permet d’utiliser un seul support pour les deux tests: trop peu profond un bassin ne fournit pas la profondeur demande le saut et balaye en aval, mais trop profond un bassin fournit plus que le saut exige et noie. Chaque procès échoué indique donc la moitié de la fourchette à conserver et, en l’espèce, la recherche échoue dans les deux sens avant la convergence.
Observer: Le processus de recherche commence par un premier essai de = 2,0 m. Ce procès satisfait l’étape D, = 6,2 étant déjà un saut régulier, et échoue l’étape G, le saut étant balayé. La bisection approfondit ensuite le bassin, déborde dans un saut noyé, revient trop loin, et converge sur la séquence 2.0, 11.0, 6.5, 8.75 m. Il se termine à la première profondeur du bassin satisfaisant aux deux fenêtres d’acceptation, de sorte que le résultat dépend de la valeur de départ et de la largeur du support; un rapport doit indiquer quel support l’a produit.
FR_WINDOW = (4.5, 9.0) # steady-jump range, step D
RT_WINDOW = (1.05, 1.15) # retention window, step G
@dataclass(frozen=True)
class BasinState:
d_b: float # trial basin depth [m]
H: float # energy head above the basin floor [m]
h1: float # supercritical entry depth [m]
v1: float # entry velocity [m/s]
Fr1: float # entry Froude number [-]
h2: float # conjugate depth [m]
Rt: float # retention ratio [-]
@property
def fr_ok(self):
return FR_WINDOW[0] <= self.Fr1 < FR_WINDOW[1]
@property
def rt_ok(self):
return RT_WINDOW[0] <= self.Rt <= RT_WINDOW[1]
def critical_depth(q, g=G):
'''Critical depth of a rectangular section [m]; the bracket for step B.'''
return (q**2 / g)**(1 / 3)
def entry_depth(H, q):
'''
Step B: the shallow, supercritical root of energy_head(h, q) = H [m].
Both roots satisfy the energy equation. The supercritical one lies below the
critical depth, and it is the branch the spillway delivers, so the bracket is
closed at the critical depth.
'''
return brentq(lambda h: energy_head(h, q) - H, 1e-4, critical_depth(q))
def basin_state(d_b, design=None):
'''Steps B, C, F and G of the workflow for one trial basin depth d_b.'''
design = design or DESIGN
H = design.H0 + d_b # step B: head above the floor
h1 = entry_depth(H, design.q) # step B: supercritical root
v1 = design.q / h1 # continuity
Fr1 = froude_number(v1, h1) # step C
h2 = conjugate_depth(h1, Fr1) # step F
Rt = retention_ratio(design.h_tw, d_b, h2) # step G
return BasinState(d_b=d_b, H=H, h1=h1, v1=v1, Fr1=Fr1, h2=h2, Rt=Rt)def search_basin_depth(db_start=2.0, db_min=0.0, db_max=20.0, max_iter=15, verbose=True):
'''
Steps A and D: bisection on the basin depth d_b until both windows close.
Returns the accepted BasinState and the full iteration history.
'''
d_b = db_start
history = []
if verbose:
print(f"{'iter':>4}{'d_b [m]':>9}{'Fr1':>8}{'h1 [m]':>9}{'h2 [m]':>9}"
f"{'Rt':>8} verdict")
print("-" * 66)
for i in range(1, max_iter + 1):
state = basin_state(d_b)
history.append(state)
if verbose:
flags = []
if not state.fr_ok:
flags.append("Fr1 low" if state.Fr1 < FR_WINDOW[0] else "Fr1 high")
if not state.rt_ok:
flags.append("swept out" if state.Rt < RT_WINDOW[0] else "drowned")
print(f"{i:>4}{state.d_b:>9.3f}{state.Fr1:>8.2f}{state.h1:>9.4f}"
f"{state.h2:>9.4f}{state.Rt:>8.3f} "
f"{'accepted' if not flags else ', '.join(flags)}")
if state.fr_ok and state.rt_ok:
return state, history
# A deeper basin raises H, so the entry flow is shallower and faster: Fr1 and Rt increase monotonically with d_b. One bracket therefore serves both criteria, and each verdict says which way to move: a jump that is too gentle or swept out wants a deeper basin, one that is too fierce or drowned wants a shallower one.
if not state.fr_ok:
if state.Fr1 < FR_WINDOW[0]:
db_min = d_b # jump too gentle: deepen the basin
else:
db_max = d_b # jump too fierce: raise the floor
elif state.Rt > RT_WINDOW[1]:
db_max = d_b # drowned: shallower basin
else:
db_min = d_b # swept out: deeper basin
d_b = 0.5 * (db_min + db_max)
raise RuntimeError("no acceptable basin depth found in the search range")
if TASKS_DONE:
DESIGN_STATE, HISTORY = search_basin_depth()
print(f"\naccepted basin depth d_b = {DESIGN_STATE.d_b:.3f} m")
else:
DESIGN_STATE, HISTORY = None, []
print("Complete Tasks 1 to 5, then re-run this cell.")Complete Tasks 1 to 5, then re-run this cell.
Perte de la tête à travers le saut¶
La perte de tête quantifie le travail du bassin. Ce n’est pas une tâche: la décision de conception a déjà été prise par les deux fenêtres d’acceptation ci-dessus. La perte est calculée ici parce que c’est la raison pour laquelle le bassin existe. La combinaison de l’équation d’énergie avec la relation conjuguée-profondeur sur un lit horizontal donne la perte de tête sous forme fermée,
est une perte de tête plutôt que d’énergie: elle représente la conversion irréversible de l’énergie mécanique à flux moyen en turbulence et finalement en énergie interne, l’énergie totale étant conservée tout au long.
def head_loss(h1, h2):
'''Given: head loss across the jump [m].'''
return (h2 - h1)**3 / (4 * h1 * h2)
if TASKS_DONE:
delta_H = head_loss(DESIGN_STATE.h1, DESIGN_STATE.h2)
H1 = energy_head(DESIGN_STATE.h1, DESIGN.q)
print(f"head loss dH = {delta_H:8.3f} m")
print(f"energy head at 1 H1 = {H1:8.3f} m")
print(f"relative loss dH / H1 = {delta_H / H1:8.1%}")
else:
delta_H = H1 = None
print("Complete Tasks 1 to 4, then re-run this cell.")Complete Tasks 1 to 4, then re-run this cell.
Longueur du bassin et protection contre l’affouillement¶
Tâche 5, évaluée à la conception acceptée, parallèlement à la règle du pouce ( 6 ) contre laquelle la longueur du bassin peut être vérifiée.
if TASKS_DONE:
f_peterka = peterka_f(DESIGN_STATE.Fr1)
l_b, l_s = basin_lengths(DESIGN_STATE.h2, DESIGN_STATE.Fr1)
print(f"Peterka multiplier f_l = {f_peterka:7.2f} (at Fr1 = {DESIGN_STATE.Fr1:.2f})")
print(f"basin length l_b = {l_b:7.2f} m <- Task 5")
print(f"rule of thumb 6 h_2 = {6 * DESIGN_STATE.h2:7.2f} m"
f" ({abs(6 * DESIGN_STATE.h2 / l_b - 1):.1%} from l_b)")
print(f"scour protection l_s = {l_s:7.2f} m <- Task 5")
else:
f_peterka = l_b = l_s = None
print("Complete Tasks 1 to 5, then re-run this cell.")Complete Tasks 1 to 5, then re-run this cell.
Évaluation des résultats¶
Vérification de la conception de la fenêtre¶
Les deux fenêtres d’acceptation sont ici évaluées graphiquement en fonction de la profondeur du bassin . Les bandes ombragées dans les parcelles sont les fenêtres et la ligne verticale en tirets est la conception acceptée. Un seul critère est déterminant : reste à l’intérieur de sa fenêtre à travers tous les tests, mais entre dans sa fenêtre d’acceptation et la laisse à nouveau à environ 2,5 m de profondeur du bassin, entre 6,79 m et 9,31 m. Le critère de rétention choisit donc , et c’est le critère qui dépend de la profondeur d’eau de queue donnée .
if TASKS_DONE:
db_range = np.linspace(0.2, 20.0, 240)
states = [basin_state(float(x)) for x in db_range]
Fr_curve = np.array([s.Fr1 for s in states])
Rt_curve = np.array([s.Rt for s in states])
fig, axes = plt.subplots(1, 2, figsize=(9.4, 3.6), sharex=True)
axes[0].axhspan(*FR_WINDOW, color=CYAN, alpha=0.18, lw=0)
axes[0].plot(db_range, Fr_curve, color=BLUE, lw=2.2)
axes[0].set_ylabel("$Fr_1$ at the basin entrance [-]")
axes[0].set_title("Step D: steady-jump window")
axes[0].set_ylim(4.3, 9.3)
axes[0].annotate("steady jump\n$4.5 \\leq Fr_1 < 9$", (0.8, 8.4),
color=BLUE, fontsize=8.5)
axes[1].axhspan(Rt_curve.min(), RT_WINDOW[0], color=WARN, alpha=0.10, lw=0)
axes[1].axhspan(*RT_WINDOW, color=CYAN, alpha=0.18, lw=0)
axes[1].axhspan(RT_WINDOW[1], Rt_curve.max(), color=GREY, alpha=0.10, lw=0)
axes[1].axhline(1.0, color=GREY, lw=0.8, ls=":")
axes[1].plot(db_range, Rt_curve, color=BLUE, lw=2.2)
axes[1].set_ylabel("retention ratio $r_\\mathrm{t}$ [-]")
axes[1].set_title("Step G: retention window")
axes[1].set_ylim(Rt_curve.min(), Rt_curve.max())
axes[1].annotate("jump swept out", (4.2, 0.83), color=WARN, fontsize=8.5)
axes[1].annotate("retained", (0.8, 1.09), color=BLUE, fontsize=8.5)
axes[1].annotate("jump drowned", (0.8, 1.42), color=GREY, fontsize=8.5)
for ax in axes:
ax.axvline(DESIGN_STATE.d_b, color=WARN, lw=1.4, ls="--")
# the design variable is the one symbol set apart from the notation:
# bold and in the warn colour, on the slides and here alike
ax.set_xlabel("basin depth $\\mathbf{d_b}$ [m]", color=WARN)
ax.set_xlim(0.2, 20.0)
axes[0].plot([DESIGN_STATE.d_b], [DESIGN_STATE.Fr1], "o", color=WARN, ms=6, zorder=3)
axes[1].plot([DESIGN_STATE.d_b], [DESIGN_STATE.Rt], "o", color=WARN, ms=6, zorder=3)
axes[0].annotate(f"design\n$\\mathbf{{d_b}} = {DESIGN_STATE.d_b:.2f}$ m,"
f" $Fr_1 = {DESIGN_STATE.Fr1:.2f}$",
(DESIGN_STATE.d_b, DESIGN_STATE.Fr1), textcoords="offset points",
xytext=(-104, 34), color=WARN, fontsize=8.5,
arrowprops=dict(arrowstyle="-", color=WARN, lw=0.8))
axes[1].annotate(f"$r_\\mathrm{{t}} = {DESIGN_STATE.Rt:.3f}$",
(DESIGN_STATE.d_b, DESIGN_STATE.Rt), textcoords="offset points",
xytext=(40, -58), color=WARN, fontsize=8.5,
arrowprops=dict(arrowstyle="-", color=WARN, lw=0.8))
fig.tight_layout()
plt.show()
else:
print("Complete Tasks 1 to 5, then re-run this cell.")Complete Tasks 1 to 5, then re-run this cell.
Section longitudinale du bassin de sciage¶
Le modèle accepté est tracé dans le bloc de code suivant à l’échelle, avec le lit de rivière en aval comme référence. Deux profondeurs sont marquées au-dessus du plancher du bassin, et la différence entre elles est le sujet de l’étape G:
Disponible est la profondeur fournie par l’eau de queue et la profondeur du bassin, .
Requis est la profondeur requise pour le saut, c’est-à-dire la profondeur conjuguée .
Leur rapport est , et l’excédent est la marge par laquelle le saut est conservé dans le bassin plutôt que sur le lit en aval. Seules les profondeurs finales du saut suivent les relations 1d, de sorte que la surface tirée entre les sections 1 et 2 est indicative.
if TASKS_DONE:
s = DESIGN_STATE
floor = -s.d_b # basin floor, below the downstream riverbed
h_avail = DESIGN.h_tw + s.d_b # depth available over the floor
top = DESIGN.H0 # energy head above the un-sunk invert
u = l_b / 24.8 # one drawing unit, so the layout scales
x_face, x_toe, x_end = -7.0 * u, 0.0, l_b
x_tail = x_end + 0.6 * l_b
x_j0, x_j1 = 0.26 * l_b, 0.62 * l_b
fig, ax = plt.subplots(figsize=(9.4, 3.4))
# --- structure and bed ---------------------------------------------------
# the chute face descends from the spillway crest to the basin floor,
# then the floor runs to the end sill and the river bed continues downstream
ax.plot([x_face - 2.0 * u, x_face, x_face + 3.2 * u, x_toe, x_end, x_end, x_tail],
[top, top, top, floor, floor, 0.0, 0.0],
color=NAVY, lw=2.4, solid_joinstyle="round")
# --- water body ----------------------------------------------------------
xs = np.linspace(x_toe, x_tail, 500)
bed = np.where(xs <= x_end, floor, 0.0)
ramp = np.clip((xs - x_j0) / (x_j1 - x_j0), 0.0, 1.0)
surface = (floor + s.h1) + (DESIGN.h_tw - floor - s.h1) * (
0.5 - 0.5 * np.cos(math.pi * ramp))
ax.fill_between(xs, surface, bed, color=CYAN, alpha=0.30, lw=0)
ax.plot(xs, surface, color=BLUE, lw=2.0)
# --- dimensions ----------------------------------------------------------
def dim(x, y0, y1, label, colour=NAVY, dx=0.7 * u, ha="left"):
ax.annotate("", (x, y0), (x, y1),
arrowprops=dict(arrowstyle="<->", color=colour, lw=1.1))
ax.text(x + dx, 0.5 * (y0 + y1), label, color=colour, fontsize=8.2,
va="center", ha=ha)
ax.plot([0.42 * x_end, x_end], [floor + s.h2] * 2, color=WARN, lw=1.0, ls="--")
dim(0.50 * x_end, floor, floor + s.h2,
f"required\n$h_2$ = {s.h2:.2f} m", colour=WARN, dx=-0.7 * u, ha="right")
dim(0.86 * x_end, floor, DESIGN.h_tw,
f"available\n$h_{{tw}}+\\mathbf{{d_b}}$ = {h_avail:.2f} m",
dx=-0.7 * u, ha="right")
ax.annotate(f"$h_1$ = {s.h1:.2f} m",
(x_toe + 3.2 * u, floor + 0.5 * s.h1), textcoords="offset points",
xytext=(2, 54), color=NAVY, fontsize=8.2,
arrowprops=dict(arrowstyle="->", color=NAVY, lw=0.9))
dim(x_end + 2.4 * u, floor, 0.0,
f"$\\mathbf{{d_b}}$ = {s.d_b:.2f} m", colour=WARN)
dim(x_end + 9.0 * u, 0.0, DESIGN.h_tw, f"$h_{{tw}}$ = {DESIGN.h_tw:.2f} m")
ax.annotate("", (x_toe, floor - 0.365 * s.h2), (x_end, floor - 0.365 * s.h2),
arrowprops=dict(arrowstyle="<->", color=NAVY, lw=1.1))
ax.text(0.5 * x_end, floor - 0.62 * s.h2,
f"basin length $l_b$ = {l_b:.1f} m",
color=NAVY, fontsize=8.2, ha="center")
# --- labels --------------------------------------------------------------
ax.axhline(0.0, color=GREY, lw=0.7, ls=":")
ax.text(0.5 * (x_j0 + x_j1), 0.62 * top, "hydraulic jump",
color=NAVY, fontsize=9, ha="center", style="italic")
ax.annotate("", (0.5 * (x_j0 + x_j1), 0.32 * top),
(0.5 * (x_j0 + x_j1), 0.55 * top),
arrowprops=dict(arrowstyle="->", color=NAVY, lw=0.9))
ax.text(x_face - 1.6 * u, top + 0.12 * s.h2,
f"from the spillway chute, $H_0$ = {top:.2f} m",
color=NAVY, fontsize=8.2)
ax.set_xlim(x_face - 2.5 * u, x_tail + 1.0 * u)
ax.set_ylim(floor - 0.75 * s.h2, top + 0.55 * s.h2)
ax.set_aspect("equal")
ax.set_xlabel("distance along the basin [m]")
ax.set_ylabel("level above the\ndownstream bed [m]")
ax.set_title(f"Stilling basin below Clyde Dam, accepted design "
f"($r_\\mathrm{{t}}$ = {h_avail:.2f} / {s.h2:.2f} = {s.Rt:.3f})")
ax.spines["left"].set_visible(True)
fig.tight_layout()
plt.show()
else:
print("Complete Tasks 1 to 5, then re-run this cell.")Complete Tasks 1 to 5, then re-run this cell.
Résumé de la conception¶
if TASKS_DONE:
s = DESIGN_STATE
rows = [
("given", "spillway design flow", "Q", DESIGN.Q, "m3/s"),
("given", "unit discharge", "q", DESIGN.q, "m2/s"),
("given", "energy head above the invert", "H0", DESIGN.H0, "m"),
("given", "tailwater depth", "h_tw", DESIGN.h_tw, "m"),
("design", "basin depth", "d_b", s.d_b, "m"),
("design", "energy head above the floor", "H", s.H, "m"),
("design", "entry depth", "h1", s.h1, "m"),
("design", "entry velocity", "v1", s.v1, "m/s"),
("design", "entry Froude number", "Fr1", s.Fr1, "-"),
("design", "conjugate depth", "h2", s.h2, "m"),
("design", "retention ratio", "Rt", s.Rt, "-"),
("result", "head loss", "dH", delta_H, "m"),
("result", "relative head loss", "dH/H1", 100 * delta_H / H1, "%"),
("result", "basin length", "l_b", l_b, "m"),
("result", "scour protection length", "l_s", l_s, "m"),
]
print(f"{'':8}{'quantity':<34}{'symbol':<14}{'value':>10} unit")
group = None
for kind, name, symbol, value, unit in rows:
if kind != group:
print("-" * 70)
group = kind
print(f"{kind:<8}{name:<34}{symbol:<14}{value:>10.3f} {unit}")
print("=" * 70)
print(f"windows: Fr1 in [{FR_WINDOW[0]}, {FR_WINDOW[1]}) -> "
f"{'met' if s.fr_ok else 'NOT met'}; "
f"Rt in [{RT_WINDOW[0]}, {RT_WINDOW[1]}] -> "
f"{'met' if s.rt_ok else 'NOT met'}")
else:
print("Complete Tasks 1 to 5, then re-run this cell.")Complete Tasks 1 to 5, then re-run this cell.
Follow-up questions¶
Each of the following requires a single change to one input of the code written above, to explore how uncertainties and changes in the boundary conditions act on the hydraulic jump, and therefore on the stilling basin design.
What happens if the tailwater depth is lower? Recompute with the tailwater depth reduced by 2 m and everything else unchanged, that is, hand
basin_stateaDesignDatawhoseh_twis 2 m smaller. Determine the effect on and its consequence for the downstream riverbed, and identify how the jump position changes.How does the jump behave under different discharge scenarios? The basin is designed for a 500-year flood but operates mostly at much lower discharges. So re-evaluate
basin_statewith aDesignDatawhoseqis halved, and determine whether the jump remains steady and remains retained.
Limits of the simplification¶
This calculation template uses simplified 1d equations, and its results are therefore subject to considerable uncertainty, related to (but not limited to):
hydrology, that is the derivation of the design flood;
the tailwater rating curve, instead of the rectangular Manning idealisation;
load and failure scenarios, including floods exceeding the spillway design discharge, partial-gate operation, and the sluice gates beside the spillway;
cavitation, air entrainment, uplift, fluctuating pressures and fatigue of the structure;
scour and riverbed-stability assessment downstream of the protected length;
geotechnical analysis and structural design;
fish passage and sediment management.
Sources¶
Chanson, Hydraulics of Open Channel Flow, 2nd ed., Elsevier Butterworth-Heinemann, 2004.
Peterka, Hydraulic Design of Stilling Basins and Energy Dissipators, USBR Engineering Monograph 25, revised 1978. https://
ntrl .ntis .gov /NTRL /dashboard /searchResults /titleDetail /PB95139457 .xhtml Bollrich, Technische Hydromechanik, Dresden, Germany, 2000.
NZ Ministry of Works and Development, Clyde Dam: Clutha Power, 1987, https://
archive .org /details /clyde -dam -clutha -power.