diff --git a/notebooks/4b_shallow_water_tides.ipynb b/notebooks/4b_shallow_water_tides.ipynb
new file mode 100644
index 0000000..cad9802
--- /dev/null
+++ b/notebooks/4b_shallow_water_tides.ipynb
@@ -0,0 +1,317 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "070a3d1e-3e53-4d9d-adf0-1100343150ea",
+ "metadata": {},
+ "source": [
+ "# Shallow Water Tides\n",
+ "\n",
+ "Building upon our previous analysis of tidal characters and main tidal constituents, this notebook delves into Chapter 5 of the textbook, where you gained insights into tidal propagation in coastal waters and higher harmonic tidal constituents. Here, our primary focus will be on ***shallow-water tides***, also known as ***overtides***, explained in chapter 5.7.5 of the textbook.\n",
+ "Shallow-water tides are generated as a result of non-linear effects in shallow coastal waters and tidal basins. Unlike the basic astronomical constituents driven by tidal forces from the Earth, Moon, and Sun, shallow-water tides have periods that are integer fractions (1/2, 1/3, etc.) of these basic periods. This characteristic gives them the name overtides. We will not repeat the whole theory here, so make sure you followed these weeks lectures and Chapter 5 of the textbook. Some of the overtides are:\n",
+ "- M2 higher harmonics: M3, M4, M6, M8\n",
+ "- S2 higher harmonics: S4, S6, S8\n",
+ "- Tides generated by interaction of different tidal components: MS4, MN4\n",
+ "\n",
+ "\n",
+ "In this notebook, we will explore overtides using python code and introduce a new method of obtaining the tidal signal and making tidal predictions. Therefore, please import the libraries that we need for the analysis from the cell below.\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "68b6986c-fd53-474d-ad6e-4e371c9bac63",
+ "metadata": {},
+ "source": [
+ "## Add additional package\n",
+ "\n",
+ "This week we have added a new package to the environment. So to run this notebook you will have to install this. You can do this by activating your environment, and then installing the package. The package that we want to install is utide. Because it's a pure Python package, that is not available on the conda channels, we will install it with pip. \n",
+ "\n",
+ "The commands that you have to run in a miniforge prompt: \n",
+ "```bash\n",
+ "mamba activate coastal\n",
+ "pip install coastal\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "9b2ce2ff-523a-4801-9dce-e70af8c20a05",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import pathlib\n",
+ "from pathlib import Path\n",
+ "import sys\n",
+ "import warnings\n",
+ "import ipywidgets as widgets\n",
+ "from ipywidgets import interact\n",
+ "from matplotlib.patches import Patch\n",
+ "from matplotlib.ticker import MaxNLocator, StrMethodFormatter\n",
+ "import matplotlib.pyplot as plt\n",
+ "import matplotlib.dates as mdates\n",
+ "import numpy as np\n",
+ "import pandas as pd\n",
+ "import cartopy.crs as ccrs\n",
+ "import xarray as xr\n",
+ "from datetime import datetime, timedelta\n",
+ "from IPython.display import display, Image\n",
+ "import math\n",
+ "import pandas as pd\n",
+ "import pooch\n",
+ "import pickle\n",
+ "from scipy import stats\n",
+ "with warnings.catch_warnings():\n",
+ " warnings.simplefilter(\"ignore\", category=RuntimeWarning)\n",
+ " import utide\n",
+ "\n",
+ "cwd = pathlib.Path().resolve()\n",
+ "proj_dir = cwd.parent.parent.parent # this is the root of the CoastalCodeBook\n",
+ "sys.path.append(str(proj_dir))\n",
+ "\n",
+ "from initialize.Tide_Initialize import plot_2timeseries_with_interactive_controls, questions_4b"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "id": "30da269b-6d4f-4bcc-87ac-9ae5ed3050d2",
+ "metadata": {},
+ "source": [
+ "
\n",
+ "## 1. Visualisation of shallow-water tides\n",
+ "\n",
+ "Now that we are familiar with shallow-water tides, we can visualise them. To do this, we will plot the tidal signals at Scheveningen and Jakarta, using the same methodology as in the previous notebooks.\n",
+ "\n",
+ "Execute the block below to generate an interactive figure. The figure displays the individual tidal components (upper plot), their combined tidal signal (second plot), and the total tidal signal (third plot) for the two locations.\n",
+ "\n",
+ "You can adjust the plotted time range using the slider (from 1 to 15 days) and select which tidal constituents to display with tick boxes. This allows you to experiment with different constituents, observe the resulting signals, and compare the locations.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "4895150e-a8ea-428f-adca-38a998cb2f6d",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Choose tidal constituents\n",
+ "comps = ['M2', 'S2', 'N2', 'K2', #semi-diurnal\n",
+ " 'K1', 'O1', 'P1', 'Q1', #diurnal\n",
+ " 'M3', 'M4', 'M6', 'M8', 'S4', 'MN4', 'MS4'] #short period (overtides)\n",
+ "\n",
+ "# We choose 15 days to plot\n",
+ "dates = np.array([\n",
+ " datetime(2000, 1, 1, 0, 0, 0) + timedelta(seconds=item * 3600)\n",
+ " for item in range(24*15) #15 days\n",
+ "])\n",
+ "\n",
+ "# download and load tidal signals\n",
+ "scheveningen_fp = pooch.retrieve(\n",
+ " \"https://coclico.blob.core.windows.net/coastal-dynamics/2_wind_waves_tides/tide_scheveningen.p\",\n",
+ " known_hash=\"4ebac210fc0893e52655cbc3c9501a6c805e3537f327fed7edb9e7dbfe7fa06a\",\n",
+ ")\n",
+ "jakarta_fp = pooch.retrieve(\n",
+ " \"https://coclico.blob.core.windows.net/coastal-dynamics/2_wind_waves_tides/tide_jakarta.p\",\n",
+ " known_hash=\"7950246c47e757d9dd427063d7c809fda4267ed119efd18de43237aa9f98c9c6\",\n",
+ ")\n",
+ "with open(scheveningen_fp, 'rb') as pickle_file:\n",
+ " scheveningen = pickle.load(pickle_file)\n",
+ "with open(jakarta_fp, 'rb') as pickle_file:\n",
+ " jakarta = pickle.load(pickle_file)\n",
+ "\n",
+ "tide = {\n",
+ " 'Scheveningen': scheveningen,\n",
+ " 'Jakarta': jakarta,\n",
+ " }\n",
+ "\n",
+ "plot_2timeseries_with_interactive_controls(comps, dates, tide)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "f4d487a8-8819-448d-9799-1979159a7a56",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Run this cell to get questions\n",
+ "questions_4b()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "b75bb66c-6ac4-4c28-96ad-9fe0d42483af",
+ "metadata": {},
+ "source": [
+ "
\n",
+ "## 2. Tidal Reconstruction & Prediction\n",
+ "\n",
+ "Because the tide is caused by regular astronomical phenomena, it can be predicted accurately a long time ahead (although not including meteorological effects such as storm surges). While we've utilized global model data from FES2014 in our previous analyses, it's important to note that the tidal signal at a particular location can also be derived from measured sea levels through harmonic analysis.\n",
+ "\n",
+ "In this part of the notebook, we will analyse the tidal signal obtained from the measured sea level. \n",
+ "For this, we will use the [utide](https://github.com/wesleybowman/UTide/tree/master) Python package, which is a Python adaptation of the Matlab [UTide](https://nl.mathworks.com/matlabcentral/fileexchange/46523-utide-unified-tidal-analysis-and-prediction-functions) package. It is a unified tidal analysis framework used for carrying out tidal analysis on a multi-year sequence of observations collected at irregularly spaced times. We will again use the [GESLA-3 (Global Extreme Sea Level Analysis)](https://gesla787883612.wordpress.com/) sea level records at Scheveningen.\n",
+ "\n",
+ "\n",
+ "### Reconstruction\n",
+ "\n",
+ "Here we will briefly show how to reconstruct the tidal signal from a measured timeseries. Take a look at the cell below where we do reconstruction and then run it.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "036fa1bf-515b-4f50-b286-ebea13a82f6f",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "tide_gauge_fp = pooch.retrieve(\n",
+ " \"https://coclico.blob.core.windows.net/coastal-dynamics/2_wind_waves_tides/Scheveningen_GESLA.pkl\",\n",
+ " known_hash=\"90355584803ddcdf88b01fcf02546c4d8201a3fa6f63355ecfdb8ab6a07d1e38\",\n",
+ ")\n",
+ "tide_gauge = pd.read_pickle(tide_gauge_fp)\n",
+ "\n",
+ "\n",
+ "tide_gauge.dropna(inplace=True)\n",
+ "time = tide_gauge.index\n",
+ " \n",
+ "# Linear regression\n",
+ "t_numeric = np.arange(len(time))\n",
+ "slope, intercept, r_value, p_value, std_err = stats.linregress(t_numeric, tide_gauge)\n",
+ " \n",
+ "# Detrend the series\n",
+ "detrended_series = tide_gauge - (slope * t_numeric + intercept)\n",
+ " \n",
+ "# Solve for tidal coefficients\n",
+ "coef = utide.solve(\n",
+ " time, detrended_series,\n",
+ " lat=52.099, # latitude of Scheveningen in GESLA\n",
+ " method='ols',\n",
+ " nodal=True,\n",
+ " trend=False,\n",
+ " conf_int='MC',\n",
+ ")\n",
+ " \n",
+ "# Reconstruct tidal signal\n",
+ "result = utide.reconstruct(time, coef)\n",
+ "tide = pd.Series(result.h, index=result.t_in)\n",
+ "\n",
+ "## This could take several minutes, depending on your computer, so please wait a bit. You will get the following:\n",
+ "# solve: matrix prep ... solution ... done.\n",
+ "# prep/calcs ... done.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "c0d86602-b939-474b-87a9-c0b109b3013e",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "## Plot\n",
+ "# Choose a time window to plot (has to be between 1977 - 2017)\n",
+ "start_date = \"2015-05-01 00:00\"\n",
+ "end_date = \"2015-05-20 00:00\"\n",
+ "\n",
+ "filtered_tide = tide[start_date:end_date]\n",
+ "filtered_gauge = tide_gauge[start_date:end_date]\n",
+ "var = [filtered_gauge, filtered_tide, filtered_gauge-filtered_tide]\n",
+ "label = ['Measured sea level', 'Reconstructed tide', 'Non-tidal residual']\n",
+ "color = ['black', 'blue', 'orange']\n",
+ "fig, ax = plt.subplots(figsize=(12, 6), sharex=True)\n",
+ "for i in range(len(var)):\n",
+ " ax.plot(var[i], label=label[i], color=color[i])\n",
+ " ax.set_ylim(-1.5,2)\n",
+ " ax.set_yticks([-1, 0, 1])\n",
+ "\n",
+ "ax.set_title('Scheveningen', fontsize=12, fontweight='bold')\n",
+ "ax.set_ylabel('Sea Level [m]', ha='center', va='center', rotation='vertical', fontsize=12)\n",
+ "ax.legend(loc='upper right')\n",
+ "plt.show()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "e7f56916-073c-4f70-8e68-ce858c895bfb",
+ "metadata": {},
+ "source": [
+ "
\n",
+ "Remember the similar plot from 2d_tidal_constituents. There we asked you if you can try to explain why the tidal signal doesn't perfectly match the observed sea level. This time we could also calculate the non-tidal residual. The non-tidal residual is the part of the sea level that remains once the astronomical tidal component has been removed. This primarily contains the meteorological contribution to sea level, often called the surge. However, it may also contain some non-linear interactions."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "56dcea69-b1e9-4d9a-bde0-ef1115176c7d",
+ "metadata": {},
+ "source": [
+ "\n",
+ "
\n",
+ "### Prediction\n",
+ "\n",
+ "Utide can also be used for future simulation of tidal signals. Take a look at the code below and run the cell."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "5f7eb0d4-ea81-4a75-bd83-f0a3aabe8b0a",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Simulation dates (feel free to choose your own)\n",
+ "start_date = '2015-01-01'\n",
+ "end_date = '2040-12-31'\n",
+ "\n",
+ "d_pred = pd.date_range(start=start_date, end=end_date, freq='h')\n",
+ "\n",
+ "# Predict tidal signal\n",
+ "sim = utide.reconstruct(d_pred, coef)\n",
+ "tide_sim = pd.Series(sim.h, index=sim.t_in)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "2d3b4d8d-c180-4fe7-9492-ccb3c9a30ed1",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "## Plot\n",
+ "# Choose a time window to plot (has to be in the interval you choose for prediction)\n",
+ "start_date = \"2025-05-01 00:00\"\n",
+ "end_date = \"2030-05-20 00:00\"\n",
+ "\n",
+ "filtered_tide_sim = tide_sim[start_date:end_date]\n",
+ "\n",
+ "plt.subplots(figsize=(10, 6))\n",
+ "plt.plot(filtered_tide_sim)\n",
+ "plt.title('Prediction of the tidal signal at Scheveningen')\n",
+ "plt.xlabel('Time')\n",
+ "plt.ylabel('Tidal signal [m]')\n",
+ "plt.show()\n",
+ "print('\\n Now we have the tidal signal for dates in which the observations are not yet available.')"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python [conda env:coastal]",
+ "language": "python",
+ "name": "conda-env-coastal-py"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.11.7"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/notebooks/initialize/Tide_Initialize.py b/notebooks/initialize/Tide_Initialize.py
index 0f91e57..7c6da47 100644
--- a/notebooks/initialize/Tide_Initialize.py
+++ b/notebooks/initialize/Tide_Initialize.py
@@ -1,6 +1,4 @@
-# %% plot_tide_diagram - from 2c
-
-
+# %% plot_tide_diagram - from 2c
def plot_tide_diagram(angle_sun, angle_moon):
fig, ax = plt.subplots()
@@ -726,5 +724,263 @@ def check_answers(button, answer=answer):
## Missing a condition that A!=B, ..
+# %% plot_2timeseries_with_interactive_controls - from 4b
+
+import matplotlib.pyplot as plt
+import matplotlib.dates as mdates
+import ipywidgets as widgets
+from warnings import filterwarnings
+from matplotlib.ticker import MaxNLocator, StrMethodFormatter
+
+
+def plot_2timeseries_with_interactive_controls(comps, dates, tide):
+ locs = ["Scheveningen", "Jakarta"]
+ all_comp = comps = [
+ "M2",
+ "S2",
+ "N2",
+ "K2",
+ "K1",
+ "O1",
+ "P1",
+ "Q1",
+ "M3",
+ "M4",
+ "M6",
+ "M8",
+ "S4",
+ "MN4",
+ "MS4",
+ ]
+ # Define a list of checkboxes for component selection and put them in one row
+ checkboxes = [
+ widgets.Checkbox(
+ value=(comp in ["M2"]),
+ description=comp,
+ layout=widgets.Layout(width="auto"),
+ )
+ for comp in comps
+ ]
+ checkbox_row = widgets.HBox(
+ checkboxes, layout=widgets.Layout(display="flex", flex_flow="row wrap")
+ )
+
+ # Plot with interactive slider and checkboxes
+ date_range_selector = widgets.SelectionRangeSlider(
+ options=[(date.strftime("%d/%m %Hh"), date) for date in dates],
+ index=(0, len(dates) - 1),
+ description="Dates",
+ orientation="horizontal",
+ layout={"width": "700px"},
+ continuous_update=False,
+ readout=True,
+ )
+
+ def plot_timeseries(date_range, **kwargs):
+ start_date, end_date = date_range
+
+ # Filter selected components
+ selected_components = [comp for comp, value in kwargs.items() if value]
+
+ # Plot selected components
+ fig, axes = plt.subplots(
+ nrows=3, ncols=2, figsize=(12, 7), sharex=True, constrained_layout=False
+ )
+
+ for comp in selected_components:
+ axes[0, 0].plot(
+ tide[locs[0]][comp.lower()][start_date:end_date],
+ label=comp,
+ linewidth=0.5,
+ )
+ axes[0, 1].plot(
+ tide[locs[1]][comp.lower()][start_date:end_date],
+ label=comp,
+ linewidth=0.5,
+ )
+ l = axes[0, 1].legend(
+ fontsize="small", loc="upper right", bbox_to_anchor=(1.2, 1)
+ )
+ for line in l.get_lines():
+ line.set_linewidth(3)
+ axes[0, 0].set_title(f"{locs[0]}", fontweight="bold")
+ axes[0, 1].set_title(f"{locs[1]}", fontweight="bold")
+
+ # Calculate and plot the sum
+ sum_values = [0] * len(locs)
+ for i, loc in enumerate(locs):
+ sum_values[i] = sum(
+ tide[loc][comp.lower()][start_date:end_date]
+ for comp in selected_components
+ )
+
+ axes[1, 0].plot(
+ sum_values[0].index,
+ sum_values[0],
+ color="darkblue",
+ label="Sum of selected components",
+ linewidth=0.5,
+ )
+ axes[1, 1].plot(
+ sum_values[1].index,
+ sum_values[1],
+ color="darkblue",
+ label="Sum of selected components",
+ linewidth=0.5,
+ )
+ l1 = axes[1, 1].legend(
+ fontsize="small", loc="upper right", bbox_to_anchor=(1, 1.2)
+ )
+ for line in l1.get_lines():
+ line.set_linewidth(3)
-# %%
+ sum_allvalues = [0] * len(locs)
+ for i, loc in enumerate(locs):
+ sum_allvalues[i] = sum(
+ tide[loc][comp.lower()][start_date:end_date] for comp in all_comp
+ )
+ axes[2, 0].plot(
+ sum_allvalues[0].index,
+ sum_allvalues[0],
+ color="black",
+ label="Total tidal signal",
+ linewidth=0.5,
+ )
+ axes[2, 1].plot(
+ sum_allvalues[1].index,
+ sum_allvalues[1],
+ color="black",
+ label="Total tidal signal",
+ linewidth=0.5,
+ )
+ l2 = axes[2, 1].legend(
+ fontsize="small", loc="upper right", bbox_to_anchor=(1, 1.2)
+ )
+ for line in l2.get_lines():
+ line.set_linewidth(3)
+
+ # Set labels and legend
+ for ax in axes.flat:
+ ax.tick_params(axis="x", rotation=0)
+ ax.xaxis.set_major_formatter(mdates.DateFormatter("%d/%m\n%H:%Mh"))
+ ax.xaxis.set_major_locator(MaxNLocator(nbins=6))
+ fig.text(
+ 0.05, 0.5, "Sea level [cm]", va="center", rotation="vertical", fontsize=14
+ )
+ fig.text(0.5, 0.05, "Time", ha="center", va="center", fontsize=14)
+ plt.show()
+
+ filterwarnings("ignore", category=UserWarning)
+
+ # Create an interactive widget with checkboxes
+ figure = widgets.interactive(
+ plot_timeseries,
+ date_range=date_range_selector,
+ **{checkbox.description: checkbox for checkbox in checkboxes},
+ )
+
+ # Create a new container for arranging controls
+ controls = widgets.VBox([figure.children[0], checkbox_row, figure.children[-1]])
+ controls.layout.height = "100%"
+ display(controls)
+
+
+# %% Questions in 4b_shallow_water_tides
+
+
+def questions_4b():
+ import numpy as np
+ import ipywidgets as widgets
+
+ options2 = ["M3", "M4", "M6", "M8", "S4"]
+
+ options1 = [
+ "M2",
+ "S2",
+ "N2",
+ "K2", # semi-diurnal
+ "K1",
+ "O1",
+ "P1",
+ "Q1", # diurnal
+ "M3",
+ "M4",
+ "M6",
+ "M8",
+ "S4",
+ "MN4",
+ "MS4",
+ ] # short period (overtides)
+
+ options = [options1, options2]
+
+ Answer2 = ["M6"]
+ Answer1 = ["M4", "MS4", "MN4", "S4"]
+
+ answers = [Answer1, Answer2]
+
+ Q1 = " "
+ Q2 = " "
+
+ questions = [Q1, Q2]
+
+ def multiple_choice_question_body(Question, options, answer):
+ question_widget = widgets.Label(
+ value=Question, layout=widgets.Layout(width="500px")
+ )
+
+ # Use Checkbox widget for multiple-choice options
+ checkboxes = [widgets.Checkbox(description=opt, value=False) for opt in options]
+
+ submit_button = widgets.Button(
+ description="Check",
+ )
+
+ output_widget = widgets.HTML(
+ value="",
+ placeholder="",
+ disabled=False,
+ layout=widgets.Layout(width="500px", border="none"),
+ overflow="hidden",
+ )
+
+ VBox1 = widgets.VBox(
+ [question_widget] + checkboxes + [submit_button, output_widget]
+ )
+
+ display(VBox1)
+
+ def check_answers(button, answer=answer):
+ # Check if all checkboxes are selected
+ selected_options = [
+ checkbox.description for checkbox in checkboxes if checkbox.value
+ ]
+ if set(selected_options) == set(answer):
+ output_widget.value = 'Correct! Well done.'
+ else:
+ output_widget.value = 'Incorrect, please try again.'
+
+ submit_button.on_click(check_answers)
+
+ # make and display the questions
+
+ general_question1 = "1. Select components whose period is around 6 hours. Validate your choice with the spectral plot from notebook 2d_tidal_constituents (Tidal Amplitudes chapter)."
+
+ general_question2 = """2. Two important sources for non-linearity in the tidal propagation equations are bottom friction and continuity.
+ - Compare the periods of M2 and M3, M4, M6, and M8.
+ - Compare the periods of S2 and S4.
+ How were these higher harmonics generated? What is the main overtide that is generated as a consequence of the friction depending non-linearly on the tidal velocity?"""
+
+ general_question34 = """3. Select components M2 and M4.
+ - Are the two constituents in phase? What do you notice in the combined signal?
+ - Compare to Fig. 5.71 from the textbook. Determine which panels in Fig. 5.71 best match the signals from Scheveningen and Jakarta. Check if one of them is a combination of two panels.
+
+4. Select the main tidal constituents and compare the resulting combined signal (second plot) to the total tidal signal, which includes the overtides (third plot). What contribution do the overtides have to the total tidal signal?"""
+
+ print("\n\n\033[1m" + general_question1 + "\033[0m")
+ multiple_choice_question_body(questions[0], options1, answers[0])
+ print("\n\n\033[1m" + general_question2 + "\033[0m")
+ multiple_choice_question_body(questions[1], options2, answers[1])
+ print("\n\n\033[1m" + general_question34 + "\033[0m")
+
+ return None