{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Python Refresher — Lab\n", "\n", "**AI for Hydraulics · Part 1 · Pre-course**\n", "\n", "This lab reviews the Python you need for the course, on small hydraulic problems: pipe flow, rainfall records, gauging stations, flow regimes, friction losses and messy sensor data.\n", "\n", "**How to work**\n", "- Run the cells from top to bottom (`Shift + Enter`).\n", "- Replace every `...` with your code.\n", "- Each exercise ends with a ✅ **Check** giving the values you should obtain.\n", "- Use plain Python only (no NumPy): that's the point of this refresher.\n", "- Before you finish: `Runtime → Restart and run all` to make sure the notebook runs from top to bottom." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import math" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exercise 1 — Variables, arithmetic and f-strings\n", "\n", "Water flows at $Q = 0.08$ m³/s in a circular pipe of diameter $D = 0.25$ m. The kinematic viscosity of water at 20 °C is $\\nu = 1.0 \\times 10^{-6}$ m²/s.\n", "\n", "**a)** Compute the flow area $A = \\pi D^2/4$, the mean velocity $V = Q/A$ and the Reynolds number $Re = V D / \\nu$.\n", "\n", "**b)** Print them on one line with an f-string: area with 4 decimals, velocity with 3 decimals, Reynolds number with a thousands separator and no decimals, each followed by its unit." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Q = 0.08 # discharge (m³/s)\n", "D = 0.25 # diameter (m)\n", "nu = 1.0e-6 # kinematic viscosity (m²/s)\n", "\n", "A = ...\n", "V = ...\n", "Re = ...\n", "\n", "print(...)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**c)** Classify the flow with `if` / `elif` / `else`: laminar if $Re < 2300$, transitional if $2300 \\le Re < 4000$, turbulent otherwise.\n", "\n", "✅ **Check:** `A = 0.0491 m², V = 1.630 m/s, Re = 407,437`, turbulent." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "flow_type = ...\n", "\n", "print(flow_type)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exercise 2 — Lists and loops: a month of rainfall\n", "\n", "`rain` contains the daily rainfall (mm) of one month at a rain gauge; `rain[0]` is day 1." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "rain = [0.0, 0.0, 2.4, 15.8, 32.1, 6.2, 0.0, 0.0, 0.0, 0.3,\n", " 0.0, 0.0, 0.0, 0.0, 11.5, 4.8, 0.0, 0.0, 0.0, 0.0,\n", " 0.0, 0.0, 1.2, 22.7, 8.9, 0.0, 0.0, 0.0, 0.0, 0.0]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**a)** Compute the number of days, the monthly total (rounded to 1 decimal), the maximum daily rainfall and the **day number** on which it occurred (hint: `list.index`).\n", "\n", "**b)** A *rainy day* is a day with at least 1 mm. Using a list comprehension, build the list of rainy-day amounts and count them." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "n_days = ...\n", "total = ...\n", "max_rain = ...\n", "max_day = ...\n", "\n", "rainy = ...\n", "n_rainy = ..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**c)** Find the **longest dry spell**: the largest number of consecutive days with less than 1 mm. Use a `for` loop and two counters (the current spell and the longest so far).\n", "\n", "✅ **Check:** 30 days, total 105.9 mm, max 32.1 mm on day 5, 9 rainy days, longest dry spell 8 days." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "longest = 0\n", "current = 0\n", "for r in rain:\n", " ...\n", "\n", "print(longest)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exercise 3 — Dictionaries: gauging stations\n", "\n", "Each station is a dictionary; together they form a list of dictionaries (a small table). The values are illustrative." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "stations = [\n", " {\"code\": \"S01\", \"river\": \"Oued A\", \"area_km2\": 3600, \"mean_Q\": 7.2},\n", " {\"code\": \"S02\", \"river\": \"Oued B\", \"area_km2\": 850, \"mean_Q\": 2.9},\n", " {\"code\": \"S03\", \"river\": \"Oued C\", \"area_km2\": 1240, \"mean_Q\": 2.1},\n", " {\"code\": \"S04\", \"river\": \"Oued D\", \"area_km2\": 420, \"mean_Q\": 1.6},\n", "]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**a)** For each station, add a new key `\"q_Ls_km2\"`: the **specific discharge** in L/s/km² (mean discharge converted to L/s, divided by the catchment area). Print the code and the specific discharge (2 decimals) of every station.\n", "\n", "**b)** Which station has the largest specific discharge? (Hint: `max` with `key=lambda s: ...`.)\n", "\n", "**c)** What is the total catchment area of the four stations? (Use a generator expression inside `sum`.)\n", "\n", "**d)** Build a dictionary `by_code` mapping each station code to its dictionary, then use it to print the river of station `\"S03\"`.\n", "\n", "✅ **Check:** specific discharges 2.00, 3.41, 1.69, 3.81 L/s/km²; largest S04; total area 6110 km²; S03 is on Oued C." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "for s in stations:\n", " ...\n", "\n", "best = ...\n", "total_area = ...\n", "by_code = ..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exercise 4 — Functions: flow regimes along a channel\n", "\n", "**a)** Write a function `froude(V, h, g=9.81)` returning the Froude number $Fr = V / \\sqrt{g\\,h}$ of a flow of mean velocity $V$ (m/s) and depth $h$ (m), with a docstring.\n", "\n", "**b)** Write a function `regime(Fr, tol=0.02)` returning `\"subcritical\"` if $Fr < 1 - tol$, `\"supercritical\"` if $Fr > 1 + tol$, and `\"critical\"` otherwise." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def froude(V, h, g=9.81):\n", " ...\n", "\n", "\n", "def regime(Fr, tol=0.02):\n", " ..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**c)** `sections` lists measurements along a spillway as tuples `(name, V, h)`. Loop over it, unpacking each tuple, and print a line per section: name, Froude number (3 decimals) and regime.\n", "\n", "✅ **Check:** upstream 0.286 subcritical · weir crest 0.995 critical · chute 3.281 supercritical · stilling basin 0.182 subcritical." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sections = [\n", " (\"upstream\", 1.2, 1.8),\n", " (\"weir crest\", 2.7, 0.75),\n", " (\"chute\", 6.5, 0.4),\n", " (\"stilling basin\", 0.9, 2.5),\n", "]\n", "\n", "for name, V_s, h_s in sections: # V_s, h_s: keep V from Exercise 1 for Exercise 5\n", " ..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exercise 5 — Iteration: friction losses in a pipe\n", "\n", "We return to the pipe of Exercise 1 (reuse `D`, `V`, `Re`). It is a commercial steel pipe of length $L = 850$ m with absolute roughness $\\varepsilon = 0.15$ mm.\n", "\n", "**a)** Write `colebrook(Re, rel_rough, f0=0.02, tol=1e-8, max_iter=100)` solving the Colebrook–White equation by fixed-point iteration. It returns the friction factor **and** the number of iterations, and raises a `RuntimeError` if it does not converge.\n", "\n", "$$\\frac{1}{\\sqrt{f}} = -2 \\log_{10}\\!\\left( \\frac{\\varepsilon / D}{3.7} + \\frac{2.51}{Re\\,\\sqrt{f}} \\right)$$\n", "\n", "Rearranged for iteration: $f_{new} = \\left[-2 \\log_{10}\\!\\left( \\dfrac{\\varepsilon / D}{3.7} + \\dfrac{2.51}{Re\\,\\sqrt{f}} \\right)\\right]^{-2}$" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def colebrook(Re, rel_rough, f0=0.02, tol=1e-8, max_iter=100):\n", " ..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**b)** Compute the friction factor of the pipe (careful: $\\varepsilon$ is given in **mm**).\n", "\n", "**c)** Compare it with the explicit **Swamee–Jain** approximation and give the relative difference in %:\n", "\n", "$$f_{SJ} = \\frac{0.25}{\\left[\\log_{10}\\!\\left(\\dfrac{\\varepsilon/D}{3.7} + \\dfrac{5.74}{Re^{0.9}}\\right)\\right]^2}$$\n", "\n", "**d)** Compute the head loss with the Darcy–Weisbach equation $h_f = f \\, \\dfrac{L}{D} \\, \\dfrac{V^2}{2g}$.\n", "\n", "✅ **Check:** $f$ ≈ 0.01843 after 5 iterations; Swamee–Jain ≈ 0.01855 (≈ 0.66 % higher); $h_f$ ≈ 8.48 m." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "L = 850.0 # pipe length (m)\n", "eps_mm = 0.15 # absolute roughness (mm)\n", "g = 9.81\n", "\n", "rel_rough = ...\n", "f, n_iter = ...\n", "\n", "f_sj = ...\n", "diff_pct = ...\n", "\n", "h_f = ..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exercise 6 — Errors: cleaning sensor readings\n", "\n", "A water-level logger exported its readings as **text**. Some are invalid: missing (`\"n/a\"`, `\"\"`), error codes (`\"ERR\"`), or physically impossible for this station (outside 0–5 m).\n", "\n", "**a)** Write `parse_level(text, h_min=0.0, h_max=5.0)` that converts `text` to a float and **raises a `ValueError`** if the value is outside `[h_min, h_max]`. (Note that `float(\"n/a\")` already raises a `ValueError` by itself.)\n", "\n", "**b)** Loop over `raw`, using `try` / `except ValueError` to put valid levels in `valid` and rejected strings in `rejected`. Print the rejected readings and the mean of the valid ones (3 decimals).\n", "\n", "✅ **Check:** 7 valid readings; rejected `['n/a', '', '-1.0', 'ERR', '12.4']`; mean ≈ 2.473 m." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "raw = [\"2.31\", \"2.35\", \"n/a\", \"2.42\", \"\", \"2.58\", \"-1.0\",\n", " \"2.61\", \"ERR\", \"2.55\", \"12.4\", \"2.49\"]\n", "\n", "\n", "def parse_level(text, h_min=0.0, h_max=5.0):\n", " ...\n", "\n", "\n", "valid = []\n", "rejected = []\n", "for r in raw:\n", " ..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exercise 7 — Files: saving the clean data\n", "\n", "**a)** Write the valid readings to a file `levels_clean.txt`, one line per reading in the form `index,level` (index starting at 1). Use a `with` block.\n", "\n", "**b)** Read the file back, split each line on the comma, convert the levels to floats, and print the number of lines and the maximum level.\n", "\n", "✅ **Check:** 7 lines, maximum 2.61 m." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "with open(\"levels_clean.txt\", \"w\") as fh:\n", " ...\n", "\n", "with open(\"levels_clean.txt\") as fh:\n", " ..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## You're ready for Week 1\n", "\n", "If you completed these exercises without difficulty, you're ready for NumPy. If some parts were hard, re-read the corresponding sections of the lecture and the [official Python tutorial](https://docs.python.org/3/tutorial/) before the first session." ] } ], "metadata": { "colab": { "provenance": [] }, "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 5 }