Overview of the model set-up for the Trappist-1e case. The MassFlux simulation covers the whole sphere, while the HighRes simulation is shown by superimposing a high-resolution grid that covers only the substellar region. The graphic shows cloud condensate (white isosurfaces), surface temperature (shading), and free troposphere wind vectors (arrows), focusing on (a) the day side, with the HighRes model domain and the cloud condensate isosurface, and (b) the night side of the planet. The cloud condensate is shown using a threshold of 10−5 kg kg−1 of total cloud condensate (liquid water plus ice). This figure is available online as an interactive zoom and rotatable figure.
Import the necessary libraries.
import warnings
warnings.filterwarnings("ignore")
Progress bar
from fastprogress import progress_bar
3D visualization and mesh analysis library
import pyvista as pv
Scientific stack
import iris
import numpy as np
from aeolus.core import Run
from aeolus.plot.pv import grid_for_scalar_cube_sph, grid_for_vector_cubes_sph
from aeolus.util import subplot_label_generator
Local modules
from commons import NS_MODEL_TYPES
import mypaths
Set white background for 3d figures.
pv.set_plot_theme("document")
Global definitions.
planet = "trap1e"
run_key = "grcs"
ns_cycle = "20090801T0900Z"
OUTPUT_NAME_PREFIX = f"{planet}_{run_key}"
runs = {}
for model_type, model_specs in progress_bar(NS_MODEL_TYPES.items()):
subdir = f"{planet}_{run_key}"
label = f"{planet}_{run_key}_{model_type}"
fpath = (
mypaths.nsdir
/ subdir
/ ns_cycle
/ model_specs["path"].parent
/ "_processed"
/ f"{subdir}_{model_type}_{ns_cycle}.nc"
)
runs[label] = Run(
files=fpath, name=label, planet=planet, model_type=model_type, processed=True
)
pyvista
objects for a composite plot¶Set cloud condensate isosurface threshold.
CC_ISOSURF = [1e-5] # [kg kg-1]
RADIUS = float(runs[label].const.radius.data) # Planet radius [m]
RADIUS
5804071.0
Set viewpoints.
CAM_POS_LATS = [10, 20]
CAM_POS_LONS = [30, 120]
Set common visualisation propeties.
z_scale = 100.0
z_offset = RADIUS * 1.01
model_type = "global"
the_run = runs[f"{planet}_{run_key}_{model_type}"]
Create a grid for the surface temperature.
t_sfc = the_run.proc.extract_strict("surface_temperature")
grid_sfc = grid_for_scalar_cube_sph(
t_sfc, z_offset=z_offset, label=f"{model_type}_sfc_grid"
)
Extract 3 wind components from the global model output.
wind_levels = [5000] # [m]
winds = the_run.proc.extract(
iris.Constraint(level_height=lambda x: x in wind_levels)
).extract(["x_wind", "y_wind", "upward_air_velocity"])
Create a grid for the wind vectors.
grid_vec = grid_for_vector_cubes_sph(
*winds,
vector_scale=RADIUS * 0.004,
vertical_wind_scale=1e2,
z_scale=z_scale,
z_offset=z_offset,
xstride=1,
ystride=1,
label=f"{model_type}_wind_vectors_grid",
)
grid_vec
Header | Data Arrays | ||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
Create glyph objects to show wind vectors.
glyphs = grid_vec.glyph(
orient=f"{model_type}_wind_vectors_grid",
scale=f"{model_type}_wind_vectors_grid",
tolerance=0.035,
)
Extract the total cloud condensate.
global_qct = the_run.proc.extract_strict(
"mass_fraction_of_cloud_liquid_water_in_air"
) + the_run.proc.extract_strict("mass_fraction_of_cloud_ice_in_air")
Create a grid for the cloud condensate and extract a 3D contour (isosurface).
global_qct_cntr = (
grid_for_scalar_cube_sph(
global_qct, z_scale=z_scale, z_offset=z_offset, label=f"{model_type}_qct_grid"
)
.cell_data_to_point_data()
.contour(isosurfaces=CC_ISOSURF)
)
Set common visualisation propeties.
z_scale = 120.0
z_offset = RADIUS * 1.02
TOPLEV = 60
DLEV = 2 # use every 2nd level
DY = 3 # stride along y-coordinate
DX = 3 # stride along x-coordinate
model_type = "lam" # Limited-area model
the_run = runs[f"{planet}_{run_key}_{model_type}"]
Extract the total cloud condensate from the HighRes simulation.
lam_qct = the_run.proc.extract_strict(
"mass_fraction_of_cloud_liquid_water_in_air"
) + the_run.proc.extract_strict("mass_fraction_of_cloud_ice_in_air")
Create a grid from the 3D total cloud condensate field.
lam_grid = grid_for_scalar_cube_sph(
lam_qct[:TOPLEV:DLEV, ::DY, ::DX],
z_scale=z_scale,
z_offset=z_offset,
label=f"{model_type}_qct_grid",
)
Create an isosurface of the cloud condensate.
lam_qct_cntr = lam_grid.cell_data_to_point_data().contour(isosurfaces=CC_ISOSURF)
Extract a mesh from the HighRes data.
lam_dom = (
grid_for_scalar_cube_sph(
lam_qct[:TOPLEV, ::20, ::20], # show every 20th grid point
z_scale=z_scale,
z_offset=z_offset,
label="lam_dom",
)
.extract_geometry()
.extract_all_edges()
)
Pack grids and plotting arguments into a single list.
VIS_CONTAINER = [
{
# Global surface temperature
"mesh": grid_sfc,
"kwargs": {
"cmap": "plasma",
"clim": [160, 285],
"show_scalar_bar": False,
"smooth_shading": True,
},
},
{
# Global wind vectors
"mesh": glyphs,
"kwargs": {"cmap": "Greys", "scalars": "GlyphScale", "show_scalar_bar": False},
},
{
# Global cloud condensate
"mesh": global_qct_cntr,
"kwargs": {
"color": "#ededff",
"clim": [1e-05, 1e-05],
"opacity": 0.5,
"show_scalar_bar": False,
"smooth_shading": True,
},
},
{
# HighRes cloud condensate
"mesh": lam_qct_cntr,
"kwargs": {
"color": "#ebebff",
"clim": [1e-05, 1e-05],
"opacity": 1.0,
"show_scalar_bar": False,
"specular": 0.5,
"ambient": 0.5,
"smooth_shading": True,
},
},
{
# HighRes domain edges
"mesh": lam_dom,
"kwargs": {
"style": "wireframe",
"color": "k",
"opacity": 0.2,
"smooth_shading": True,
},
},
]
Assemble the plot.
iletters = subplot_label_generator()
p = pv.Plotter(shape=(1, len(CAM_POS_LONS)), window_size=np.array([1024, 768 // 2]) * 2)
for idx, (cam_lon, cam_lat) in enumerate(zip(CAM_POS_LONS, CAM_POS_LATS)):
p.subplot(0, idx)
p.add_text(f"({next(iletters)})", font="times", font_size=24)
for plot_dict in VIS_CONTAINER:
p.add_mesh(plot_dict["mesh"], **plot_dict["kwargs"])
p.set_position(pv.grid_from_sph_coords([cam_lon], [90 - cam_lat], [4.5e7]).points)
p.set_focus((0, 0, 0))
p.set_viewup((0, 0, 1))
p.show(auto_close=False, use_panel=False)
And save it.
imgname = mypaths.plotdir / f"{OUTPUT_NAME_PREFIX}_110d_view3d"
p.screenshot(f"{imgname}.png", transparent_background=False)
print(f"Saved to ../{imgname.relative_to(mypaths.topdir)}.png")
Saved to ../plots/trap1e_grcs_110d_view3d.png
p.close()
import matplotlib.pyplot as plt
from matplotlib.offsetbox import AnchoredText
from plot_func import use_style
use_style()
Load the image file.
inp_img = plt.imread(imgname.with_suffix(".png"))
Select the region to zoom into.
img_a = inp_img[140:640, 230:770, :]
img_b = inp_img[140:640, 1230:1780, :]
Plot the two enlarged subplots.
fig, axs = plt.subplots(ncols=2, figsize=(40, 20))
iletters = subplot_label_generator()
for ax, img in zip(axs, (img_a, img_b)):
ax.imshow(img)
ax.axis("off")
ax.add_artist(
AnchoredText(f"({next(iletters)})", loc=2, frameon=False, prop=dict(size="20"))
)
plt.subplots_adjust(wspace=0.05)
And save it.
fig.savefig(imgname.with_name(imgname.name + "__enlarged").with_suffix(".png"))
p = pv.Plotter(shape=(1, 1), window_size=np.array([1024, 768 // 2]) * 2)
# Use one viewpoint
cam_lon = CAM_POS_LONS[0]
cam_lat = CAM_POS_LATS[0]
for plot_dict in VIS_CONTAINER:
p.add_mesh(plot_dict["mesh"], **plot_dict["kwargs"])
p.set_position(pv.grid_from_sph_coords([cam_lon], [90 - cam_lat], [4.5e7]).points)
p.set_focus((0, 0, 0))
p.set_viewup((0, 0, 1))
p.close()
# p.export_vtkjs(imgname)