How to create Animations#

Section author: Florian Zill (Helmholtz Centre for Environmental Research GmbH - UFZ)

To demonstrate the creation of an animated plot we use a component transport example from the ogs benchmark gallery (https://www.opengeosys.org/docs/benchmarks/hydro-component/elder/).

import matplotlib.pyplot as plt
import numpy as np

import ogstools as ot
from ogstools import examples

mesh_series = examples.load_meshseries_CT_2D_XDMF()

To read your own data as a mesh series you can do:

from ogstools.meshlib import MeshSeries
mesh_series = MeshSeries("filepath/filename_pvd_or_xdmf")

You can choose which timesteps to render by passing either an int array corresponding to the indices, or a float array corresponding to the timevalues to render. If a requested timevalue is not part of the timeseries it will be interpolated. In this case every second frame will be interpolated.

timevalues = np.linspace(
    mesh_series.timevalues[0], mesh_series.timevalues[-1], num=25
)

Now, let’s animate the saturation solution. A timescale at the top indicates existing timesteps and the position of the current timevalue. Note that rendering many frames in conjunction with large meshes might take a really long time. We can pass a plot_func which can apply custom formatting and / or plotting. To modify the domain, we can use the transform method of MeshSeries.

def mesh_func(mesh: ot.Mesh) -> ot.Mesh:
    "Clip the left half of the mesh."
    return mesh.clip("-x", [0, 0, 0])


def plot_func(ax: plt.Axes, timevalue: float) -> None:
    "Add the time to the title."
    ax.set_title(f"{timevalue/(365.25*86400):.1f} yrs")
anim = mesh_series.transform(mesh_func).animate(
    ot.variables.saturation, timevalues, plot_func, vmin=0, vmax=100, dpi=50
)
 0%|          | 0/25 [00:00<?, ?it/s]
 4%|▍         | 1/25 [00:00<00:03,  6.15it/s]
 8%|▊         | 2/25 [00:00<00:03,  6.14it/s]
12%|█▏        | 3/25 [00:00<00:03,  6.51it/s]
16%|█▌        | 4/25 [00:00<00:03,  6.35it/s]
20%|██        | 5/25 [00:00<00:03,  6.58it/s]
24%|██▍       | 6/25 [00:00<00:02,  6.42it/s]
28%|██▊       | 7/25 [00:01<00:02,  6.60it/s]
32%|███▏      | 8/25 [00:01<00:02,  6.45it/s]
36%|███▌      | 9/25 [00:01<00:02,  6.61it/s]
40%|████      | 10/25 [00:01<00:02,  6.47it/s]
44%|████▍     | 11/25 [00:01<00:02,  6.62it/s]
48%|████▊     | 12/25 [00:01<00:02,  6.47it/s]
52%|█████▏    | 13/25 [00:01<00:01,  6.62it/s]
56%|█████▌    | 14/25 [00:02<00:01,  6.47it/s]
60%|██████    | 15/25 [00:02<00:01,  6.62it/s]
64%|██████▍   | 16/25 [00:02<00:01,  6.46it/s]
68%|██████▊   | 17/25 [00:02<00:01,  6.60it/s]
72%|███████▏  | 18/25 [00:02<00:01,  6.45it/s]
76%|███████▌  | 19/25 [00:03<00:01,  5.20it/s]
80%|████████  | 20/25 [00:03<00:00,  5.44it/s]
84%|████████▍ | 21/25 [00:03<00:00,  5.81it/s]
88%|████████▊ | 22/25 [00:03<00:00,  5.91it/s]
92%|█████████▏| 23/25 [00:03<00:00,  6.19it/s]
96%|█████████▌| 24/25 [00:03<00:00,  6.17it/s]
96%|█████████▌| 24/25 [00:03<00:00,  6.04it/s]

The animation can be saved (as mp4) like so:

ot.plot.utils.save_animation(anim, "Saturation", fps=5)

Total running time of the script: (0 minutes 9.292 seconds)