Skip to content

Add code to plot ocean TF within transect #600

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 106 additions & 45 deletions landice/output_processing_li/plot_transect.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@
# -*- coding: utf-8 -*-
"""
Plot bed and surface topography, surface speed, and
(optionally) temperature from MPAS netCDF along a transect.
@author: trevorhillebrand
(optionally) temperature and thermal forcing from MPAS netCDF along a transect.
@author: trevorhillebrand, edited by cashafer
"""

import numpy as np
import csv
from netCDF4 import Dataset
from optparse import OptionParser
from scipy.interpolate import LinearNDInterpolator
from scipy.spatial import Delaunay
import matplotlib.pyplot as plt
from matplotlib.pyplot import cm

Expand All @@ -36,20 +37,60 @@
parser.add_option("--temperature", dest="interp_temp",
action="store_true", help="interpolate temperature")

parser.add_option("--thermal_forcing", dest="interp_thermal_forcing",
action="store_true", help="interpolate thermal forcing")

for option in parser.option_list:
if option.default != ("NO", "DEFAULT"):
option.help += (" " if option.help else "") + "[default: %default]"
options, args = parser.parse_args()


# Get list of times for plotting
times_list = [i for i in options.times.split(',')] # list of string times for plotting
times = [int(i) for i in options.times.split(',')] # list of integer time indices

# Load MPAS netCDF file
dataset = Dataset(options.data_file, 'r')
dataset.set_always_mask(False)

# Get x and y coordinates
if options.coords_file is not None:
x = []
y = []
with open(options.coords_file, newline='') as csvfile:
reader = csv.reader(csvfile, delimiter=',')
for row in reader:
x.append(float(row[0]))
y.append(float(row[1]))
if [options.x_coords, options.y_coords] != [None, None]:
print('-c and -x/-y options were both provided. Reading from ',
f'{options.coords_file} and ignoring -x and -y settings.')
x = np.asarray(x)
y = np.asarray(y)
else:
x = np.array([float(i) for i in options.x_coords.split(',')])
y = np.array([float(i) for i in options.y_coords.split(',')])

# Determine bounding box using max and min of user-provided coordinates
pad = 5000
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might want to make the padding size a function of dcEdge. @matthewhoffman , what do you think?

x_max = np.max(x) + pad
x_min = np.min(x) - pad
y_max = np.max(y) + pad
y_min = np.min(y) - pad

xCell = dataset.variables["xCell"][:]
yCell = dataset.variables["yCell"][:]
nCells = dataset.dimensions['nCells'].size
areaCell = dataset.variables["areaCell"][:]

indices = np.where( np.logical_and(
np.logical_and(xCell>=x_min, xCell<=x_max),
np.logical_and(yCell>=y_min, yCell<=y_max)) )[0]

xCell = xCell[indices]
yCell = yCell[indices]

tri_mesh = Delaunay( np.vstack((xCell, yCell)).T )

layerThicknessFractions = dataset.variables["layerThicknessFractions"][:]
nVertLevels = dataset.dimensions['nVertLevels'].size
if "daysSinceStart" in dataset.variables.keys():
Expand All @@ -66,61 +107,54 @@
if '-1' in times_list:
times_list[times_list.index('-1')] = str(dataset.dimensions['Time'].size - 1)

# Cannot plot temperature for more than one time index.
if options.interp_temp and (len(times) > 1):
print('Cannot plot temperature for more than one time index.' +
' Skipping temperature interpolation and plotting.')
# Cannot plot temperature or thermal forcing for more than one time index.
if (options.interp_temp or options.interp_thermal_forcing) and (len(times) > 1):
print('Cannot plot temperature or thermal forcing for more than one time index.' +
' Skipping temperature or TF interpolation and plotting.')
options.interp_temp = False
options.interp_thermal_forcing = False

li_mask_ValueDynamicIce = 2
cellMask = dataset.variables['cellMask'][times,:]
cellMask = dataset.variables['cellMask'][times, indices]
cellMask_dynamicIce = (cellMask & li_mask_ValueDynamicIce) // li_mask_ValueDynamicIce
# only take thickness of dynamic ice
thk = dataset.variables["thickness"][times,:] * cellMask_dynamicIce
thk = dataset.variables["thickness"][times, indices] * cellMask_dynamicIce

plot_speed = True
# Include speed on non-dynamic ice to avoid interpolation artifacts.
if "surfaceSpeed" in dataset.variables.keys():
speed = dataset.variables["surfaceSpeed"][times,:] * 3600. * 24. * 365.
speed = dataset.variables["surfaceSpeed"][times, indices] * 3600. * 24. * 365.
elif "surfaceSpeed" not in dataset.variables.keys() and \
all([ii in dataset.variables.keys() for ii in ['uReconstructX', 'uReconstructY']]):
speed = np.sqrt(dataset.variables["uReconstructX"][times,:,0]**2. +
dataset.variables["uReconstructY"][times,:,0]**2.)
speed = np.sqrt(dataset.variables["uReconstructX"][times, indices, 0]**2. +
dataset.variables["uReconstructY"][times, indices, 0]**2.)
speed *= 3600. * 24. * 365. # convert from m/s to m/yr
else:
print('File does not contain surfaceSpeed or uReconstructX/Y.',
' Skipping velocity plot.')
plot_speed = False


if options.interp_temp:
temperature = dataset.variables['temperature'][times,:]
temperature = dataset.variables['temperature'][times, indices]

if options.interp_thermal_forcing:
# Extrapolated Ocean thermal forcing
thermal_forcing = dataset.variables['TFocean'][times, indices]
thermal_forcing[thermal_forcing == 1.0e36] = np.nan # Large invalid TF values set to NaN
# Number of ocean layers
nISMIP6OceanLayers = dataset.dimensions['nISMIP6OceanLayers'].size
# Depths associated with thermal forcing field
ismip6shelfMelt_zOcean = dataset.variables['ismip6shelfMelt_zOcean'][:]


bedTopo = dataset.variables["bedTopography"][0,:]
bedTopo = dataset.variables["bedTopography"][0, indices]
print('Reading bedTopography from the first time level only. If multiple',
'times are needed, plot_transects.py will need to be updated.')
# Use coordinates from CSV file or -x -y options, but not both.
# CSV file takes precedent if both are present.
if options.coords_file is not None:
x = []
y = []
with open(options.coords_file, newline='') as csvfile:
reader = csv.reader(csvfile, delimiter=',')

for row in reader:
x.append(float(row[0]))
y.append(float(row[1]))
if [options.x_coords, options.y_coords] != [None, None]:
print('-c and -x/-y options were both provided. Reading from ',
f'{options.coords_file} and ignoring -x and -y settings.')
x = np.asarray(x)
y = np.asarray(y)
else:
x = np.array([float(i) for i in options.x_coords.split(',')])
y = np.array([float(i) for i in options.y_coords.split(',')])


# increase sampling to match highest mesh resolution
total_distance, = np.cumsum( np.sqrt( np.diff(x)**2. + np.diff(y)**2. ) )
n_samples = int(round(total_distance / np.min(dataset.variables["dcEdge"][:])))
total_distance = np.cumsum( np.sqrt( np.diff(x)**2. + np.diff(y)**2. ) )
n_samples = int(round(total_distance[-1] / np.min(dataset.variables["dcEdge"][indices])))
x_interp = np.interp(np.linspace(0, len(x)-1, n_samples),
np.linspace(0, len(x)-1, len(x)), x)
y_interp = np.interp(np.linspace(0, len(y)-1, n_samples),
Expand All @@ -142,12 +176,11 @@

plt.rcParams.update({'font.size': 16})

bed_interpolant = LinearNDInterpolator(np.vstack((xCell, yCell)).T, bedTopo)
bed_interpolant = LinearNDInterpolator(tri_mesh, bedTopo)
bed_transect = bed_interpolant(np.vstack((x_interp, y_interp)).T)

for i, time in enumerate(times):
thk_interpolant = LinearNDInterpolator(
np.vstack((xCell, yCell)).T, thk[i,:])
thk_interpolant = LinearNDInterpolator(tri_mesh, thk[i,:])
thk_transect = thk_interpolant(np.vstack((x_interp, y_interp)).T)
lower_surf = np.maximum( -910. / 1028. * thk_transect, bed_transect)
lower_surf_nan = lower_surf.copy() # for plotting
Expand All @@ -159,8 +192,7 @@
thickAx.plot(distance, upper_surf_nan, color=timeColors[i])

if plot_speed:
speed_interpolant = LinearNDInterpolator(
np.vstack((xCell, yCell)).T, speed[i,:])
speed_interpolant = LinearNDInterpolator(tri_mesh, speed[i,:])
speed_transect = speed_interpolant(np.vstack((x_interp, y_interp)).T)
speed_transect[thk_transect == 0.] = np.nan
speedAx.plot(distance, speed_transect, color=timeColors[i])
Expand All @@ -180,11 +212,25 @@
temp_transect = np.zeros((len(x_interp), nVertLevels))
for lev in range(nVertLevels):
print(f'Interpolating temperature for level {lev}')
temp_interpolant = LinearNDInterpolator(
np.vstack((xCell, yCell)).T,
temperature[i,:,lev])
temp_interpolant = LinearNDInterpolator(tri_mesh, temperature[i,:,lev])
temp_transect[:, lev] = temp_interpolant(
np.vstack((x_interp, y_interp)).T)

if options.interp_thermal_forcing:
ocean_layer_interfaces = np.zeros((len(thk_transect), nISMIP6OceanLayers + 1)) # Ocean layer depths
ocean_layer_interfaces[:,0] = 0. # First interface is the topmost z = 0 sea level interface

# For the entire length of the transect, set the layer interface depths
for ii in range(len(thk_transect)):
ocean_layer_interfaces[ii,1:] = ismip6shelfMelt_zOcean[:] + ismip6shelfMelt_zOcean[0]

# Create the thermal forcing transect using the TF interpolator at each ocean lev
thermal_forcing_transect = np.zeros((len(x_interp), nISMIP6OceanLayers))

for ocean_lev in range(nISMIP6OceanLayers):
print(f'Interpolating ocean thermal forcing for ocean level {ocean_lev}')
thermal_forcing_interpolant = LinearNDInterpolator(tri_mesh, thermal_forcing[i, :, ocean_lev])
thermal_forcing_transect[:, ocean_lev] = thermal_forcing_interpolant(np.vstack( (x_interp, y_interp) ).T)

thickAx.plot(distance, bed_transect, color='black')

Expand All @@ -195,6 +241,15 @@
cmap='YlGnBu_r',
vmin=240., vmax=273.15)

if options.interp_thermal_forcing:
thermal_forcing_plot = thickAx.pcolormesh(np.tile(distance, (nISMIP6OceanLayers+1, 1)).T,
ocean_layer_interfaces[:, :], thermal_forcing_transect[1:, :],
cmap='YlOrRd', vmin=0, vmax=5.5)
thickAx.fill_between(distance, upper_surf_nan, lower_surf_nan, color='xkcd:ice blue')
thickAx.fill_between(distance, bed_transect, thickAx.get_ylim()[0], color='xkcd:greyish brown')
Comment on lines +248 to +249
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should be applied generally, not just for the thermal forcing case. However, the ice color will need to be skipped if options.temperature.

thickAx.grid(False)
thickAx.set_ylim([None, 750])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The axis limits should not be hard-coded.


speedAx.set_xlabel('Distance (km)')
speedAx.set_ylabel('Surface\nspeed (m/yr)')
thickAx.set_ylabel('Elevation\n(m asl)')
Expand All @@ -204,6 +259,11 @@
temp_cbar.set_label('Temperature (K)')
temp_cbar.ax.tick_params(labelsize=12)

if options.interp_thermal_forcing:
tf_cbar = plt.colorbar(thermal_forcing_plot)
tf_cbar.set_label('Thermal Forcing (C)')
tf_cbar.ax.tick_params(labelsize=12)

if (len(times) > 1):
time_cbar = plt.colorbar(cm.ScalarMappable(cmap='plasma'), ax=thickAx)
time_cbar.ax.tick_params(labelsize=12)
Expand All @@ -220,3 +280,4 @@
plt.show()

dataset.close()