Skip to content

Commit

Permalink
Merge pull request #16 from firedrakeproject/TBendall/volcanic_ash
Browse files Browse the repository at this point in the history
Transport of volcanic ash
  • Loading branch information
tommbendall authored Aug 2, 2024
2 parents 12f6926 + f9fcb6a commit a719bc6
Show file tree
Hide file tree
Showing 2 changed files with 240 additions and 0 deletions.
129 changes: 129 additions & 0 deletions transport/plot_volcanic_ash.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
"""
Plots the volcanic ash dispersion test case.
"""
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
from netCDF4 import Dataset
import numpy as np
from matplotlib.colors import ListedColormap
from tomplot import (set_tomplot_style, tomplot_contours, tomplot_cmap,
plot_contoured_field, add_colorbar_fig,
tomplot_field_title, extract_gusto_coords,
extract_gusto_field, plot_field_quivers)

# ---------------------------------------------------------------------------- #
# Directory for results and plots
# ---------------------------------------------------------------------------- #
# When copying this example these should not be relative to this file
results_dir = f'/home/thomas/firedrake/src/gusto/case_studies/results/volcanic_ash/'
plot_dir = f'/home/thomas/firedrake/src/gusto/case_studies/figures'
results_file_name = f'{results_dir}/field_output.nc'
plot_stem = f'{plot_dir}/volcanic_ash'
# ---------------------------------------------------------------------------- #
# Things that should be altered based on the plot
# ---------------------------------------------------------------------------- #
field_name = 'ash'
colour_scheme = 'gist_heat_r'
field_label = 'Ash / ppm'
contour_method = 'tricontour'
plot_wind = False
wind_name_X, wind_name_Y = 'VelocityX', 'VelocityY'
pole_longitude = 175.0
pole_latitude = 35.0
new_lon_extents = (-30, 30)
new_lat_extents = (-30, 30)
new_lon_xlims = (-35, 35)
new_lat_ylims = (-35, 35)
Lx = 1000.0
# ---------------------------------------------------------------------------- #
# Things that are likely the same for all plots
# ---------------------------------------------------------------------------- #
set_tomplot_style()
data_file = Dataset(results_file_name, 'r')
# ---------------------------------------------------------------------------- #
# Work out time idxs and overall min/max
# ---------------------------------------------------------------------------- #
time_idxs = range(len(data_file['time'][:]))
universal_field_data = extract_gusto_field(data_file, field_name)
contours = tomplot_contours(universal_field_data)
projection = ccrs.RotatedPole(pole_longitude=pole_longitude, pole_latitude=pole_latitude)
# ---------------------------------------------------------------------------- #
# Make a modified cmap with transparent colour instead of white
# ---------------------------------------------------------------------------- #
ncolours = len(contours)-1
cmap = plt.cm.get_cmap(colour_scheme, ncolours)
colours = cmap(np.linspace(0, 1, ncolours))
# Set transparency for most colours
for i in range(ncolours):
colours[i,-1] = 0.75
# Set first colour to transparent
colours[0,-1] = 0.0
cmap = ListedColormap(colours)
# ---------------------------------------------------------------------------- #
# Loop through points in time
# ---------------------------------------------------------------------------- #
for time_idx in time_idxs:
# ------------------------------------------------------------------------ #
# Data extraction
# ------------------------------------------------------------------------ #
field_data = extract_gusto_field(data_file, field_name, time_idx=time_idx)
coords_X, coords_Y = extract_gusto_coords(data_file, field_name)
time = data_file['time'][time_idx] / (24*60*60)
# ------------------------------------------------------------------------ #
# Extract wind data
# ------------------------------------------------------------------------ #
if plot_wind:
wind_data_X = extract_gusto_field(data_file, wind_name_X, time_idx=time_idx)
wind_data_Y = extract_gusto_field(data_file, wind_name_Y, time_idx=time_idx)
wind_coords_X, wind_coords_Y = extract_gusto_coords(data_file, wind_name_X)
# ------------------------------------------------------------------------ #
# Transform coordinates
# ------------------------------------------------------------------------ #
coords_X = (new_lon_extents[0]
+ coords_X * (new_lon_extents[1] - new_lon_extents[0]) / Lx)
coords_Y = (new_lat_extents[0]
+ coords_Y * (new_lat_extents[1] - new_lat_extents[0]) / Lx)
if plot_wind:
wind_coords_X = (wind_coords_X - new_lon_extents[0]) * (new_lon_extents[1] - new_lon_extents[0]) / Lx
wind_coords_Y = (wind_coords_Y - new_lat_extents[0]) * (new_lat_extents[1] - new_lat_extents[0]) / Lx
# ------------------------------------------------------------------------ #
# Plot data
# ------------------------------------------------------------------------ #
fig = plt.figure(figsize=(5, 5))
ax = fig.add_subplot(1, 1, 1, projection=projection)
ax.stock_img()
ax.coastlines()

cf, _ = plot_contoured_field(ax, coords_X, coords_Y, field_data, contour_method,
contours, cmap=cmap, plot_contour_lines=False,
projection=None, transparency=None)

if plot_wind:
_ = plot_field_quivers(ax, wind_coords_X, wind_coords_Y,
wind_data_X, wind_data_Y,
spatial_filter_step=5, projection=projection)

tomplot_field_title(ax, f't = {time:.2f} days', minmax=True, field_data=field_data,
minmax_format=".2f")
ax.set_xlim(new_lon_xlims)
ax.set_ylim(new_lat_ylims)

# ------------------------------------------------------------------------ #
# Manually add colorbar as it is difficult to get it into the correct position
# ------------------------------------------------------------------------ #
cbar_format = '{x:.0f}'
cbar_ticks = [0, 2]
cbar_ax = fig.add_axes([0.925, 0.11, 0.025, 0.7725])
cb = fig.colorbar(cf, cax=cbar_ax, format=cbar_format, ticks=cbar_ticks,
orientation='vertical', ticklocation='right')
cb.set_label(field_label, labelpad=-5)

# ------------------------------------------------------------------------ #
# Save figure
# ------------------------------------------------------------------------ #
plot_name = f'{plot_stem}_{time_idx:02d}.png'
print(f'Saving figure to {plot_name}')
fig.savefig(plot_name, bbox_inches='tight')
plt.close()


111 changes: 111 additions & 0 deletions transport/volcanic_ash.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
"""
An example experiment for volcanic ash dispersion.
"""

from gusto import *
from firedrake import (RectangleMesh, exp, SpatialCoordinate,
Constant, sin, cos, sqrt, grad)
import sys

# ---------------------------------------------------------------------------- #
# Test case parameters
# ---------------------------------------------------------------------------- #

dt = 120. # 1 mins
tmax = 5*24*60*60 # End time
Lx = 1e6 # Domain length in x direction
Ly = 1e6 # Domain length in y direction
nx = 200 # Number of cells in x direction
ny = 200 # Number of cells in y direction
dumpfreq = int(tmax / (50*dt)) # Output dump frequency
tau = 2.0*24*60*60 # Half life of source
centre_x = 3 * Lx / 8.0 # x coordinate for volcano
centre_y = 2 * Ly / 3.0 # y coordinate for volcano
width = Lx / 50.0 # width of volcano
umax = 12.0 # Representative wind value
twind = 5*24*60*60 # Time scale for wind components
omega22 = 3.0 / twind # Frequency for sin(2*pi*x/Lx)*sin(2*pi*y/Ly)
omega21 = 0.9 / twind # Frequency for sin(2*pi*x/Lx)*sin(pi*y/Ly)
omega12 = 0.6 / twind # Frequency for sin(pi*x/Lx)*sin(2*pi*y/Ly)
omega44 = 0.1 / twind # Frequency for sin(4*pi*x/Lx)*sin(4*pi*y/Ly)

if '--running-tests' in sys.argv:
tmax = dt
dumpfreq = 1
nx = 20
ny = 20

# ---------------------------------------------------------------------------- #
# Set up model objects
# ---------------------------------------------------------------------------- #

# Domain
mesh = RectangleMesh(nx, ny, Lx, Ly, quadrilateral=True)
domain = Domain(mesh, dt, "RTCF", 1)
x, y = SpatialCoordinate(mesh)

# Equation
V = domain.spaces('DG')
eqn = AdvectionEquation(domain, V, "ash")

# I/O
dirname = 'volcanic_ash'
output = OutputParameters(dirname=dirname,
dumpfreq=dumpfreq,
dumplist=['ash'],
dump_nc=True,
dump_vtus=False)
diagnostic_fields = [CourantNumber(), XComponent('u'), YComponent('u')]
io = IO(domain, output, diagnostic_fields=diagnostic_fields)

# Transport schemes
transport_method = [DGUpwind(eqn, "ash")]

# Physics scheme ------------------------------------------------------------- #
# Source is a Lorentzian centred on a point
dist_x = x - centre_x
dist_y = y - centre_y
dist = sqrt(dist_x**2 + dist_y**2)
# Lorentzian function
basic_expression = -width / (dist**2 + width**2)

def time_varying_expression(t):
return 2*basic_expression*exp(-t/tau)

physics_parametrisations = [SourceSink(eqn, 'ash', time_varying_expression,
time_varying=True)]

# Transporting wind ---------------------------------------------------------- #
def transporting_wind(t):
# Divergence-free wind. A series of sines/cosines with different time factors
psi_expr = (0.25*Lx/pi)*umax*(
sin(pi*x/Lx)*sin(pi*y/Ly)
+ 0.15*sin(2*pi*x/Lx)*sin(2*pi*y/Ly)*(1.0 + cos(2*pi*omega22*t))
+ 0.25*sin(2*pi*x/Lx)*sin(pi*y/Ly)*sin(2*pi*omega21*(t-0.7*twind))
+ 0.17*sin(pi*x/Lx)*sin(2*pi*y/Ly)*cos(2*pi*omega12*(t+0.2*twind))
+ 0.12*sin(4*pi*x/Lx)*sin(4*pi*y/Ly)*(1.0 + sin(2*pi*omega44*(t-0.83*twind)))
)

return domain.perp(grad(psi_expr))

# Time stepper
stepper = PrescribedTransport(eqn, SSPRK3(domain, limiter=DG1Limiter(V)),
io, transport_method,
physics_parametrisations=physics_parametrisations,
prescribed_transporting_velocity=transporting_wind)

# ---------------------------------------------------------------------------- #
# Initial conditions
# ---------------------------------------------------------------------------- #

ash0 = stepper.fields("ash")
# Start with some ash over the volcano
ash0.interpolate(Constant(0.0)*-basic_expression)

# ---------------------------------------------------------------------------- #
# Run
# ---------------------------------------------------------------------------- #

num_steps = int(tmax / dt)
logger.info(f'Beginning run to do {num_steps} steps')
stepper.run(t=0, tmax=tmax)

0 comments on commit a719bc6

Please sign in to comment.