Skip to content

Commit a14c605

Browse files
olastrzslayoo
andauthored
refactor Paraview code & parcel-model animations (#1725)
Co-authored-by: Sylwester Arabas <sylwester.arabas@agh.edu.pl>
1 parent 1663eea commit a14c605

File tree

11 files changed

+840
-4
lines changed

11 files changed

+840
-4
lines changed

.github/workflows/pdoc.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ jobs:
2828
python-version: "3.12"
2929

3030
- name: exclude pvpython scripts
31-
run: rm examples/PySDM_examples/utils/pvanim.py
31+
run: grep -rl '^#!/usr/bin/env pvpython' examples/PySDM_examples | xargs rm -f
3232

3333
- run: pip install pdoc nbformat gitpython
3434
- run: pip install -e .

.github/workflows/tests.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,8 +300,10 @@ jobs:
300300
rm /tmp/pytest/test_run_notebooks_*current
301301
ls /tmp/pytest/test_run_notebooks_*/
302302
ls /tmp/pytest/test_run_notebooks_*/output/
303-
ffmpeg -i /tmp/pytest/test_run_notebooks_*/output/docs_intro_animation.ogv -loop 0 -vf "fps=10,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" $HOME/work/_temp/_github_home/figures/docs_intro_animation_${{ matrix.platform }}.gif
304303
mv /tmp/pytest/test_run_notebooks_*/output/last_animation_frame.pdf $HOME/work/_temp/_github_home/figures/last_animation_frame_${{ matrix.platform }}.pdf
304+
for anim_name in docs_intro_animation parcel_animation; do
305+
ffmpeg -i /tmp/pytest/test_run_notebooks_*/output/${anim_name}.ogv -loop 0 -vf "fps=10,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" $HOME/work/_temp/_github_home/figures/${anim_name}_${{ matrix.platform }}.gif
306+
done
305307
ls $HOME/work/_temp/_github_home/figures
306308
307309
- name: animation movie upload

PySDM/exporters/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@
66
from .netcdf_exporter_1d import NetCDFExporter_1d, readNetCDF_1d
77
from .vtk_exporter import VTKExporter
88
from .vtk_exporter_1d import VTKExporter_1d
9+
from .vtk_exporter_parcel import VTKExporterParcel
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
"""
2+
VTK Exporter for parcel PySDM simulations.
3+
4+
This module defines `VTKExporterParcel`, a subclass of `PySDM.exporters.VTKExporter`,
5+
that writes simulation outputs to VTK format using `pyevtk`. It exports product
6+
profiles (e.g., relative humidity) as unstructured grids and particle attributes
7+
as point clouds, along with `.pvd` collection files for time-series visualization
8+
in ParaView.
9+
"""
10+
11+
from pyevtk.hl import unstructuredGridToVTK, pointsToVTK
12+
from pyevtk.vtk import VtkHexahedron, VtkGroup
13+
import numpy as np
14+
15+
from PySDM.exporters.vtk_exporter import VTKExporter
16+
17+
18+
# pylint: disable=too-many-instance-attributes
19+
class VTKExporterParcel(VTKExporter):
20+
"""
21+
Custom VTK exporter for parcel PySDM, exporting products as grids
22+
and attributes as point clouds for ParaView visualization.
23+
"""
24+
25+
def __init__(self, n_sd, output, mass_of_dry_air):
26+
super().__init__()
27+
self.output = output
28+
self.coords = {
29+
"x": np.random.random(n_sd),
30+
"y": np.random.random(n_sd),
31+
"z": np.random.random(n_sd),
32+
}
33+
self.half_diagonal = []
34+
self.n_levels = len(self.output["products"]["z"])
35+
36+
volume = mass_of_dry_air / output["products"]["rhod"][0]
37+
delta_z = output["products"]["z"][1] - output["products"]["z"][0]
38+
for level in range(self.n_levels):
39+
if level > 0:
40+
prev_to_curr_density_ratio = (
41+
output["products"]["rhod"][level - 1]
42+
/ output["products"]["rhod"][level]
43+
)
44+
volume *= prev_to_curr_density_ratio
45+
delta_z = (
46+
output["products"]["z"][level] - output["products"]["z"][level - 1]
47+
)
48+
area = volume / delta_z
49+
self.half_diagonal.append((2 * area) ** 0.5)
50+
51+
def write_pvd(self):
52+
pvd_attributes = VtkGroup(self.attributes_file_path)
53+
for key, value in self.exported_times["attributes"].items():
54+
pvd_attributes.addFile(key + ".vtu", sim_time=value)
55+
pvd_attributes.save()
56+
57+
pvd_products = VtkGroup(self.products_file_path)
58+
for key, value in self.exported_times["products"].items():
59+
pvd_products.addFile(key + ".vtu", sim_time=value)
60+
pvd_products.save()
61+
62+
def export_products(
63+
self, step, simulation
64+
): # pylint: disable=arguments-differ, too-many-locals
65+
path = self.products_file_path + "_num" + self.add_leading_zeros(step)
66+
self.exported_times["products"][path] = step * simulation.particulator.dt
67+
68+
n_vertices = 4 * self.n_levels
69+
x_vertices = np.zeros(n_vertices)
70+
y_vertices = np.zeros(n_vertices)
71+
z_vertices = np.zeros(n_vertices)
72+
connectivity = [0, 1, 2, 3]
73+
cell_type = np.zeros(self.n_levels - 1)
74+
cell_type[:] = VtkHexahedron.tid
75+
76+
for level in range(self.n_levels):
77+
half_diag = self.half_diagonal[level]
78+
current_z = self.output["products"]["z"][level]
79+
idx = level * 4
80+
x_vertices[idx], y_vertices[idx], z_vertices[idx] = (
81+
-half_diag,
82+
-half_diag,
83+
current_z,
84+
)
85+
idx += 1
86+
x_vertices[idx], y_vertices[idx], z_vertices[idx] = (
87+
-half_diag,
88+
half_diag,
89+
current_z,
90+
)
91+
idx += 1
92+
x_vertices[idx], y_vertices[idx], z_vertices[idx] = (
93+
half_diag,
94+
half_diag,
95+
current_z,
96+
)
97+
idx += 1
98+
x_vertices[idx], y_vertices[idx], z_vertices[idx] = (
99+
half_diag,
100+
-half_diag,
101+
current_z,
102+
)
103+
connectivity += [*range(4 * (level + 1), 4 * (level + 2))] * 2
104+
connectivity = np.asarray(connectivity[:-4])
105+
offset = np.asarray(range(8, 8 * self.n_levels, 8))
106+
107+
_ = {"test_pd": np.array([44] * n_vertices)}
108+
109+
relative_humidity = self.output["products"]["S_max_percent"]
110+
cell_data = {
111+
"RH": np.full(shape=(len(relative_humidity) - 1,), fill_value=np.nan)
112+
}
113+
cell_data["RH"][:step] = (
114+
np.array(relative_humidity[:-1] + np.diff(relative_humidity) / 2)
115+
)[:step]
116+
unstructuredGridToVTK(
117+
path,
118+
x_vertices,
119+
y_vertices,
120+
z_vertices,
121+
connectivity=connectivity,
122+
offsets=offset,
123+
cell_types=cell_type,
124+
cellData=cell_data,
125+
)
126+
127+
def export_attributes(self, step, simulation): # pylint: disable=arguments-differ
128+
path = self.attributes_file_path + "_num" + self.add_leading_zeros(step)
129+
self.exported_times["attributes"][path] = step * simulation.particulator.dt
130+
131+
payload = {}
132+
133+
for key in self.output["attributes"].keys():
134+
payload[key] = np.asarray(self.output["attributes"][key])[:, step].copy()
135+
if step != 0:
136+
delta_z = (
137+
self.output["products"]["z"][step]
138+
- self.output["products"]["z"][step - 1]
139+
)
140+
if step == 1:
141+
self.coords["z"] *= delta_z
142+
else:
143+
self.coords["z"] += delta_z
144+
145+
pointsToVTK(
146+
path,
147+
2 * (self.coords["x"] - 0.5) * self.half_diagonal[step],
148+
2 * (self.coords["y"] - 0.5) * self.half_diagonal[step],
149+
self.coords["z"],
150+
data=payload,
151+
)

examples/PySDM_examples/Pyrcel/simulation.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,13 @@
1313

1414
class Simulation(BasicSimulation):
1515
def __init__(
16-
self, settings, products=None, scipy_solver=False, rtol_thd=1e-10, rtol_x=1e-10
16+
self,
17+
settings,
18+
products=None,
19+
scipy_solver=False,
20+
rtol_thd=1e-10,
21+
rtol_x=1e-10,
22+
mass_of_dry_air=44 * si.kg,
1723
):
1824
n_sd = sum(settings.n_sd_per_mode)
1925
builder = Builder(
@@ -27,7 +33,7 @@ def __init__(
2733
initial_water_vapour_mixing_ratio=settings.initial_vapour_mixing_ratio,
2834
T0=settings.initial_temperature,
2935
w=settings.vertical_velocity,
30-
mass_of_dry_air=44 * si.kg,
36+
mass_of_dry_air=mass_of_dry_air,
3137
),
3238
)
3339
builder.add_dynamic(AmbientThermodynamics())
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# pylint: disable=invalid-name
2+
"""
3+
ParaView visualisation of a parcel-model example based on the test case from
4+
[Pyrcel package docs](https://pyrcel.readthedocs.io/)
5+
6+
paraview_parcel_model.ipynb:
7+
.. include:: ./paraview.ipynb.badges.md
8+
"""

0 commit comments

Comments
 (0)