Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Convergence (Quantitative)

This chapter uses the simulation files from the Telemac steady 2d tutorial, but with a slightly different definition of timesteps and printout periods:

/ steady2d-conv.cas
TIME STEP : 1.
NUMBER OF TIME STEPS : 10000
GRAPHIC PRINTOUT PERIOD : 50
LISTING PRINTOUT PERIOD : 50

Additionally, the simulation was rerun with the -s flag, which saves the simulation state in a file called similar to [FILE-NAME].cas_YEAR-MM-DD-HHhMMminSSs.sortie.

telemac2d.py steady2d-conv.cas -s

Both the steering .cas and .sortie files can be downloaded from the hydro-informatics.com repositories:

Extract and Check Flux Data

The Telemac jupyter notebook templates (HOMETEL/notebooks/ > data_manip/extraction/*.ipynb or workshops/exo_fluxes.ipynb) provide some guidance for extracting data from simulation results, which can be tweaked into a generally applicable framework for observing mass convergence at the boundaries as a function of the defined NUMBER OF TIME STEPS. However, the notebook templates do not work straightforwardly, which is why the following paragraphs describe a simple, minimalist Python tweak called pythomac, developed by hydro-informatics.com. There are three options for working with our codes, and all of them require having an installation of Python along with the numpy, pandas, and matplotlib libraries (see the Python installation guide):

pip-install pythomac
clone pythomac
download pythomac as zip

Pip-install the pythomac package:

pip install pythomac

Detailed instructions for using the extract_fluxes() function through pythomac and its dependencies (numpy, pandas, and matplotlib) can be found at https://pythomac.readthedocs.io.

To use the functions defined in pythomac, copy the following code into a new Python script called, for instance, example_flux_convergence.py in the directory where the dry-initialized steady2d simulation ran (or download example_flux_convergence.py):

# example_flux_convergence.py

from pathlib import Path
from pythomac import extract_fluxes

simulation_dir = str(Path(__file__).parents[1])
telemac_cas = "steady2d.cas"

fluxes_df = extract_fluxes(
    model_directory=simulation_dir,
    cas_name=telemac_cas,
    plotting=True
)

Run the Python script from Terminal (or Anaconda Prompt) as follows (make sure to cd into the simulation directory):

python example_flux_convergence.py

The script will have placed in the simulation folder:

python telemac flux discharge convergence pythomac

Figure 1:Flux convergence plot across the two boundaries of the dry-initialized steady Telemac2d simulation with a total simulation time of timesteps seconds, created with the pythomac.extract_fluxes() function.

Identify Convergence

To test if and when the fluxes converged, we track the relative flux imbalance for every timestep tt as:

εt=Qi,tQj,tQj,t\varepsilon_{t} = \frac{\left| |Q_{i,t}| - |Q_{j,t}| \right|}{|Q_{j,t}|}

where Qi,tQ_{i,t} and Qj,tQ_{j,t} are the outflow and inflow fluxes across the model boundaries at timestep tt, respectively. The flux magnitudes |\cdot| are used because Telemac reports boundary fluxes with a sign (inflow positive, outflow negative); mass balance then corresponds to Qi,t=Qj,t|Q_{i,t}| = |Q_{j,t}|, so that εt0\varepsilon_{t} \to 0 at convergence, and normalizing by the inflow Qj,t|Q_{j,t}| makes εt\varepsilon_{t} dimensionless. In a stable steady simulation, the ratio of consecutive flux imbalances should converge toward a convergence constant cεc_{\varepsilon} equal to unity with increasing time:

limtεt+1εtι=cε\lim_{t\to \infty} \frac{\varepsilon_{t+1}}{\varepsilon^{\iota}_{t}} = c_{\varepsilon}

The combination of the convergence rate (or order) ι\iota, and the convergence constant cεc_{\varepsilon} indicate:

To determine the timestep at which a steady simulation can be considered to have reached a stable state, we want to observe when ι\iota = 1 and cεc_{\varepsilon} = 1 indicate sublinear convergence. That is, we look for the timestep tt above which each additional timestep t+1t+1 only insignificantly improves the model precision (read more on the term insignificant in the below section). Thus, assuming that the model convergences in any form, we can set cεc_{\varepsilon} = 1 to compute ι(t)\iota (t) as a function of εt\varepsilon_{t} and εt+1\varepsilon_{t+1}:

εt+1εtι(t)=cει(t)=1cεlogεtεt+1cε=1ι(t)=logεtεt+1\begin{align} \frac{\varepsilon_{t+1}}{\varepsilon^{\iota(t)}_{t}} &=c_{\varepsilon} & \Leftrightarrow \\ \iota(t) &= \frac{1}{c_{\varepsilon}} \cdot \log_{\varepsilon_{t}\varepsilon_{t+1}} & \overbrace{\Longleftrightarrow }^{c_{\varepsilon} = 1}\\ \iota(t) &= \log_{\varepsilon_{t}\varepsilon_{t+1}} & \end{align}

This equation can be implemented in a Python function as follows:

import numpy as np
import pandas as pd


def calculate_convergence(Q_in, Q_out, conv_constant=1.):
    # relative flux imbalance epsilon_t = ||Q_in| - |Q_out|| / |Q_in|; the magnitudes |.|
    #   are needed because Telemac reports outflow negative, so that balance -> epsilon -> 0
    epsilon = np.abs(np.abs(Q_in) - np.abs(Q_out)) / np.abs(Q_in)
    # derive epsilon at t and t+1
    epsilon_t0 = epsilon[:-1]  # cut off last element
    epsilon_t1 = epsilon[1:]   # cut off element zero
    # return the relative imbalance and the convergence rate iota as a pandas DataFrame
    return pd.DataFrame({
        "Relative imbalance": epsilon_t1,
        "Convergence rate": np.emath.logn(epsilon_t0, epsilon_t1) / conv_constant,
    })

To calculate ι(t)\iota (t) (Python variable name: iota_t) with the above function, amend the example_flux_convergence.py Python script:

# example_flux_convergence.py

# ...
# add to header (alternatively copy-paste the above function into this script):
from pythomac import calculate_convergence

# calculate fluxes_df (see above code block)
fluxes_df = [...]

# calculate iota (t) with the calculate_convergence function
iota_t = calculate_convergence(
    fluxes_df["Fluxes Boundary 1"][1:],  # remove first zero-entry
    fluxes_df["Fluxes Boundary 2"][1:],  # remove first zero-entry
)

The resulting convergence rate ι(t)\iota (t) is plotted in Fig. 2 for the steady 2d tutorial, with the modified printout periods of 50 seconds and a total simulation time of 10000 seconds.

convergence rate fluxes telemac boundaries

Figure 2:The convergence rate ι\iota as a function 10000 simulation timesteps of the steady 2d simulation.

Derive Optimum Simulation Time

To save computing time, we are interested in the timestep at which the inflow and outflow fluxes converged. The fluxes plotted in Fig. 1, and the convergence rate in Fig. 2 qualitatively suggest that the simulation was stable after approximately 6000 seconds (timesteps). The bumps in both figures after 4000 timesteps well indicate the “encounter” of the flux “waves” coming from the upstream and downstream boundaries (see the animation in the steady 2d tutorial). Only afterward, did convergence set in.

The definition of when convergence is reached can be subjective. To save computing time, we look for the smallest timestep tt beyond which the relative flux imbalance εt\varepsilon_{t} (Equation (1)) stays permanently below a target tolerance εtar\varepsilon_{tar}. Tolerances of εtar\varepsilon_{tar} = 104^{-4} are typically acceptable for preliminary calibration runs, while validation and hotstart-initialization runs warrant smaller values (106^{-6} or smaller). As Fig. 2 illustrates, the imbalance may briefly drop below the tolerance and rise again after approximately 4000 timesteps, when the upstream “wave” rolls over the downstream boundary; only the last permanent crossing counts. Therefore, we want an algorithmic implementation that detects the last timestep at which εtεtar\varepsilon_{t} \geq \varepsilon_{tar} and takes the next one as the convergence time. To this end, we can add to the above-started example_flux_convergence.py Python script the following code:

# example_flux_convergence.py

# ...

# calculate fluxes_df (see above code block)
fluxes_df = [...]

# calculate iota_t with calculate_convergence (also returns a "Relative imbalance" column)
iota_t = [...]

# define a desired target tolerance (epsilon_tar) for the relative flux imbalance
target_convergence_precision = 1.0E-4

# get the relative flux imbalance epsilon_t (Delta_Q) from calculate_convergence
relative_imbalance = iota_t["Relative imbalance"].to_numpy()

# find the index of the last element that still exceeds the target tolerance and add +1
#   (i.e., the first element from which the imbalance stays permanently below the tolerance)
below_tolerance = relative_imbalance < target_convergence_precision
idx = np.flatnonzero(~below_tolerance)[-1] + 1

# print the timestep at which the desired convergence was achieved to the console
printout_period_in_cas = 50  # the printout period defined in the cas file
print("The simulation converged after {0} simulation seconds ({1}th printout).".format(
        str(printout_period_in_cas * idx), str(idx)))
The simulation converged after 6000 simulation seconds (120th printout).

Now that you know when your Telemac simulation converges satisfactorily, you can reduce the NUMBER OF TIME STEPS parameter in the .cas steering file, for example:

/ steady2d-conv.cas
TIME STEP : 1.
NUMBER OF TIME STEPS : 6000
GRAPHIC PRINTOUT PERIOD : 50
LISTING PRINTOUT PERIOD : 50

Troubleshoot Instabilities & Divergence

If a steady simulation never reaches stability of fluxes or even flux divergence, make sure that all boundaries are robustly defined according to the spotlight section on boundary conditions, and have a look at the workflow in the next section on mass conservation.