France - Large Domain Simulation#
This second tutorial on smash aims to perform a simulation over the whole of metropolitan France with a simple model structure. The objective, compared to the first tutorial, is to create a mesh over a large spatial domain, to perform a forward run and to visualize the simulated discharge over the entire domain.
Required data#
You need first to download all the required data.
If the download was successful, a file named France-dataset.tar should be available. We can switch to the directory where this file has been
downloaded and extract it using the following command:
tar xf France-dataset.tar
Now a folder called France-dataset should be accessible and contain the following files and folders:
France_flwdir.tifA GeoTiff file containing the flow direction data,
prcpA directory containing precipitation data in GeoTiff format with the following directory structure:
%Y/%m/%d(2012/01/01),
petA directory containing daily interannual potential evapotranspiration data in GeoTiff format,
In this dataset, there are no gauge attributes or observed discharge, we are only interested in performing a forward run on a domain without optimization beforehand.
We can open a Python interface. The current working directory will be assumed to be the directory where
the France-dataset is located.
Open a Python interface:
python3
Imports#
We will first import everything we need in this tutorial. Both LogNorm and SymLogNorm will be used for plotting
In [1]: import smash
In [2]: import numpy as np
In [3]: import matplotlib.pyplot as plt
In [4]: from matplotlib.colors import LogNorm, SymLogNorm
Model creation#
Model setup creation#
The setup dictionary is pretty similar to the one used for the Cance tutorial except that we do not read observed discharge and the
simulation period is different.
In [5]: setup = {
...: "start_time": "2012-01-01 00:00",
...: "end_time": "2012-01-02 08:00",
...: "dt": 3_600,
...: "hydrological_module": "gr4",
...: "routing_module": "lr",
...: "read_prcp": True,
...: "prcp_conversion_factor": 0.1,
...: "prcp_directory": "./France-dataset/prcp",
...: "read_pet": True,
...: "daily_interannual_pet": True,
...: "pet_directory": "./France-dataset/pet",
...: }
...:
Model mesh creation#
For the mesh, we only need the flow direction file and the mainland France bounding box bbox to pass to the smash.factory.generate_mesh
function. A bouding box in smash is a list of 4 values (xmin, xmax, ymin, ymax), each of which corresponds respectively to
the x minimum value, the x maximum value, the y mimimum value and the y maximum value. The values must be in the same unit and projection as the
flow direction.
In [6]: bbox = [100_000, 1_250_000, 6_050_000, 7_125_000] # Mainland Fance bbox in Lambert-93
In [7]: mesh = smash.factory.generate_mesh(
...: flwdir_path="./France-dataset/France_flwdir.tif",
...: bbox=bbox,
...: )
...:
Note
Compare to a mesh generated with gauge attributes, the following variables are missing: flwdst, gauge_pos, code, area
and area_dln.
We can visualize the shape of the mesh, the flow direction and the flow accumulation
In [8]: mesh["nrow"], mesh["ncol"]
Out[8]: (1075, 1150)
In [9]: plt.imshow(mesh["flwdir"]);
In [10]: plt.colorbar(label="Flow direction (D8)");
In [11]: plt.title("France - Flow direction");
In [12]: plt.imshow(mesh["flwacc"], norm=LogNorm());
In [13]: plt.colorbar(label="Flow accumulation (m²)");
In [14]: plt.title("France - Flow accumulation");
Then, we can initialize the smash.Model object
In [15]: model = smash.Model(setup, mesh)
</> Computing mean atmospheric data
</> Adjusting GR interception capacity
Model simulation#
Forward run#
We can now call the Model.forward_run method, but by default and for memory reasons, the simulated discharge on the
entire spatio-temporal domain is not saved. This means storing an numpy.ndarray of shape (nrow, ncol, ntime_step), which may be quite large depending on the
simulation period and the spatial domain. To activate this option, the return_options argument must be filled in, specifying that you want to retrieve
the simulated discharge on the whole domain. Whenever the return_options is filled in, the Model.forward_run method
returns a smash.ForwardRun object storing these variables.
In [16]: fwd_run = model.forward_run(return_options={"q_domain": True})
In [17]: fwd_run
Out[17]:
q_domain: <class 'numpy.ndarray'>
time_step: <class 'pandas.core.indexes.datetimes.DatetimeIndex'>
In [18]: fwd_run.time_step
Out[18]:
DatetimeIndex(['2012-01-01 01:00:00', '2012-01-01 02:00:00',
'2012-01-01 03:00:00', '2012-01-01 04:00:00',
'2012-01-01 05:00:00', '2012-01-01 06:00:00',
'2012-01-01 07:00:00', '2012-01-01 08:00:00',
'2012-01-01 09:00:00', '2012-01-01 10:00:00',
'2012-01-01 11:00:00', '2012-01-01 12:00:00',
'2012-01-01 13:00:00', '2012-01-01 14:00:00',
'2012-01-01 15:00:00', '2012-01-01 16:00:00',
'2012-01-01 17:00:00', '2012-01-01 18:00:00',
'2012-01-01 19:00:00', '2012-01-01 20:00:00',
'2012-01-01 21:00:00', '2012-01-01 22:00:00',
'2012-01-01 23:00:00', '2012-01-02 00:00:00',
'2012-01-02 01:00:00', '2012-01-02 02:00:00',
'2012-01-02 03:00:00', '2012-01-02 04:00:00',
'2012-01-02 05:00:00', '2012-01-02 06:00:00',
'2012-01-02 07:00:00', '2012-01-02 08:00:00'],
dtype='datetime64[ns]', freq='3600s')
In [19]: fwd_run.q_domain.shape
Out[19]: (1075, 1150, 32)
The returned object smash.ForwardRun contains two variables q_domain and time_step. With q_domain a numpy.ndarray of shape
(nrow, ncol, ntime_step) storing the simulated discharge and time_step a pandas.DatetimeIndex storing the saved time steps.
We can view the simulated discharge for one time step, for example the last one.
In [20]: q = fwd_run.q_domain[..., -1]
In [21]: q = np.where(model.mesh.active_cell == 0, np.nan, q) # Remove the non-active cells from the plot
In [22]: plt.imshow(q, norm=SymLogNorm(1e-4));
In [23]: plt.colorbar(label="Discharge $(m^3/s)$");
In [24]: plt.title("France - Discharge");
Note
Given that we performed a forward run on only 32 time steps with default rainfall-runoff parameters and initial states, the simulated discharge is not realistic.
By default, if the returned time steps are not defined, all the time steps are returned. It is possible to return only certain time steps by
specifying them in the return_options argument, for example only the two last ones.
In [25]: time_step = ["2012-01-02 07:00", "2012-01-02 08:00"]
In [26]: fwd_run = model.forward_run(
....: return_options={
....: "time_step": time_step,
....: "q_domain": True
....: }
....: )
....:
In [27]: fwd_run.time_step
Out[27]: DatetimeIndex(['2012-01-02 07:00:00', '2012-01-02 08:00:00'], dtype='datetime64[ns]', freq=None)
In [28]: fwd_run.q_domain.shape
Out[28]: (1075, 1150, 2)
In [29]: q = fwd_run.q_domain[..., -1]
In [30]: q = np.where(model.mesh.active_cell == 0, np.nan, q) # Remove the non-active cells from the plot
In [31]: plt.imshow(q, norm=SymLogNorm(1e-4));
In [32]: plt.colorbar(label="Discharge $(m^3/s)$");
In [33]: plt.title("France - Discharge");
This concludes this second tutorial on smash.