CoastalCodeBook
  1. Week 4
  2. 4b: Shallow-water Tides
  • Introduction
  • About the Codebook
    • Introduction
    • Course structure
    • Notebook overview
    • Acknowledgements
  • Usage
    • Installation Guide
    • Getting Started
    • Troubleshooting
    • Building the book
  • Week 1
    • 1a: Tectonic classification
    • 1b: Process-based coastal classification
  • Week 2
    • 2a: Pre-knowledge waves
    • 2b: Wave dispersion and grouping
    • 2c: Generation of the tide
    • 2d: Tidal constituents
  • Week 3
    • 3a: Tidal Environments
    • 3b: Wave asymmetry and skewness
    • 3c: Radiation stresses and wave forces
  • Week 4
    • 4a: Bound long waves
    • 4b: Shallow-water Tides
  • Week 5
    • 5: Coastal Classification and Beach States
  • Week 6
    • 6: Cross-shore transport
  • Week 7
    • 7: Alongshore transport
  • Week 8
    • 8a: Escoffier curve
    • 8b: Tide-induced residual transport (Van de Kreeke and Robaczewska)
    • 8c: Equilibrium concentration versus lag effects
    • 8d: Interventions and natural changes
  1. Week 4
  2. 4b: Shallow-water Tides
image

4b: Shallow-water Tides

This is the second of the two notebooks used this week. Building upon our analysis of tidal characters and main tidal constituents in previous weeks, it delves into the tidal part of Chapter 5 of the Coastal Dynamics Open Textbook, where you gained insights into tidal propagation in coastal waters and higher harmonic tidal constituents. Our primary focus will be on shallow-water tides, explained in Section 5.7.5 of the textbook.

Theory

Besides the main tidal constituents - which have periods that can be explained from astronomical considerations - additional components with different periods occur in shallow water (shelf regions and coastal seas). These shallow-water tides, also known as overtides and compound tides, are generated as a result of non-linear effects in shallow coastal waters and tidal basins. Compound tides (e.g. MS4) result from the shallow-water interaction between two parent constituents (M2 and S2). Overtides (e.g. M4) result from the interaction of a component with itself (M2 and M2).

The periods of shallow-water tides partly overlap with astronomical tides (diurnal and semi-diurnal components) or have shorter periods that are integer fractions (1/2, 1/3, etc.) of the basic periods. The name overtides comes from the latter characteristic. We will not repeat the whole theory here, so make sure you followed these week’s lectures and read the tidal parts of Chapter 3 and Chapter 5 of the textbook.

Some of the shorter period tides are (we will consider this selection of components in part 1):

  • M3: An astronomical terdiurnal tide, often quite unimportant
  • M2 higher harmonics: Overtides M4, M6, M8
  • S2 higher harmonics: Overtides S4, S6, S8
  • Compound tides generated by the interaction of different components: MS4, MN4

Notebook outline

  1. In Part 1 of this notebook we will explore the above-listed tidal components. These are tidal constituents with shorter periods than the diurnal and semi-diurnal components we have seen in Notebooks 2d and 3a. Part 1 is accompanied by 7 multiple-choice or multiple-selection questions.
  2. In Part 2 we will introduce a method of obtaining the tidal signal and making tidal predictions. Here the challenge for you is to try and follow the computation procedure step-by-step. It may help to re-read Section 3.9 of the book, which is on Tidal Analysis and Prediction. There are no questions for this part.

To get started, please first import the libraries that we need for the analysis from the cell below.

from datetime import datetime, timedelta
import os
from pathlib import Path
import pickle
import warnings

import numpy as np
import pandas as pd
import holoviews as hv
from scipy import stats
import panel as pn

with warnings.catch_warnings():
    warnings.simplefilter("ignore", category=RuntimeWarning)
    warnings.simplefilter("ignore", category=UserWarning)
    import utide

import coastal_dynamics as cd

pn.extension()
hv.extension("bokeh")
import sys

sys.path.append('../')

from modules import mod_4b
questions = cd.read_questions(Path("../hashed_questions/4b_shallow_water_tides_hashed.json"))

question_industry = cd.QuestionIndustry(questions)

cd.UseAnswersApp("Q4b").serve()
dir_path_fes = Path("../database/2_wind_waves_tides/")
dir_path_tides = Path("../database/4_tides/")

Part 1: Visualization of shallow-water tides

Now that we are familiar with shallow-water tides, we can visualise them. To do this, we will plot the tidal signals at the same four locations as before, and using the same methodology as in the previous notebooks.

Execute the block below to generate an interactive figure. The figure displays tidal information for two locations of your choice in two separate columns; start with selecting Scheveningen in the left column and Jakarta in the right columm. Each column shows:

  1. the individual tidal components that are selected using the checkboxes (first row of plots);
  2. the combined tidal signal of the selected components and of all 15 components that are selectable using the checkboxes (second row of plots). Check for yourself that if you select all 15 components, the lines match;
  3. the combined total tidal signal of all 15 components - selectable using the checkboxes - and of all 34 FES components (third row of plots).

The third plot serves to show that the 15 selectable components represent the full FES tidal signal reasonably well for each location.

Please note:
1. You can use the slider to adjust the time range to show a selection of the year 2000. 2. You can 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. 3. You can easily adjust the view of the figure; you can pan (move the view) and zoom, and the two axes are individually zoomable as well. Simply hover over them and use the wheel zoom tool.

# Choose tidal constituents
comps = ['M2', 'S2', 'N2', 'K2',  #semi-diurnal
         'K1', 'O1', 'P1', 'Q1',  #diurnal
         'M3', 'M4', 'M6', 'M8', 'S4', 'MN4', 'MS4'] #short period (M3 + overtides + compound tides)

# Download and load previously calculated tidal signal per constituent
scheveningen_fp = os.path.join(dir_path_fes, "tide_scheveningen.p")
df_scheveningen = mod_4b.pickle_to_df(scheveningen_fp, comps)

jakarta_fp = os.path.join(dir_path_fes, "tide_jakarta.p")
df_jakarta = mod_4b.pickle_to_df(jakarta_fp, comps)

galveston_fp = os.path.join(dir_path_fes, "tide_galveston.p")
df_galveston = mod_4b.pickle_to_df(galveston_fp, comps)

valparaiso_fp = os.path.join(dir_path_fes, "tide_valparaiso.p")
df_valparaiso = mod_4b.pickle_to_df(valparaiso_fp, comps)

locations = {
    "Scheveningen": df_scheveningen,
    "Jakarta": df_jakarta,
    "Galveston": df_galveston,
    "Valparaiso": df_valparaiso,
}

mod_4b.plot_2timeseries_with_interactive_controls_pn(comps, locations)
# Run this cell to get questions about Part 1

q = [
    "Q4b-shallowwatertides",
    "Q4b-period-6h",
    "Q4b-main_overtide",
    "Q4b-Scheveningen-main",
    "Q4b-overtides",
    "Q4b-M2_M4_Scheveningen",
    "Q4b-M2_M4_Jakarta"
]

question_industry.serve(q)

Part 2: Tidal analysis, reconstruction & Prediction

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.

Tidal analysis

In this part of the notebook, we will analyse the tidal signal obtained from the measured sea level. For this, we will use the utide Python package, which is a Python adaptation of the Matlab UTide 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) sea level records at Scheveningen.

Harmonic analysis and long-term constituents

UTide performs a harmonic analysis as explained in Section 3.9 of the book; from a time series of water level data at a certain location, it computes the amplitudes and phases of tidal constituents with known frequencies; they are deterministic and predictable. The corresponding periods range from hours for the shallow-water tides to long-term astronomical components, like semi-annual and annual components Sa and SSa and the component with a period of 18.6 years.

Even though the semi-annual and annual astronomical constituents Sa and SSa are insignificant for most applications, if one includes them in a yearlong harmonic analysis of water level data, their calculated values may come out much larger than would be expected from astronomical forcing. This is because meteorological semi-annual and annual effects on the water levels, like seasonal changes in wind, temperature and atmospheric pressure may show up in these constituents. However, these meteorological effects are not consistent from year to year; their variations from year to year are not predictable. For that reason, one could decide to not use the semi-annual and annual components that come out of a harmonic analysis. Here we take a different approach; we take a long enough time series (many years of data), such that we find constituent values that are more representative of a long-term average semi-annual and annual cycle. In our example, we need a minimum of 20 years of data for this (we will use 40 years, from 1977 to 2017), with the added benefit that also the component with a length of 18.6 years is included.

Computational time

Of course, the choice of running Utide for a 40-year data series comes at the expense of computational cost. It is much cheaper to use a shorter data series. We have therefore pre-computed the phases and amplitudes for the 1977-2017 period. Instead of running Utide yourself, you can load these results. For reference, we do provide the script to make the Utide computation yourself (but we implore you to don’t try a long time series on the server).

The three cells below do the following: 1. The first cell loads the Scheveningen GESLA data for 1953-2018 2. The second cell loads the constituent values (phases and amplitudes) computed by Utide for the 1977-2017 period 3. The third cell is commented out. It shows the code we used to generate the results loaded in the second cell. Take a close look at the code to see if you can make sense of the steps we took. Leave the cell uncommented, or, if you want to do a Utide computation yourself, please choose a small time period, e.g., a year.

# The code cell loads the Scheveningen GESLA observed water levels. The full series runs from 1953 - 2018
tide_gauge_fp = Path("../database/2_wind_waves_tides/Scheveningen_GESLA.pkl")
tide_gauge = pd.read_pickle(tide_gauge_fp)

# drop nan values
tide_gauge.dropna(inplace=True)

# Use this if you would want to check the start and end dates in the data series: 
#display(tide_gauge.head())
#display(tide_gauge.tail())
# Load the already prepared coefficients (coef) for the time frame of 1977-2017 
# Optional: one could also load one of the other pckl files in the database to see the difference for coefficients determined for different periods
# The data range is shown in the filename.

coef_fp = os.path.join(dir_path_tides, "coef19770101-20171231.pckl")
with open(coef_fp, "rb") as pickle_file:
    coef = pickle.load(pickle_file)
# # This script has been used to compute the Utide results (coef) loaded in the above cell
# # Leave it uncommented or use a much smaller time interval. Start with a year, e.g., 2017)

# # Select time window for computation within the time frame of the data
# # Must be between 1953 and 2017
# start_date_utide = "2017-01-01 00:00"
# end_date_utide = "2017-12-31 00:00"

# # splice correct time window in dataframe
# mask = (tide_gauge.index.values >= pd.to_datetime(start_date)) & (tide_gauge.index.values < pd.to_datetime(end_date))
# tide_gauge_utide = tide_gauge[mask]

# # get time (which is the index)
# time_utide = tide_gauge_utide.index

# Prepare the U tide computations
# Linear regression
# t_numeric = np.arange(len(time_utide))
# slope, intercept, r_value, p_value, std_err = stats.linregress(t_numeric, tide_gauge_utide)
    
# # Detrend the series
# detrended_series = tide_gauge_utide - (slope * t_numeric + intercept)
    
# # Solve for tidal coefficients
# coef = utide.solve(
#     time_utide, detrended_series,
#     lat=52.099, # latitude of Scheveningen in GESLA
#     method='ols',
#     nodal=True,
#     trend=False,
#     conf_int='MC',
# )

# # This could take several minutes, depending on your computer, so please wait a bit. You will get the following:
# #  solve: matrix prep ... solution ... done.

Reconstruction

Here we will briefly show how to reconstruct the tidal signal from a measured timeseries. Reconstruction refers to making a tidal prediction for the same time period or part of the time period that we used for the harmonic analysis.

Take a close look at the cell below where we do reconstruction (can you make sense of the steps we take?) and then run it. We have chosen for the reconstruction of a short period in 2015. The reconstruction goes pretty fast since we already have the amplitudes and phases of the tidal constituents and only have to rebuild the time series from these. You can change the period of the reconstruction, but the longer you make it, the more time is required.

When you run the cell, you will get the following: prep/calcs … done. When the computation is finished, a figure with the results will be shown. The figure shows the observational data, the reconstructed tidal signal and the difference between these two.

# Reconstruct any part of the tidal signal
# We will not reconstruct the full 1977-2017 period but only reconstruct the part we want to plot: 


# Choose a time window to reconstruct and plot (can be any window in the period between 1977 - 2017, do not make it too long)
start_date_reconstruct = "2015-05-01 00:00"
end_date_reconstruct = "2015-05-20 00:00"

# splice correct time window in dataframe
mask = (tide_gauge.index.values >= pd.to_datetime(start_date_reconstruct)) & (tide_gauge.index.values < pd.to_datetime(end_date_reconstruct))
tide_gauge_reconstruct = tide_gauge[mask]

# get time (which is the index)
time_reconstruct = tide_gauge_reconstruct.index

# Reconstruct tidal signal
result = utide.reconstruct(time_reconstruct, coef)
tide_reconstruct = pd.Series(result.h, index=result.t_in)

# This could take some time, depending on the chosen time window and your computer, so please wait a bit. 
# You will get the following:
# prep/calcs ... done.

var = [tide_gauge_reconstruct, tide_reconstruct, tide_gauge_reconstruct-tide_reconstruct]
label = ['Measured sea level', 'Reconstructed tide', 'Difference']
color = ['black', 'blue', 'orange']

tides = []
for j in range(len(var)):
    tides.append(hv.Curve((var[j]), label=label[j]).opts(show_legend=True, color=color[j]))

figure = hv.Overlay(tides).opts(aspect=2, responsive=True, title="Scheveningen", ylabel="Sea level [m]", xlabel="Time", legend_position="right",)
figure
prep/calcs ... done.

Reflection on the reconstruction

As you have probably noticed in the code, the measured signal is de-trended first. Then UTide is used on the de-trended signal to obtain the phases and amplitudes of the tidal constituents. Finally, these phase and amplitudes are used to reconstruct a time signal again, which approaches the de-trended signal we started off with, but is not exactly the same, as you can see from the yellow difference between the observed and reconstructed signal. Why is that?

Remember the similar plot from 2d_tidal_constituents. There we asked you to try to explain why the tidal signal doesn’t perfectly match the observed sea level. We called this difference the so-called non-tidal residual. The non-tidal residual is the part of the sea level that remains once the tidal components have been removed. This primarily contains the meteorological contribution to sea level, often called the surge. However, it may also contain some non-linear interactions.

Please note that, here, the difference is the difference between the observational data and a reconstructed signal based on many years of data, showing the unpredictable meteorological contribution to the observed water levels not yet included in the solved-for constituents (see also the above discussion on harmonic analysis and long-term constituents).

Prediction

Utide can also be used for future simulation of tidal signals (again, of course, excluding the random meteorological effects, see above). Take a look at the code below and run the cell. We do the same as for the reconstruction, but now we choose a time window in the future to compute the tidal signal for and plot it. Like the reconstruction, the prediction also goes pretty fast since we already have the amplitudes and phases of the tidal constituents and only have to compute the future time series from these. You can change the prediction period, but the longer you make it, the more time is required.

Again, you will get the following: prep/calcs … done. Once the calculations are done, the cell also plots the tidal signal for dates for which the observations are not yet available.

# Dates for the simulation and plot (feel free to choose your own)
start_date = "2035-05-01 00:00"
end_date = "2035-05-20 00:00"

t_pred  = pd.date_range(start=start_date, end=end_date, freq='h')

# Predict tidal signal
sim = utide.reconstruct(t_pred, coef)
tide_sim = pd.Series(sim.h, index=sim.t_in)

## Plot
tide_simu = hv.Curve(tide_sim)
tide_simu.opts(aspect=2, responsive=True, title="Prediction of the tidal signal at Scheveningen", ylabel="Tidal signal [m]", xlabel="Time")

tide_simu
prep/calcs ... done.

The end

You have reached the end of this notebook 4b. This was the last notebook of this week.

4a: Bound long waves
5: Coastal Classification and Beach States