{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# NumPy — Lab\n", "\n", "**AI for Hydraulics · Part 1 · Week 1**\n", "\n", "In this lab you will practise NumPy on small hydraulic problems: channel geometry, Manning's equation, rating tables, rainfall statistics and a flood hydrograph.\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", "- No data file is needed: all data is defined or generated in the notebook." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "\n", "print(np.__version__)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exercise 1 — Creating arrays\n", "\n", "**a)** Create an array `depths` of flow depths from 0.1 m to 3.0 m every 0.1 m (both included) with `np.linspace`. Print its `shape`, its `dtype`, and its first and last values." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "depths = ..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**b)** Create an array `n_values` with the **same shape** as `depths`, filled with the Manning coefficient 0.03." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "n_values = ..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**c)** Using a random generator with seed `7`, draw 10 water temperatures from a normal distribution with mean 18 °C and standard deviation 2 °C. Print them rounded to 1 decimal, and their mean.\n", "\n", "✅ **Check:** `depths` has shape `(30,)` and dtype `float64`. The temperatures start with `18. 18.6 17.5`, with mean ≈ 17.6 °C." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "rng = ...\n", "temperatures = ..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exercise 2 — Selecting data\n", "\n", "`Q` contains the mean daily discharge (m³/s) of a river over one month (day 1 is `Q[0]`). A flood occurs around day 9." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Q = np.array([12.4, 11.8, 11.1, 10.6, 10.2, 9.9, 18.7, 35.2, 41.6, 30.3,\n", " 22.1, 17.5, 14.6, 12.9, 11.7, 10.8, 10.1, 9.5, 9.0, 8.6,\n", " 8.2, 7.9, 7.5, 7.2, 6.8, 6.5, 6.1, 5.8, 5.4, 4.9, 4.6])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**a)** Print the discharge of the first week and of the last 3 days." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "first_week = ...\n", "last_3_days = ..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**b)** Using a boolean mask, print the discharges above 20 m³/s and the **day numbers** (1 to 31) on which they occur." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "flood_mask = ...\n", "flood_values = ...\n", "flood_days = ..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**c)** How many days are below the low-flow threshold of 5 m³/s?\n", "\n", "**d)** What is the mean discharge from day 10 to day 20 (both included)?\n", "\n", "✅ **Check:** flood days `[ 8 9 10 11]`, 2 low-flow days, mean ≈ 14.28 m³/s." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "n_low_flow = ...\n", "mean_10_20 = ..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exercise 3 — Uniform flow in a trapezoidal channel\n", "\n", "An irrigation canal has a trapezoidal section with bottom width $b = 8$ m, side slope $m = 1.5$, Manning coefficient $n = 0.03$ and bed slope $S = 0.0005$.\n", "\n", "$$A = (b + m\\,y)\\,y \\qquad P = b + 2\\,y\\sqrt{1+m^2} \\qquad R_h = \\frac{A}{P} \\qquad Q = \\frac{1}{n} A R_h^{2/3} S^{1/2}$$\n", "\n", "**a)** For 40 depths `y` between 0.1 m and 4.0 m, compute `A`, `P`, `R` and the discharge `Qc` **without any loop**." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "b, m, n, S, g = 8.0, 1.5, 0.03, 0.0005, 9.81\n", "y = np.linspace(0.1, 4.0, 40)\n", "\n", "A = ...\n", "P = ...\n", "R = ...\n", "Qc = ..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**b)** Compute the mean velocity $V = Q/A$ and the Froude number $Fr = V / \\sqrt{g\\,D}$, where $D = A/T$ is the hydraulic depth and $T = b + 2\\,m\\,y$ the top width. Is the flow ever supercritical ($Fr > 1$) in this range?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "V = ...\n", "T = ...\n", "D = ...\n", "Fr = ..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**c)** The canal must carry a design discharge of 50 m³/s. Since `Qc` increases with `y`, use `np.interp` to estimate the **normal depth** $y_n$.\n", "\n", "✅ **Check:** Q at 4.0 m ≈ 76.83 m³/s, max Fr ≈ 0.262 (always subcritical), $y_n$ ≈ 3.20 m." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Q_design = 50.0\n", "y_normal = ..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exercise 4 — Broadcasting: a design table\n", "\n", "We now study the same canal for 4 possible bed slopes.\n", "\n", "**a)** Using broadcasting (`np.newaxis`), build `Q_table` of shape `(40, 4)`: one row per depth `y`, one column per slope." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "slopes = np.array([0.0002, 0.0005, 0.001, 0.002])\n", "\n", "Q_table = ..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**b)** For each slope, find the **first depth** of the grid whose capacity is at least `Q_design`. Hint: `(Q_table >= Q_design).argmax(axis=0)` gives the row index of the first `True` in each column.\n", "\n", "**c)** ⚠️ Look carefully at the result for the gentlest slope (0.0002). Is it physically correct? Use `.any(axis=0)` to detect the columns where **no** depth reaches the design discharge, and replace those depths by `np.nan`.\n", "\n", "✅ **Check:** last row ≈ `[ 48.6 76.8 108.7 153.7]`. Correct first depths: `[nan 3.2 2.7 2.3]` m." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "enough = ...\n", "first_idx = ...\n", "y_first = ..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exercise 5 — Monthly rainfall statistics\n", "\n", "The next cell generates 20 years (2001–2020) of synthetic monthly rainfall (mm) with a Mediterranean pattern (wet winters, dry summers). `rain` has one row per year and one column per month." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "rng = np.random.default_rng(2026)\n", "seasonal = np.array([60, 50, 45, 35, 20, 8, 2, 4, 15, 35, 55, 65]) # typical monthly mean (mm)\n", "rain = rng.gamma(2.0, seasonal / 2.0, size=(20, 12)).round(1)\n", "\n", "years = np.arange(2001, 2021)\n", "months = np.array([\"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\",\n", " \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\"])\n", "print(rain.shape)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**a)** Compute the annual totals and the mean annual rainfall.\n", "\n", "**b)** Which year was the wettest, and which the driest? Give their totals." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "annual = ...\n", "mean_annual = ...\n", "\n", "wettest_year = ...\n", "driest_year = ..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**c)** Compute the mean rainfall of each month and the name of the wettest month on average.\n", "\n", "**d)** Compute the anomaly matrix (each value minus the mean of its month) using broadcasting. Check that the mean anomaly of every month is (almost) zero." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "monthly_mean = ...\n", "wettest_month = ...\n", "\n", "anomaly = ..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**e)** How many months in the whole record exceed 100 mm?\n", "\n", "**f)** For each year, compute the share (%) of the annual rainfall that falls in the wet season (October to March). What is the average share?\n", "\n", "✅ **Check:** mean annual ≈ 410.4 mm, wettest 2006 (696.6 mm), driest 2014 (228.6 mm), wettest month Dec, 15 months above 100 mm, wet-season share ≈ 78.6 %." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "n_above_100 = ...\n", "\n", "wet_season = [9, 10, 11, 0, 1, 2] # column indices of Oct, Nov, Dec, Jan, Feb, Mar\n", "wet_share = ..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exercise 6 — A flood hydrograph with missing data\n", "\n", "The next cell builds a 72-hour flood hydrograph (hourly discharge, m³/s) above a baseflow of 4 m³/s. The gauge failed at a few time steps: those values are `np.nan`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "t = np.arange(72.0) # time (h)\n", "Q_base, Q_peak, t_peak, k = 4.0, 60.0, 18.0, 3.0\n", "Qh = Q_base + Q_peak * (t / t_peak * np.exp(1 - t / t_peak)) ** k\n", "Qh = Qh.round(2)\n", "Qh[[5, 6, 20, 21, 22, 47, 60]] = np.nan # sensor failures\n", "print(Qh[:8])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**a)** How many values are missing? What does `Qh.max()` return, and why?\n", "\n", "**b)** Fill the gaps by linear interpolation in time (`np.interp`), into a new array `Qf`. Do not modify `Qh`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "n_missing = ...\n", "\n", "Qf = ..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**c)** Find the peak discharge and the time to peak.\n", "\n", "**d)** Compute the total flood volume and the direct-runoff volume (above the 4 m³/s baseflow) with `np.trapezoid`, in hm³ (1 hm³ = 10⁶ m³). Careful with units: $t$ is in hours.\n", "\n", "**e)** What is the fastest rise of discharge in one hour, and when does it occur? How many hours is the discharge above 30 m³/s?\n", "\n", "✅ **Check:** 7 missing values, peak 64.0 m³/s at t = 18 h, total volume ≈ 6.79 hm³, direct runoff ≈ 5.77 hm³, fastest rise ≈ 5.82 m³/s per hour (between t = 7 h and 8 h), 27 hours above 30 m³/s." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Q_max = ...\n", "t_max = ...\n", "\n", "V_total = ...\n", "V_direct = ...\n", "\n", "max_rise = ...\n", "hours_above_30 = ..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Bonus — How much faster is vectorization?\n", "\n", "Compute the wetted area of the canal of Exercise 3 for one million random depths between 0.1 and 4 m, first with a Python loop (list comprehension over `y_big.tolist()`), then vectorized. Time both with `time.perf_counter()` and print the speed-up factor." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import time\n", "\n", "rng = np.random.default_rng(0)\n", "y_big = rng.uniform(0.1, 4.0, 1_000_000)\n", "\n", "..." ] } ], "metadata": { "colab": { "provenance": [] }, "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 5 }