-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #16 from firedrakeproject/TBendall/volcanic_ash
Transport of volcanic ash
- Loading branch information
Showing
2 changed files
with
240 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |