diff --git a/compass/ocean/iceshelf.py b/compass/ocean/iceshelf.py index 8a69efa617..f34c03298d 100644 --- a/compass/ocean/iceshelf.py +++ b/compass/ocean/iceshelf.py @@ -11,34 +11,94 @@ from compass.model import partition, run_model -def compute_land_ice_pressure_and_draft(ssh, modify_mask, ref_density): +def compute_land_ice_pressure_from_thickness(land_ice_thickness, modify_mask, + land_ice_density=None): """ - Compute the pressure from and overlying ice shelf and the ice-shelf draft + Compute the pressure from an overlying ice shelf from ice thickness Parameters ---------- - ssh : xarray.DataArray - The sea surface height (the ice draft) + land_ice_thickness: xarray.DataArray + The ice thickness modify_mask : xarray.DataArray A mask that is 1 where ``landIcePressure`` can be deviate from 0 - ref_density : float + land_ice_density : float, optional + A reference density for land ice + + Returns + ------- + land_ice_pressure : xarray.DataArray + The pressure from the overlying land ice on the ocean + """ + gravity = constants['SHR_CONST_G'] + if land_ice_density is None: + land_ice_density = constants['SHR_CONST_RHOICE'] + land_ice_pressure = modify_mask * \ + numpy.maximum(land_ice_density * gravity * land_ice_thickness, 0.) + return land_ice_pressure + + +def compute_land_ice_pressure_from_draft(land_ice_draft, modify_mask, + ref_density=None): + """ + Compute the pressure from an overlying ice shelf from ice draft + + Parameters + ---------- + land_ice_draft : xarray.DataArray + The ice draft (sea surface height) + + modify_mask : xarray.DataArray + A mask that is 1 where ``landIcePressure`` can be deviate from 0 + + ref_density : float, optional A reference density for seawater displaced by the ice shelf Returns ------- - landIcePressure : xarray.DataArray + land_ice_pressure : xarray.DataArray The pressure from the overlying land ice on the ocean + """ + gravity = constants['SHR_CONST_G'] + if ref_density is None: + ref_density = constants['SHR_CONST_RHOSW'] + land_ice_pressure = \ + modify_mask * numpy.maximum(-ref_density * gravity * land_ice_draft, + 0.) + return land_ice_pressure + + +def compute_land_ice_draft_from_pressure(land_ice_pressure, modify_mask, + ref_density=None): + """ + Compute the ice-shelf draft associated with the pressure from an overlying + ice shelf + + Parameters + ---------- + land_ice_pressure : xarray.DataArray + The pressure from the overlying land ice on the ocean + + modify_mask : xarray.DataArray + A mask that is 1 where ``landIcePressure`` can be deviate from 0 - landIceDraft : xarray.DataArray - The ice draft, equal to the initial ``ssh`` + ref_density : float, optional + A reference density for seawater displaced by the ice shelf + + Returns + ------- + land_ice_draft : xarray.DataArray + The ice draft """ gravity = constants['SHR_CONST_G'] - landIcePressure = \ - modify_mask * numpy.maximum(-ref_density * gravity * ssh, 0.) - landIceDraft = ssh - return landIcePressure, landIceDraft + if ref_density is None: + ref_density = constants['SHR_CONST_RHOSW'] + land_ice_draft_array = \ + modify_mask * -land_ice_pressure / (ref_density * gravity) + land_ice_draft = land_ice_draft_array.transpose(*land_ice_pressure.dims) + return land_ice_draft def adjust_ssh(variable, iteration_count, step, update_pio=True, diff --git a/compass/ocean/tests/ice_shelf_2d/initial_state.py b/compass/ocean/tests/ice_shelf_2d/initial_state.py index d245f3c9c8..cb76da4fbf 100644 --- a/compass/ocean/tests/ice_shelf_2d/initial_state.py +++ b/compass/ocean/tests/ice_shelf_2d/initial_state.py @@ -5,7 +5,7 @@ from mpas_tools.mesh.conversion import convert, cull from mpas_tools.planar_hex import make_planar_hex_mesh -from compass.ocean.iceshelf import compute_land_ice_pressure_and_draft +from compass.ocean.iceshelf import compute_land_ice_pressure_from_draft from compass.ocean.vertical import init_vertical_coord from compass.step import Step @@ -103,8 +103,10 @@ def run(self): landIceFloatingMask = landIceMask.copy() ref_density = constants['SHR_CONST_RHOSW'] - landIcePressure, landIceDraft = compute_land_ice_pressure_and_draft( - ssh=ds.ssh, modify_mask=modify_mask, ref_density=ref_density) + landIceDraft = ds.ssh + landIcePressure = compute_land_ice_pressure_from_draft( + land_ice_draft=landIceDraft, modify_mask=modify_mask, + ref_density=ref_density) salinity = surface_salinity + ((bottom_salinity - surface_salinity) * (ds.zMid / (-bottom_depth))) diff --git a/compass/ocean/tests/isomip_plus/__init__.py b/compass/ocean/tests/isomip_plus/__init__.py index fdbb42bd9d..00102aa68e 100644 --- a/compass/ocean/tests/isomip_plus/__init__.py +++ b/compass/ocean/tests/isomip_plus/__init__.py @@ -36,7 +36,7 @@ def __init__(self, mpas_core): experiment=experiment, vertical_coordinate=vertical_coordinate, time_varying_forcing=True)) - for vertical_coordinate in ['z-star', 'sigma', 'single_layer']: + for vertical_coordinate in ['z-star', 'sigma']: self.add_test_case( IsomipPlusTest( test_group=self, resolution=resolution, @@ -50,7 +50,7 @@ def __init__(self, mpas_core): vertical_coordinate=vertical_coordinate, time_varying_forcing=True, thin_film_present=True)) - for vertical_coordinate in ['sigma', 'single_layer']: + for vertical_coordinate in ['z-star', 'sigma']: self.add_test_case( IsomipPlusTest( test_group=self, resolution=resolution, @@ -67,22 +67,7 @@ def __init__(self, mpas_core): time_varying_forcing=True, time_varying_load='decreasing', thin_film_present=True)) - for vertical_coordinate in ['sigma']: - self.add_test_case( - IsomipPlusTest( - test_group=self, resolution=resolution, - experiment=experiment, - vertical_coordinate=vertical_coordinate, - tidal_forcing=True, - thin_film_present=True)) - for vertical_coordinate in ['single_layer']: - self.add_test_case( - IsomipPlusTest( - test_group=self, resolution=resolution, - experiment=experiment, - vertical_coordinate=vertical_coordinate, - tidal_forcing=True, - thin_film_present=False)) + for vertical_coordinate in ['sigma', 'z-star']: self.add_test_case( IsomipPlusTest( test_group=self, resolution=resolution, diff --git a/compass/ocean/tests/isomip_plus/forward.py b/compass/ocean/tests/isomip_plus/forward.py index e6689f8130..5ca32ed052 100644 --- a/compass/ocean/tests/isomip_plus/forward.py +++ b/compass/ocean/tests/isomip_plus/forward.py @@ -1,12 +1,5 @@ -import os -import shutil - -import numpy as np -import xarray - from compass.model import run_model from compass.ocean.tests.isomip_plus.evap import update_evaporation_flux -from compass.ocean.tests.isomip_plus.viz.plot import MoviePlotter from compass.ocean.time import get_time_interval_string from compass.step import Step @@ -157,78 +150,6 @@ def run(self): run_model(self) - if self.name == 'performance': - # plot a few fields - plot_folder = f'{self.work_dir}/plots' - if os.path.exists(plot_folder): - shutil.rmtree(plot_folder) - - dsMesh = xarray.open_dataset(os.path.join(self.work_dir, - 'init.nc')) - ds = xarray.open_dataset(os.path.join(self.work_dir, 'output.nc')) - - section_y = self.config.getfloat('isomip_plus_viz', 'section_y') - # show progress only if we're not writing to a log file - show_progress = self.log_filename is None - plotter = MoviePlotter(inFolder=self.work_dir, - streamfunctionFolder=self.work_dir, - outFolder=plot_folder, sectionY=section_y, - dsMesh=dsMesh, ds=ds, expt=self.experiment, - showProgress=show_progress) - - plotter.plot_horiz_series(ds.ssh, 'ssh', 'ssh', - True, vmin=-700, vmax=0) - plotter.plot_3d_field_top_bot_section( - ds.temperature, nameInTitle='temperature', prefix='temp', - units='C', vmin=-2., vmax=1., cmap='cmo.thermal') - - plotter.plot_3d_field_top_bot_section( - ds.salinity, nameInTitle='salinity', prefix='salin', - units='PSU', vmin=33.8, vmax=34.7, cmap='cmo.haline') - tol = 1e-10 - dsIce = xarray.open_dataset( - os.path.join(self.work_dir, - 'land_ice_fluxes.nc')) - if 'topDragMagnitude' in dsIce.keys(): - plotter.plot_horiz_series( - dsIce.topDragMagnitude, - 'topDragMagnitude', 'topDragMagnitude', True, - vmin=0 + tol, vmax=np.max(dsIce.topDragMagnitude.values), - cmap_set_under='k') - if 'landIceHeatFlux' in dsIce.keys(): - plotter.plot_horiz_series( - dsIce.landIceHeatFlux, - 'landIceHeatFlux', 'landIceHeatFlux', True, - vmin=np.min(dsIce.landIceHeatFlux.values), - vmax=np.max(dsIce.landIceHeatFlux.values)) - if 'landIceInterfaceTemperature' in dsIce.keys(): - plotter.plot_horiz_series( - dsIce.landIceInterfaceTemperature, - 'landIceInterfaceTemperature', - 'landIceInterfaceTemperature', - True, - vmin=np.min(dsIce.landIceInterfaceTemperature.values), - vmax=np.max(dsIce.landIceInterfaceTemperature.values)) - if 'landIceFreshwaterFlux' in dsIce.keys(): - plotter.plot_horiz_series( - dsIce.landIceFreshwaterFlux, - 'landIceFreshwaterFlux', 'landIceFreshwaterFlux', True, - vmin=0 + tol, vmax=1e-4, - cmap_set_under='k', cmap_scale='log') - if 'landIceFraction' in dsIce.keys(): - plotter.plot_horiz_series( - dsIce.landIceFraction, - 'landIceFraction', 'landIceFraction', True, - vmin=0 + tol, vmax=1 - tol, - cmap='cmo.balance', - cmap_set_under='k', cmap_set_over='r') - if 'landIceFloatingFraction' in dsIce.keys(): - plotter.plot_horiz_series( - dsIce.landIceFloatingFraction, - 'landIceFloatingFraction', 'landIceFloatingFraction', - True, vmin=0 + tol, vmax=1 - tol, - cmap='cmo.balance', cmap_set_under='k', cmap_set_over='r') - if self.name == 'simulation': update_evaporation_flux( in_forcing_file='forcing_data_init.nc', diff --git a/compass/ocean/tests/isomip_plus/geom.py b/compass/ocean/tests/isomip_plus/geom.py index ce761a1582..3c52630511 100755 --- a/compass/ocean/tests/isomip_plus/geom.py +++ b/compass/ocean/tests/isomip_plus/geom.py @@ -111,11 +111,12 @@ def interpolate_geom(ds_mesh, ds_geom, min_ocean_fraction, thin_film_present): ds_geom.landIceGroundedFraction) # mask the topography to the ocean region before interpolation - for var in ['Z_bed', 'Z_ice_draft', 'landIceFraction', + for var in ['Z_bed', 'Z_ice_surface', 'Z_ice_draft', 'landIceFraction', 'landIceFloatingFraction', 'smoothedDraftMask']: ds_geom[var] = ds_geom[var] * ds_geom['oceanFraction'] fields = {'bottomDepthObserved': 'Z_bed', + 'landIceThickness': 'iceThickness', 'ssh': 'Z_ice_draft', 'oceanFracObserved': 'oceanFraction', 'landIceFraction': 'landIceFraction', @@ -156,7 +157,7 @@ def _get_geom_fields(ds_geom, ds_mesh, thin_film_present): y_cell = ds_mesh.yIsomipCell.values if thin_film_present: - ocean_fraction = - ds_geom['landFraction'] + 1.0 + ocean_fraction = xarray.where(ds_geom['Z_bed'] > 0., 0., 1.) else: ocean_fraction = (ds_geom['landIceFloatingFraction'] + ds_geom['openOceanFraction']) diff --git a/compass/ocean/tests/isomip_plus/initial_state.py b/compass/ocean/tests/isomip_plus/initial_state.py index 955651040b..7199f89e08 100644 --- a/compass/ocean/tests/isomip_plus/initial_state.py +++ b/compass/ocean/tests/isomip_plus/initial_state.py @@ -7,7 +7,11 @@ from mpas_tools.cime.constants import constants from mpas_tools.io import write_netcdf -from compass.ocean.iceshelf import compute_land_ice_pressure_and_draft +from compass.ocean.iceshelf import ( + compute_land_ice_draft_from_pressure, + compute_land_ice_pressure_from_draft, + compute_land_ice_pressure_from_thickness, +) from compass.ocean.tests.isomip_plus.geom import interpolate_geom from compass.ocean.tests.isomip_plus.viz.plot import MoviePlotter from compass.ocean.vertical import init_vertical_coord @@ -36,7 +40,7 @@ class InitialState(Step): Whether the run includes a thin film below grounded ice """ def __init__(self, test_case, resolution, experiment, vertical_coordinate, - time_varying_forcing, thin_film_present): + time_varying_forcing, thin_film_present, include_viz=False): """ Create the step @@ -61,6 +65,7 @@ def __init__(self, test_case, resolution, experiment, vertical_coordinate, Whether the run includes a thin film below grounded ice """ super().__init__(test_case=test_case, name='initial_state') + self.include_viz = include_viz self.resolution = resolution self.experiment = experiment self.vertical_coordinate = vertical_coordinate @@ -84,7 +89,8 @@ def run(self): """ ds, frac = self._compute_initial_condition() self._compute_restoring(ds, frac) - self._plot(ds) + if self.include_viz: + self._plot(ds) def _compute_initial_condition(self): config = self.config @@ -97,6 +103,7 @@ def _compute_initial_condition(self): section = config['isomip_plus'] min_land_ice_fraction = section.getfloat('min_land_ice_fraction') min_ocean_fraction = section.getfloat('min_ocean_fraction') + ice_density = section.getfloat('ice_density') ds_geom = xr.open_dataset('input_geometry_processed.nc') ds_mesh = xr.open_dataset('culled_mesh.nc') @@ -104,6 +111,8 @@ def _compute_initial_condition(self): ds = interpolate_geom(ds_mesh, ds_geom, min_ocean_fraction, thin_film_present) + ds['bottomDepth'] = -ds.bottomDepthObserved + ds['landIceFraction'] = \ ds['landIceFraction'].expand_dims(dim='Time', axis=0) ds['landIceFloatingFraction'] = \ @@ -125,22 +134,54 @@ def _compute_initial_condition(self): ds['landIceFraction'] = xr.where(mask, ds.landIceFraction, 0.) - ref_density = constants['SHR_CONST_RHOSW'] - landIcePressure, landIceDraft = compute_land_ice_pressure_and_draft( - ssh=ds.ssh, modify_mask=ds.ssh < 0., ref_density=ref_density) - - ds['landIcePressure'] = landIcePressure - ds['landIceDraft'] = landIceDraft + if thin_film_present: # compute full pressure in grounded regions + land_ice_thickness = ds.landIceThickness + land_ice_pressure_unscaled = \ + compute_land_ice_pressure_from_thickness( + land_ice_thickness=land_ice_thickness, + modify_mask=ds.ssh < 0., + land_ice_density=ice_density) + else: # assume floatation pressure everywhere + land_ice_draft = ds.ssh + land_ice_pressure_unscaled = compute_land_ice_pressure_from_draft( + land_ice_draft=land_ice_draft, modify_mask=land_ice_draft < 0.) if self.time_varying_forcing: - self._write_time_varying_forcing(ds_init=ds) - - ds['bottomDepth'] = -ds.bottomDepthObserved + scales = config.get('isomip_plus_forcing', 'scales') + scales = [float(scale) + for scale in scales.replace(',', ' ').split()] + land_ice_pressure = land_ice_pressure_unscaled * scales[0] + else: + land_ice_pressure = land_ice_pressure_unscaled + + if thin_film_present: + modify_mask = ds.bottomDepth > 0. + land_ice_draft = compute_land_ice_draft_from_pressure( + land_ice_pressure=land_ice_pressure, + modify_mask=modify_mask, + ref_density=1028.) + land_ice_draft = np.maximum(land_ice_draft, + -ds.bottomDepth) + ds['ssh'] = land_ice_draft + + # Previously, we didn't apply max draft at seafloor but we want this + # for W&D + # This assumes that for MALI forcing, landIceDraft = bottomDepth in + # grounded regions + ds['landIceDraft'] = land_ice_draft + # We need to add the time dimension for plotting purposes + ds['landIcePressure'] = land_ice_pressure.expand_dims( + dim='Time', axis=0) - section = config['isomip_plus'] + if self.time_varying_forcing: + self._write_time_varying_forcing( + ds_init=ds, + ice_density=ice_density, + land_ice_pressure_unscaled=land_ice_pressure_unscaled) # Deepen the bottom depth to maintain the minimum water-column # thickness + section = config['isomip_plus'] min_column_thickness = section.getfloat('min_column_thickness') min_layer_thickness = section.getfloat('min_layer_thickness') min_levels = section.getint('minimum_levels') @@ -149,44 +190,47 @@ def _compute_initial_condition(self): min_depth = -ds.ssh + min_column_thickness ds['bottomDepth'] = np.maximum(ds.bottomDepth, min_depth) print(f'Adjusted bottomDepth for ' - f'{np.sum(ds.bottomDepth.values (x_max - 0.6 * self.resolution * 1e3), 1.0, - 0.0) - else: - ds_forcing['tidalInputMask'] = xr.zeros_like(frac) + x_max = np.max(ds.xIsomipCell.values) + ds_forcing['tidalInputMask'] = xr.where( + ds.xIsomipCell > (x_max - 0.6 * self.resolution * 1e3), 1.0, + 0.0) write_netcdf(ds_forcing, 'init_mode_forcing_data.nc') - def _write_time_varying_forcing(self, ds_init): + def _write_time_varying_forcing(self, ds_init, ice_density, + land_ice_pressure_unscaled): """ - Write time-varying land-ice forcing and update the initial condition + Write time-varying land-ice forcing """ config = self.config + ds_forcing = xr.Dataset() dates = config.get('isomip_plus_forcing', 'dates') - dates = [date.ljust(64) for date in dates.replace(',', ' ').split()] + dates = [date.ljust(64) + for date in dates.replace(',', ' ').split()] scales = config.get('isomip_plus_forcing', 'scales') - scales = [float(scale) for scale in scales.replace(',', ' ').split()] - - ds_out = xr.Dataset() - ds_out['xtime'] = ('Time', dates) - ds_out['xtime'] = ds_out.xtime.astype('S') - - landIceDraft = list() - landIcePressure = list() - landIceFraction = list() - landIceFloatingFraction = list() - - for scale in scales: - landIceDraft.append(scale * ds_init.landIceDraft) - landIcePressure.append(scale * ds_init.landIcePressure) - landIceFraction.append(ds_init.landIceFraction) - landIceFloatingFraction.append(ds_init.landIceFloatingFraction) - - ds_out['landIceDraftForcing'] = xr.concat(landIceDraft, 'Time') - ds_out.landIceDraftForcing.attrs['units'] = 'm' - ds_out.landIceDraftForcing.attrs['long_name'] = \ + scales = [float(scale) + for scale in scales.replace(',', ' ').split()] + + # The initial condition already has the first scale value applied + land_ice_pressure_forcing = ds_init.landIcePressure.copy() + land_ice_fraction_forcing = ds_init.landIceFraction.copy() + land_ice_floating_fraction_forcing = \ + ds_init.landIceFloatingFraction.copy() + land_ice_draft_unscaled = ds_init.landIceDraft.copy() + land_ice_draft_scaled = land_ice_draft_unscaled * scales[0] + land_ice_draft_limited = np.maximum(land_ice_draft_scaled, + -ds_init.bottomDepth) + land_ice_draft_forcing = land_ice_draft_limited + print(f'Grounded cells at {scales[0]}: ' + f'{np.sum(land_ice_draft_limited == -ds_init.bottomDepth)}') + + # We add additional time slices for the remaining scale values + for scale in scales[1:]: + land_ice_pressure_forcing = xr.concat( + [land_ice_pressure_forcing, + scale * land_ice_pressure_unscaled], + 'Time') + land_ice_fraction_forcing = xr.concat( + [land_ice_fraction_forcing, ds_init.landIceFraction], + 'Time') + # Since floating fraction does not change, none of the thin film + # cases allow for the area undergoing melting to change + land_ice_floating_fraction_forcing = xr.concat( + [land_ice_floating_fraction_forcing, + ds_init.landIceFloatingFraction], + 'Time') + + if self.thin_film_present: + land_ice_draft_scaled = compute_land_ice_draft_from_pressure( + land_ice_pressure=scale * land_ice_pressure_unscaled, + modify_mask=ds_init.bottomDepth > 0.) + else: + # Just scale draft in the same manner as pressure + land_ice_draft_scaled = land_ice_draft_unscaled * scale + land_ice_draft_limited = np.maximum(land_ice_draft_scaled, + -ds_init.bottomDepth) + print(f'Grounded cells at {scale}: ' + f'{np.sum(land_ice_draft_limited == -ds_init.bottomDepth)}') + # Set the maximum land ice draft in grounded regions + land_ice_draft_forcing = xr.concat( + [land_ice_draft_forcing, + land_ice_draft_limited], + 'Time') + + ds_forcing['xtime'] = xr.DataArray(data=dates, + dims=('Time')).astype('S') + ds_forcing['landIceDraftForcing'] = land_ice_draft_forcing + ds_forcing.landIceDraftForcing.attrs['units'] = 'm' + ds_forcing.landIceDraftForcing.attrs['long_name'] = \ 'The approximate elevation of the land ice-ocean interface' - ds_out['landIcePressureForcing'] = \ - xr.concat(landIcePressure, 'Time') - ds_out.landIcePressureForcing.attrs['units'] = 'm' - ds_out.landIcePressureForcing.attrs['long_name'] = \ - 'Pressure from the weight of land ice at the ice-ocean interface' - ds_out['landIceFractionForcing'] = \ - xr.concat(landIceFraction, 'Time') - ds_out.landIceFractionForcing.attrs['long_name'] = \ + ds_forcing['landIcePressureForcing'] = land_ice_pressure_forcing + ds_forcing.landIcePressureForcing.attrs['units'] = 'm' + ds_forcing.landIcePressureForcing.attrs['long_name'] = \ + 'Pressure from the weight of land ice at the ice-ocean ' \ + 'interface' + ds_forcing['landIceFractionForcing'] = land_ice_fraction_forcing + ds_forcing.landIceFractionForcing.attrs['long_name'] = \ 'The fraction of each cell covered by land ice' - ds_out['landIceFloatingFractionForcing'] = \ - xr.concat(landIceFloatingFraction, 'Time') - ds_out.landIceFloatingFractionForcing.attrs['long_name'] = \ + ds_forcing['landIceFloatingFractionForcing'] = \ + land_ice_floating_fraction_forcing + ds_forcing.landIceFloatingFractionForcing.attrs['long_name'] = \ 'The fraction of each cell covered by floating land ice' - write_netcdf(ds_out, 'land_ice_forcing.nc') - ds_init['landIceDraft'] = scales[0] * ds_init.landIceDraft - ds_init['ssh'] = ds_init.landIceDraft - ds_init['landIcePressure'] = scales[0] * ds_init.landIcePressure + ds_forcing.encoding['unlimited_dims'] = {'Time'} + # write_netcdf is not used here because it does not yet support + # multiple time levels + ds_forcing.to_netcdf('land_ice_forcing.nc') diff --git a/compass/ocean/tests/isomip_plus/isomip_plus.cfg b/compass/ocean/tests/isomip_plus/isomip_plus.cfg index 355daa71f8..4c5c827bdb 100644 --- a/compass/ocean/tests/isomip_plus/isomip_plus.cfg +++ b/compass/ocean/tests/isomip_plus/isomip_plus.cfg @@ -64,7 +64,7 @@ minimum_levels = 3 min_layer_thickness = 0.0 # Minimum thickness of the initial ocean column (to prevent 'drying') -min_column_thickness = 1e-3 +min_column_thickness = 1.1e-2 # Minimum fraction of a cell that contains ocean (as opposed to land or # grounded land ice) in order for it to be an active ocean cell. @@ -78,6 +78,9 @@ min_smoothed_draft_mask = 0.01 # considered a land-ice cell by MPAS-Ocean (landIceMask == 1). min_land_ice_fraction = 0.5 +# the density of ice prescribed in ISOMIP+ +ice_density = 918 + # the initial temperature at the sea surface init_top_temp = -1.9 # the initial temperature at the sea floor @@ -123,7 +126,7 @@ dates = 0001-01-01_00:00:00, 0002-01-01_00:00:00, 0003-01-01_00:00:00 # the amount by which the initial landIcePressure and landIceDraft are scaled # at each date -scales = 0.1, 1.0, 1.0 +scales = 1.0, 0.9, 0.9 # config options for computing ISOMIP+ streamfunctions [isomip_plus_streamfunction] @@ -141,6 +144,9 @@ plot_haney = True # whether to plot the barotropic and overturning streamfunctions plot_streamfunctions = True +# whether to plot output from the performance step, usually for debugging +plot_performance_fields = True + # frames per second for movies frames_per_second = 30 diff --git a/compass/ocean/tests/isomip_plus/isomip_plus_test.py b/compass/ocean/tests/isomip_plus/isomip_plus_test.py index cf17731a01..9cbdae286a 100644 --- a/compass/ocean/tests/isomip_plus/isomip_plus_test.py +++ b/compass/ocean/tests/isomip_plus/isomip_plus_test.py @@ -140,7 +140,8 @@ def __init__(self, test_group, resolution, experiment, experiment=experiment, vertical_coordinate=vertical_coordinate, time_varying_forcing=time_varying_forcing, - thin_film_present=thin_film_present)) + thin_film_present=thin_film_present, + include_viz=thin_film_present)) self.add_step( SshAdjustment(test_case=self, resolution=resolution, vertical_coordinate=vertical_coordinate, @@ -209,9 +210,9 @@ def configure(self): config.set('isomip_plus', 'min_column_thickness', '1e-3') if time_varying_load == 'increasing': - config.set('isomip_plus_forcing', 'scales', '1.0, 2.0, 2.0') + config.set('isomip_plus_forcing', 'scales', '1.0, 1.05, 1.1') if time_varying_load == 'decreasing': - config.set('isomip_plus_forcing', 'scales', '1.0, 0.0, 0.0') + config.set('isomip_plus_forcing', 'scales', '1.0, 0.95, 0.9') if experiment in ['Ocean0', 'Ocean2', 'Ocean3']: # warm initial conditions @@ -269,7 +270,7 @@ def configure(self): if vertical_coordinate == 'sigma': if time_varying_load in ['increasing', 'decreasing']: - config.set('vertical_grid', 'vert_levels', '3') + config.set('vertical_grid', 'vert_levels', '10') else: # default to 10 vertical levels instead of 36 config.set('vertical_grid', 'vert_levels', '10') @@ -303,10 +304,10 @@ def _get_time_steps(config, resolution, thin_film_present, tidal_forcing): dt_per_km = 120. if tidal_forcing or thin_film_present: - dt_per_km = min(dt_per_km, 10.) + dt_per_km = min(dt_per_km, 6.) config.set('isomip_plus', 'dt_per_km', f'{dt_per_km}') - dt_btr_per_km = min(dt_per_km / 5., 5.) + dt_btr_per_km = min(dt_per_km / 3., 3.) config.set('isomip_plus', 'dt_btr_per_km', f'{dt_btr_per_km}') return diff --git a/compass/ocean/tests/isomip_plus/namelist.forward_and_ssh_adjust b/compass/ocean/tests/isomip_plus/namelist.forward_and_ssh_adjust index 939e440cb3..4c977000b7 100644 --- a/compass/ocean/tests/isomip_plus/namelist.forward_and_ssh_adjust +++ b/compass/ocean/tests/isomip_plus/namelist.forward_and_ssh_adjust @@ -22,6 +22,7 @@ config_land_ice_flux_explicit_topDragCoeff = 2.5e-3 config_land_ice_flux_tidal_Jourdain_U0 = 1e-2 config_land_ice_flux_jenkins_heat_transfer_coefficient = 0.0194 config_land_ice_flux_jenkins_salt_transfer_coefficient = 0.00055428571 +config_use_implicit_top_drag = .true. config_implicit_constant_bottom_drag_coeff = 2.5e-3 config_eos_type = 'linear' config_eos_linear_alpha = 0.03836 diff --git a/compass/ocean/tests/isomip_plus/namelist.single_layer.forward_and_ssh_adjust b/compass/ocean/tests/isomip_plus/namelist.single_layer.forward_and_ssh_adjust deleted file mode 100644 index 2e6e126dc0..0000000000 --- a/compass/ocean/tests/isomip_plus/namelist.single_layer.forward_and_ssh_adjust +++ /dev/null @@ -1,11 +0,0 @@ -config_time_integrator='RK4' -config_dt='00:00:10' -config_bottom_drag_mode = 'explicit' -config_explicit_bottom_drag_coeff = 3.0e-3 -config_disable_thick_vadv = .true. -config_disable_thick_sflux = .true. -config_disable_vel_hmix = .true. -config_disable_vel_vmix = .true. -config_disable_vel_vadv = .true. -config_disable_tr_all_tend = .true. -config_pressure_gradient_type = 'ssh_gradient' diff --git a/compass/ocean/tests/isomip_plus/namelist.thin_film.forward_and_ssh_adjust b/compass/ocean/tests/isomip_plus/namelist.thin_film.forward_and_ssh_adjust index 76c2443f7c..8c3be8b797 100644 --- a/compass/ocean/tests/isomip_plus/namelist.thin_film.forward_and_ssh_adjust +++ b/compass/ocean/tests/isomip_plus/namelist.thin_film.forward_and_ssh_adjust @@ -1,7 +1,13 @@ config_time_integrator='RK4' -config_dt='00:00:10' +config_thickness_flux_type='upwind' +config_land_ice_flux_mode='standalone' +config_vert_advection_method='flux-form' config_use_wetting_drying=.true. -config_prevent_drying=.true. +config_drying_safety_height=1e-10 config_drying_min_cell_height=1.0e-3 +config_zero_drying_velocity_ramp=.false. +config_zero_drying_velocity_ramp_hmin=1e-3 +config_zero_drying_velocity_ramp_hmax=1e-1 +config_use_ssh_gradient_wetting_drying=.false. +config_prevent_drying=.true. config_zero_drying_velocity = .true. -config_thickness_flux_type='upwind' diff --git a/compass/ocean/tests/isomip_plus/process_geom.py b/compass/ocean/tests/isomip_plus/process_geom.py index 1293cc78cd..3f07250b11 100644 --- a/compass/ocean/tests/isomip_plus/process_geom.py +++ b/compass/ocean/tests/isomip_plus/process_geom.py @@ -171,10 +171,18 @@ def _smooth_geometry(land_fraction, ds, filter_sigma, threshold=0.01): mode='constant', cval=0.) bed[mask] /= smoothed_mask[mask] + # We only use the ice surface where the ocean is present to compute + # land ice pressure + ice_thickness = filters.gaussian_filter( + ds.iceThickness * ocean_fraction, filter_sigma, mode='constant', + cval=0.) + ice_thickness[mask] /= smoothed_mask[mask] + smoothed_draft_mask = filters.gaussian_filter( ds.landIceFloatingFraction, filter_sigma, mode='constant', cval=0.) smoothed_draft_mask[mask] /= smoothed_mask[mask] ds['Z_ice_draft'] = (('y', 'x'), draft) ds['Z_bed'] = (('y', 'x'), bed) + ds['iceThickness'] = (('y', 'x'), ice_thickness) ds['smoothedDraftMask'] = (('y', 'x'), smoothed_draft_mask) diff --git a/compass/ocean/tests/isomip_plus/streams.forward.template b/compass/ocean/tests/isomip_plus/streams.forward.template index 4eff72b570..e6d26d6f52 100644 --- a/compass/ocean/tests/isomip_plus/streams.forward.template +++ b/compass/ocean/tests/isomip_plus/streams.forward.template @@ -42,6 +42,7 @@ + @@ -64,6 +65,7 @@ + + - + diff --git a/compass/ocean/tests/isomip_plus/viz/__init__.py b/compass/ocean/tests/isomip_plus/viz/__init__.py index 5b9d745e3a..d9adf24a6e 100644 --- a/compass/ocean/tests/isomip_plus/viz/__init__.py +++ b/compass/ocean/tests/isomip_plus/viz/__init__.py @@ -53,8 +53,10 @@ def run(self): config = self.config section = config['isomip_plus_viz'] plot_streamfunctions = section.getboolean('plot_streamfunctions') + plot_performance_fields = section.getboolean('plot_performance_fields') plot_haney = section.getboolean('plot_haney') frames_per_second = section.getint('frames_per_second') + # output_slices = section.getint('output_slices') movie_format = section.get('movie_format') section_y = section.getfloat('section_y') @@ -65,108 +67,331 @@ def run(self): show_progress = self.log_filename is None expt = self.experiment - if self.tidal_forcing: - sim_dir = '../performance' - else: - sim_dir = '../simulation' - if not os.path.exists(f'{sim_dir}/timeSeriesStatsMonthly.0001-01-01.nc'): # noqa: E501 - sim_dir = '../performance' + sim_dir = '../simulation' streamfunction_dir = '../streamfunction' out_dir = '.' dsMesh = xarray.open_dataset(f'{sim_dir}/init.nc') dsOut = xarray.open_dataset(f'{sim_dir}/output.nc') - + dsIce = xarray.open_dataset(f'{sim_dir}/land_ice_fluxes.nc') + if dsOut.sizes['Time'] < 50: + print('Cropping dsOut') + dsOut = dsOut.isel(Time=slice(-31, -1)) + nTime = dsOut.sizes['Time'] + if nTime != dsIce.sizes['Time']: + print('Cropping dsIce to match dsOut') + dsIce = dsIce.isel(Time=slice(-nTime - 1, -1)) + dsOut['landIceDraft'] = dsIce.landIceDraft plotter = MoviePlotter(inFolder=sim_dir, streamfunctionFolder=streamfunction_dir, outFolder=f'{out_dir}/plots', expt=expt, sectionY=section_y, dsMesh=dsMesh, ds=dsOut, showProgress=show_progress) - - if 'time_varying' in expt: - plotter.plot_horiz_series(dsOut.ssh, 'ssh', 'ssh', True, - cmap='cmo.curl') - delice = dsOut.landIcePressure - dsOut.landIcePressure[0, :] - plotter.plot_horiz_series(delice, 'delLandIcePressure', - 'delLandIcePressure', True, - cmap='cmo.curl') - plotter.plot_horiz_series(dsOut.velocityX[:, :, 0], 'u', 'u', True, - cmap='cmo.balance', vmin=-5e-1, vmax=5e-1) - plotter.plot_horiz_series(dsOut.velocityY[:, :, 0], 'v', 'v', True, - cmap='cmo.balance', vmin=-5e-1, vmax=5e-1) + plotter.plot_3d_field_top_bot_section(dsOut.velocityX, 'u', 'u', True, + cmap='cmo.balance', + vmin=-5e-1, vmax=5e-1) + plotter.plot_3d_field_top_bot_section(dsOut.velocityY, 'v', 'v', True, + cmap='cmo.balance', + vmin=-5e-1, vmax=5e-1) + + # Compute thermal forcing for whole water column + # landIceInterfaceSalinity and landIceInterfaceTemperature could be + # used for thermal forcing at the interface + salinity = dsOut.salinity.isel(nVertLevels=0) + pressure = dsIce.landIcePressure + temperature = dsOut.temperature.isel(nVertLevels=0) + coeff_0 = 6.22e-2 + coeff_S = -5.63e-2 + coeff_p = -7.43e-8 + coeff_pS = -1.74e-10 + coeff_mushy = 0. + ocn_freezing_temperature = \ + coeff_0 + coeff_S * salinity + \ + coeff_p * pressure + \ + coeff_pS * pressure * salinity + \ + coeff_mushy * salinity / (1.0 - salinity / 1e3) + dsIce['thermalForcing'] = temperature - ocn_freezing_temperature + + if dsIce.sizes['Time'] > 14: + dsIce = dsIce.isel(Time=slice(-13, -12)) + if 'xIsomipCell' in dsMesh.keys(): + xCell = dsMesh.xIsomipCell / 1.e3 + else: + xCell = dsMesh.xCell / 1.e3 + + if dsOut.sizes['Time'] > 24: + days = 30 + samples_per_day = 1 + dsAve = dsOut.isel(Time=slice(-int(days * samples_per_day), -1)) + velocityMagSq = (dsAve.velocityX.isel(nVertLevels=0)**2. + + dsAve.velocityX.isel(nVertLevels=0)**2.) + rmsVelocity = xarray.DataArray( + data=np.sqrt(velocityMagSq.mean('Time').values), + dims=['nCells']).expand_dims(dim='Time', axis=0) + plotter.plot_horiz_series(rmsVelocity, + 'rmsVelocity', 'rmsVelocity', + False, vmin=1.e-2, vmax=1.e0, + cmap='cmo.speed', cmap_scale='log') + cavityMask = np.where(dsMesh.landIceFloatingMask == 1)[0] + dsCavity = dsIce.isel(nCells=cavityMask) + rmsVelocityCavity = rmsVelocity.isel(nCells=cavityMask) + xMasked = xCell.isel(nCells=cavityMask) + _plot_transect(dsCavity, rmsVelocityCavity, xMasked) + + maxBottomDepth = dsMesh.bottomDepth.max().values + plotter.plot_horiz_series(dsOut.ssh, 'ssh', 'ssh', True, + vmin=-maxBottomDepth, vmax=0) + if 'layerThickness' in dsOut.keys(): + plotter.plot_layer_interfaces() plotter.plot_horiz_series(dsOut.ssh + dsMesh.bottomDepth, 'H', 'H', True, vmin=min_column_thickness + 1e-10, - vmax=700, cmap_set_under='r', + vmax=500, cmap_set_under='k', cmap_scale='log') - - if 'tidal' in expt: - delssh = dsOut.ssh - dsOut.ssh[0, :] - plotter.plot_horiz_series(delssh, 'delssh', 'delssh', True, - cmap='cmo.curl', vmin=-1, vmax=1) + plotter.plot_horiz_series(dsOut.landIcePressure, + 'landIcePressure', 'landIcePressure', + True, vmin=1e5, vmax=1e7, cmap_scale='log') + plotter.plot_horiz_series(dsOut.surfacePressure, + 'surfacePressure', 'surfacePressure', + True, vmin=1e5, vmax=1e7, cmap_scale='log') + delSurfacePressure = dsOut.landIcePressure - dsOut.surfacePressure + plotter.plot_horiz_series(delSurfacePressure, + 'delSurfacePressure', 'delSurfacePressure', + True, vmin=1e5, vmax=1e7, cmap_scale='log') + delice = dsOut.landIcePressure - dsMesh.landIcePressure.isel(Time=0) + plotter.plot_horiz_series(delice, 'delLandIcePressure', + 'delLandIcePressure', True, + vmin=-1e6, vmax=1e6, cmap='cmo.balance') + delssh = dsOut.ssh - dsMesh.ssh.isel(Time=0) + plotter.plot_horiz_series(delssh, 'ssh - ssh_init', 'delssh_init', + True, cmap='cmo.curl', vmin=-1, vmax=1) + # Cannot currently plot because it is on edges + for t in range(dsOut.sizes['Time']): + fig = plt.figure() + sc = plt.scatter( + dsOut.xEdge, dsOut.yEdge, 10, + dsOut.wettingVelocityFactor.isel(nVertLevels=0, Time=t), + vmin=0, vmax=1, cmap='cmo.thermal') + current_cmap = sc.get_cmap() + current_cmap.set_under('k') + current_cmap.set_over('r') + plt.colorbar() + fig.gca().set_aspect('equal') + plt.savefig(f'wettingVelocityFactor_{t:02g}.png') + plt.close(fig) wct = dsOut.ssh + dsMesh.bottomDepth - idx_thin = np.logical_and(wct[0, :] > 1e-1, - wct[0, :] < 1) - wct_thin = wct[:, idx_thin] - wct_mean = wct_thin.mean(dim='nCells').values - time = dsOut.daysSinceStartOfSim.values - fig = plt.figure() - plt.plot(time, wct_mean, '.') - fig.set_xlabel('Time (days)') - fig.set_ylabel('Mean thickness of thin film (m)') - plt.savefig('wct_thin_t.png') - plt.close() - plotter.plot_horiz_series(wct, 'H', 'H', True, vmin=min_column_thickness + 1e-10, - vmax=700, cmap_set_under='r', + vmax=700, cmap_set_under='k', cmap_scale='log') - - if os.path.exists(f'{sim_dir}/timeSeriesStatsMonthly.0001-01-01.nc'): + if os.path.exists('../simulation/land_ice_fluxes.nc'): ds = xarray.open_mfdataset( - '{}/timeSeriesStatsMonthly*.nc'.format(sim_dir), + '../simulation/land_ice_fluxes.nc', concat_dim='Time', combine='nested') - - if plot_haney: - _compute_and_write_haney_number(dsMesh, ds, out_dir, - showProgress=show_progress) - - tsPlotter = TimeSeriesPlotter(inFolder=sim_dir, - outFolder='{}/plots'.format(out_dir), - expt=expt) - tsPlotter.plot_melt_time_series() - tsPlotter = TimeSeriesPlotter( - inFolder=sim_dir, - outFolder='{}/timeSeriesBelow300m'.format(out_dir), - expt=expt) - tsPlotter.plot_melt_time_series(sshMax=-300.) - + if ds.sizes['Time'] > 50 or \ + ds.sizes['Time'] != dsOut.sizes['Time']: + # assumes ds.sizes['Time'] > dsOut.sizes['Time'] + ds = ds.isel(Time=slice(-31, -1)) + dsOut = dsOut.isel(Time=slice(-31, -1)) + if ds.sizes['Time'] != dsOut.sizes['Time']: + print('error in time selection') + return + ds['layerThickness'] = dsOut.layerThickness mPlotter = MoviePlotter(inFolder=sim_dir, streamfunctionFolder=streamfunction_dir, outFolder='{}/plots'.format(out_dir), expt=expt, sectionY=section_y, dsMesh=dsMesh, ds=ds, showProgress=show_progress) - - mPlotter.plot_layer_interfaces() - - if plot_streamfunctions: - mPlotter.plot_barotropic_streamfunction() - mPlotter.plot_overturning_streamfunction() - - if plot_haney: - mPlotter.plot_haney_number(haneyFolder=out_dir) - - mPlotter.plot_melt_rates() - mPlotter.plot_ice_shelf_boundary_variables() - mPlotter.plot_temperature() - mPlotter.plot_salinity() - mPlotter.plot_potential_density() - - mPlotter.images_to_movies(outFolder='{}/movies'.format(out_dir), - framesPerSecond=frames_per_second, - extension=movie_format) + _plot_land_ice_variables(ds, mPlotter, maxBottomDepth) + + ds = xarray.open_mfdataset( + '{}/timeSeriesStatsMonthly*.nc'.format(sim_dir), + concat_dim='Time', combine='nested') + + if plot_haney: + _compute_and_write_haney_number(dsMesh, ds, out_dir, + showProgress=show_progress) + + tsPlotter = TimeSeriesPlotter(inFolder=sim_dir, + outFolder='{}/plots'.format(out_dir), + expt=expt) + tsPlotter.plot_melt_time_series() + tsPlotter.plot_melt_time_series(wctMax=20.) + tsPlotter = TimeSeriesPlotter( + inFolder=sim_dir, + outFolder='{}/timeSeriesBelow300m'.format(out_dir), + expt=expt) + tsPlotter.plot_melt_time_series(sshMax=-300.) + + mPlotter = MoviePlotter(inFolder=sim_dir, + streamfunctionFolder=streamfunction_dir, + outFolder='{}/plots'.format(out_dir), + expt=expt, sectionY=section_y, + dsMesh=dsMesh, ds=ds, + showProgress=show_progress) + + mPlotter.plot_layer_interfaces() + + if plot_streamfunctions: + mPlotter.plot_barotropic_streamfunction() + mPlotter.plot_overturning_streamfunction() + + if plot_haney: + mPlotter.plot_haney_number(haneyFolder=out_dir) + + mPlotter.plot_melt_rates() + mPlotter.plot_ice_shelf_boundary_variables() + mPlotter.plot_temperature() + mPlotter.plot_salinity() + mPlotter.plot_potential_density() + + mPlotter.images_to_movies(outFolder=f'{out_dir}/movies', + framesPerSecond=frames_per_second, + extension=movie_format) + + if plot_performance_fields: + plot_folder = f'{out_dir}/performance_plots' + min_column_thickness = self.config.getfloat('isomip_plus', + 'min_column_thickness') + + ds = xarray.open_dataset('../performance/output.nc') + # show progress only if we're not writing to a log file + show_progress = self.log_filename is None + plotter = MoviePlotter(inFolder=self.work_dir, + streamfunctionFolder=self.work_dir, + outFolder=plot_folder, sectionY=section_y, + dsMesh=dsMesh, ds=ds, expt=self.experiment, + showProgress=show_progress) + + bottomDepth = ds.bottomDepth.expand_dims(dim='Time', axis=0) + plotter.plot_horiz_series(ds.ssh.isel(Time=-1) + bottomDepth, + 'H', 'H', True, + vmin=min_column_thickness, vmax=700, + cmap_set_under='r', cmap_scale='log') + plotter.plot_horiz_series(ds.ssh, 'ssh', 'ssh', + True, vmin=-700, vmax=0) + plotter.plot_3d_field_top_bot_section( + ds.temperature, nameInTitle='temperature', prefix='temp', + units='C', vmin=-2., vmax=1., cmap='cmo.thermal') + + plotter.plot_3d_field_top_bot_section( + ds.salinity, nameInTitle='salinity', prefix='salin', + units='PSU', vmin=33.8, vmax=34.7, cmap='cmo.haline') + + dsIce = xarray.open_dataset('../performance/land_ice_fluxes.nc') + _plot_land_ice_variables(dsIce, plotter, maxBottomDepth) + + +def _plot_land_ice_variables(ds, mPlotter, maxBottomDepth): + tol = 1e-10 + mPlotter.plot_horiz_series(ds.landIceFrictionVelocity, + 'landIceFrictionVelocity', + 'frictionVelocity', + True) + mPlotter.plot_horiz_series(ds.landIceFreshwaterFlux, + 'landIceFreshwaterFlux', 'melt', + True) + mPlotter.plot_horiz_series(ds.landIceFloatingFraction, + 'landIceFloatingFraction', + 'landIceFloatingFraction', + True, vmin=1e-16, vmax=1, + cmap_set_under='k') + mPlotter.plot_horiz_series(ds.landIceDraft, + 'landIceDraft', 'landIceDraft', + True, vmin=-maxBottomDepth, vmax=0.) + if 'topDragMagnitude' in ds.keys(): + mPlotter.plot_horiz_series( + ds.topDragMagnitude, + 'topDragMagnitude', 'topDragMagnitude', True, + vmin=0 + tol, vmax=np.max(ds.topDragMagnitude.values), + cmap_set_under='k') + if 'landIceHeatFlux' in ds.keys(): + mPlotter.plot_horiz_series( + ds.landIceHeatFlux, + 'landIceHeatFlux', 'landIceHeatFlux', True, + vmin=np.min(ds.landIceHeatFlux.values), + vmax=np.max(ds.landIceHeatFlux.values)) + if 'landIceInterfaceTemperature' in ds.keys(): + mPlotter.plot_horiz_series( + ds.landIceInterfaceTemperature, + 'landIceInterfaceTemperature', + 'landIceInterfaceTemperature', + True, + vmin=np.min(ds.landIceInterfaceTemperature.values), + vmax=np.max(ds.landIceInterfaceTemperature.values)) + if 'landIceFreshwaterFlux' in ds.keys(): + mPlotter.plot_horiz_series( + ds.landIceFreshwaterFlux, + 'landIceFreshwaterFlux', 'landIceFreshwaterFlux', True, + vmin=0 + tol, vmax=1e-4, + cmap_set_under='k', cmap_scale='log') + if 'landIceFraction' in ds.keys(): + mPlotter.plot_horiz_series( + ds.landIceFraction, + 'landIceFraction', 'landIceFraction', True, + vmin=0 + tol, vmax=1 - tol, + cmap='cmo.balance', + cmap_set_under='k', cmap_set_over='r') + if 'landIceFloatingFraction' in ds.keys(): + mPlotter.plot_horiz_series( + ds.landIceFloatingFraction, + 'landIceFloatingFraction', 'landIceFloatingFraction', + True, vmin=0 + tol, vmax=1 - tol, + cmap='cmo.balance', cmap_set_under='k', cmap_set_over='r') + mPlotter.plot_melt_rates() + mPlotter.plot_ice_shelf_boundary_variables() + + +def _plot_transect(dsIce, rmsVelocity, xCell, outFolder='plots'): + dx = 2. + xmin = xCell.min() + xmax = xCell.max() + xbins = np.arange(xmin, xmax, dx) + for time_slice in range(dsIce.sizes['Time']): + title = dsIce.xtime.isel(Time=time_slice).values + meltTransect = np.zeros_like(xbins) + landIceDraftTransect = np.zeros_like(xbins) + frictionVelocityTransect = np.zeros_like(xbins) + temperatureTransect = np.zeros_like(xbins) + thermalForcingTransect = np.zeros_like(xbins) + rmsVelocityTransect = np.zeros_like(xbins) + for i, xmin in enumerate(xbins): + binMask = np.where(np.logical_and(xCell >= xmin, + xCell < xmin + dx))[0] + if (np.sum(binMask) < 1): + continue + dsTransect = dsIce.isel(nCells=binMask, Time=time_slice) + meltTransect[i] = dsTransect.landIceFreshwaterFlux.mean() + landIceDraftTransect[i] = dsTransect.landIceDraft.mean() + frictionVelocityTransect[i] = \ + dsTransect.landIceFrictionVelocity.mean() + temperatureTransect[i] = \ + dsTransect.landIceInterfaceTemperature.mean() + thermalForcingTransect[i] = dsTransect.thermalForcing.mean() + rmsVelocityTransect[i] = \ + np.mean(rmsVelocity.isel(Time=time_slice, nCells=binMask)) + + fig, ax = plt.subplots(4, 1, sharex=True, figsize=(4, 8)) + color = 'darkgreen' + x = xbins - xbins[0] + ax[0].set_title(title) + ax[0].plot(x, landIceDraftTransect, color=color) + ax[0].set_ylabel('Land ice draft (m)') + secPerYear = 365 * 24 * 60 * 60 + density = 1026. + ax[1].plot(x, meltTransect * secPerYear / density, color=color) + ax[1].set_ylabel('Melt rate (m/yr)') + ax[2].plot(x, frictionVelocityTransect, color=color) + ax[2].set_ylabel('Friction velocity (m/s)') + ax[3].plot(x, thermalForcingTransect, color=color) + ax[3].set_ylabel(r'ThermalForcing ($^{\circ}$C)') + ax[3].set_xlabel('Distance along ice flow (km)') + plt.tight_layout() + plt.savefig(f'{outFolder}/meanTransect_tend{time_slice:03g}.png', + dpi=600, bbox_inches='tight', transparent=True) + plt.close(fig) def file_complete(ds, fileName): diff --git a/compass/ocean/tests/isomip_plus/viz/plot.py b/compass/ocean/tests/isomip_plus/viz/plot.py index c332b05f59..876fcd4d38 100644 --- a/compass/ocean/tests/isomip_plus/viz/plot.py +++ b/compass/ocean/tests/isomip_plus/viz/plot.py @@ -9,7 +9,7 @@ import progressbar import xarray from matplotlib.collections import PatchCollection -from matplotlib.colors import LogNorm +from matplotlib.colors import LogNorm, SymLogNorm from matplotlib.patches import Polygon @@ -77,22 +77,30 @@ def __init__(self, inFolder='.', outFolder='plots', expt='Ocean0', plt.switch_backend('Agg') - def plot_melt_time_series(self, sshMax=None): + def plot_melt_time_series(self, sshMax=None, wctMax=None): """ Plot a series of image for each of several variables related to melt at the ice shelf-ocean interface: mean melt rate, total melt flux, mean thermal driving, mean friction velocity """ + if 'timeMonthly_avg_landIceFreshwaterFlux' not in self.ds.keys(): + return rho_fw = 1000. secPerYear = 365 * 24 * 60 * 60 + suffix = '' areaCell = self.dsMesh.areaCell iceMask = self.ds.timeMonthly_avg_landIceFraction meltFlux = self.ds.timeMonthly_avg_landIceFreshwaterFlux if sshMax is not None: ssh = self.ds.timeMonthly_avg_ssh iceMask = iceMask.where(ssh < sshMax) + suffix = f'_sshMax{sshMax}' + elif wctMax is not None: + H = self.ds.timeMonthly_avg_layerThickness.sum(dim='nVertLevels') + iceMask = iceMask.where(H < wctMax) + suffix = f'_wctMax{wctMax}' totalMeltFlux = (meltFlux * areaCell * iceMask).sum(dim='nCells') totalArea = (areaCell * iceMask).sum(dim='nCells') @@ -101,7 +109,7 @@ def plot_melt_time_series(self, sshMax=None): 'm/yr') self.plot_time_series(1e-6 * totalMeltFlux, 'total melt flux', - 'totalMeltFlux', 'kT/yr') + f'totalMeltFlux{suffix}', 'kT/yr') prefix = 'timeMonthly_avg_landIceBoundaryLayerTracers_' boundary_layer_temperature = \ @@ -113,13 +121,13 @@ def plot_melt_time_series(self, sshMax=None): da = (da * areaCell * iceMask).sum(dim='nCells') / totalArea self.plot_time_series(da, 'mean thermal driving', - 'meanThermalDriving', 'deg C') + f'meanThermalDriving{suffix}', 'deg C') da = self.ds.timeMonthly_avg_landIceFrictionVelocity da = (da * areaCell * iceMask).sum(dim='nCells') / totalArea self.plot_time_series(da, 'mean friction velocity', - 'meanFrictionVelocity', 'm/s') + f'meanFrictionVelocity{suffix}', 'm/s') def plot_time_series(self, da, nameInTitle, prefix, units=None, figsize=(12, 6), color=None, overwrite=True): @@ -239,10 +247,12 @@ def __init__(self, inFolder, streamfunctionFolder, outFolder, expt, self.sectionY = sectionY self.showProgress = showProgress + if 'Time' in dsMesh.dims: + dsMesh = dsMesh.isel(Time=0) self.dsMesh = dsMesh self.ds = ds - landIceMask = self.dsMesh.landIceMask.isel(Time=0) > 0 + landIceMask = self.dsMesh.landIceMask > 0 self.oceanMask = self.dsMesh.maxLevelCell - 1 >= 0 self.cavityMask = numpy.logical_and(self.oceanMask, landIceMask) @@ -340,7 +350,7 @@ def plot_overturning_streamfunction(self, vmin=-0.3, vmax=0.3): if self.showProgress: bar.finish() - def plot_melt_rates(self, vmin=-100., vmax=100.): + def plot_melt_rates(self, vmin=0., vmax=50.): """ Plot a series of image of the melt rate @@ -349,6 +359,8 @@ def plot_melt_rates(self, vmin=-100., vmax=100.): vmin, vmax : float, optional The minimum and maximum values for the colorbar """ + if 'timeMonthly_avg_landIceFreshwaterFlux' not in self.ds.keys(): + return rho_fw = 1000. secPerYear = 365 * 24 * 60 * 60 @@ -357,7 +369,7 @@ def plot_melt_rates(self, vmin=-100., vmax=100.): self.plot_horiz_series(da, 'melt rate', prefix='meltRate', oceanDomain=False, units='m/yr', vmin=vmin, - vmax=vmax, cmap='cmo.curl') + vmax=vmax, cmap='cmo.amp') def plot_ice_shelf_boundary_variables(self): """ @@ -367,6 +379,8 @@ def plot_ice_shelf_boundary_variables(self): ice """ + if 'timeMonthly_avg_landIceFreshwaterFlux' not in self.ds.keys(): + return self.plot_horiz_series(self.ds.timeMonthly_avg_landIceHeatFlux, 'heat flux from ocean to ice-ocean interface', prefix='oceanHeatFlux', @@ -393,7 +407,7 @@ def plot_ice_shelf_boundary_variables(self): self.plot_horiz_series(da, 'thermal driving', prefix='thermalDriving', oceanDomain=False, units='deg C', - vmin=-2, vmax=2, cmap='cmo.thermal') + vmin=-2, vmax=2, cmap='cmo.curl') da = boundary_layer_salinity - interface_salinity self.plot_horiz_series(da, 'haline driving', @@ -404,8 +418,9 @@ def plot_ice_shelf_boundary_variables(self): self.plot_horiz_series(self.ds.timeMonthly_avg_landIceFrictionVelocity, 'friction velocity', prefix='frictionVelocity', - oceanDomain=True, units='m/s', - vmin=0, vmax=0.05, cmap='cmo.speed') + oceanDomain=False, units='m/s', + vmin=1.e-4, vmax=1.e-1, cmap='cmo.speed', + cmap_scale='log') def plot_temperature(self): """ @@ -430,8 +445,8 @@ def plot_salinity(self): self.plot_3d_field_top_bot_section(da, nameInTitle='salinity', prefix='Salinity', units='PSU', - vmin=33.8, vmax=34.7, - cmap='cmo.haline') + vmin=1., vmax=34.6, + cmap='cmo.haline', cmap_scale='log') def plot_potential_density(self): """ @@ -470,7 +485,7 @@ def plot_horiz_series(self, da, nameInTitle, prefix, oceanDomain, units=None, vmin=None, vmax=None, cmap=None, cmap_set_under=None, cmap_set_over=None, cmap_scale='linear', time_indices=None, - figsize=(9, 3)): + figsize=(9, 3), contour_field=None): """ Plot a series of image of a given variable @@ -508,7 +523,9 @@ def plot_horiz_series(self, da, nameInTitle, prefix, oceanDomain, The time indices at which to plot. If not provided, set to all. """ - nTime = self.ds.sizes['Time'] + if 'Time' not in da.dims: + da = da.expand_dims(dim='Time', axis=0) + nTime = da.sizes['Time'] if self.showProgress: widgets = ['plotting {}: '.format(nameInTitle), progressbar.Percentage(), ' ', @@ -523,6 +540,8 @@ def plot_horiz_series(self, da, nameInTitle, prefix, oceanDomain, for tIndex in time_indices: self.update_date(tIndex) field = da.isel(Time=tIndex).values + if contour_field is not None: + contour_field = contour_field.isel(Time=tIndex).values outFileName = '{}/{}/{}_{:04d}.png'.format( self.outFolder, prefix, prefix, tIndex + 1) if units is None: @@ -534,7 +553,8 @@ def plot_horiz_series(self, da, nameInTitle, prefix, oceanDomain, vmax=vmax, cmap=cmap, cmap_set_under=cmap_set_under, cmap_set_over=cmap_set_over, - cmap_scale=cmap_scale, figsize=figsize) + cmap_scale=cmap_scale, figsize=figsize, + contour_field=contour_field) if self.showProgress: bar.update(tIndex + 1) if self.showProgress: @@ -542,7 +562,8 @@ def plot_horiz_series(self, da, nameInTitle, prefix, oceanDomain, def plot_3d_field_top_bot_section(self, da, nameInTitle, prefix, units=None, vmin=None, vmax=None, - cmap=None, cmap_set_under=None, + cmap=None, cmap_scale='linear', + cmap_set_under=None, cmap_set_over=None): """ Plot a series of images of a given 3D variable showing the value @@ -598,6 +619,7 @@ def plot_3d_field_top_bot_section(self, da, nameInTitle, prefix, 'top {}'.format(nameInTitle), 'top{}'.format(prefix), oceanDomain=True, vmin=vmin, vmax=vmax, cmap=cmap, + cmap_scale=cmap_scale, cmap_set_under=cmap_set_under, cmap_set_over=cmap_set_over) @@ -620,7 +642,8 @@ def plot_3d_field_top_bot_section(self, da, nameInTitle, prefix, self.plot_horiz_series(daBot, 'bot {}'.format(nameInTitle), 'bot{}'.format(prefix), oceanDomain=True, - vmin=vmin, vmax=vmax, cmap=cmap) + vmin=vmin, vmax=vmax, cmap_scale=cmap_scale, + cmap=cmap) daSection = da.isel(nCells=self.sectionCellIndices) @@ -649,7 +672,8 @@ def plot_3d_field_top_bot_section(self, da, nameInTitle, prefix, self._plot_vert_field(self.X, self.Z[tIndex, :, :], field, title=title, outFileName=outFileName, - vmin=vmin, vmax=vmax, cmap=cmap) + vmin=vmin, vmax=vmax, cmap=cmap, + show_boundaries=False, cmap_scale=cmap_scale) if self.showProgress: bar.update(tIndex + 1) if self.showProgress: @@ -708,6 +732,7 @@ def plot_layer_interfaces(self, figsize=(9, 5)): plt.plot(1e-3 * X[z_index, :], Z[z_index, :], 'k') plt.plot(1e-3 * X[0, :], Z[0, :], 'b') plt.plot(1e-3 * X[0, :], self.zBotSection, 'g') + plt.plot(1e-3 * X[0, :], self.landIceDraft, 'r') ax.autoscale(tight=True) x1, x2, y1, y2 = 420, 470, -650, -520 @@ -719,6 +744,7 @@ def plot_layer_interfaces(self, figsize=(9, 5)): axins.plot(1e-3 * X[z_index, :], Z[z_index, :], 'k') axins.plot(1e-3 * X[0, :], Z[0, :], 'b') axins.plot(1e-3 * X[0, :], self.zBotSection, 'g') + axins.plot(1e-3 * X[0, :], self.landIceDraft, 'r') axins.set_xlim(x1, x2) axins.set_ylim(y1, y2) axins.set_xticklabels([]) @@ -783,7 +809,7 @@ def update_date(self, tIndex): def _plot_horiz_field(self, field, title, outFileName, oceanDomain=True, vmin=None, vmax=None, figsize=(9, 3), cmap=None, cmap_set_under=None, cmap_set_over=None, - cmap_scale='linear'): + cmap_scale='linear', contour_field=None): try: os.makedirs(os.path.dirname(outFileName)) @@ -814,10 +840,21 @@ def _plot_horiz_field(self, field, title, outFileName, oceanDomain=True, if cmap_scale == 'log': localPatches.set_norm(LogNorm(vmin=max(1e-10, vmin), vmax=vmax, clip=False)) + elif cmap_scale == 'symlog': + localPatches.set_norm(SymLogNorm(vmin=vmin, vmax=vmax, clip=False, + linthresh=vmax / 1e2)) plt.figure(figsize=figsize) ax = plt.subplot(111) ax.add_collection(localPatches) + if contour_field is not None: + dsMesh = self.dsMesh + ax.tricontour(dsMesh.xCell / 1.e3, dsMesh.yCell / 1.e3, + contour_field, + colors='k', levels=[1.1e-2 + 1.e-8]) + ax.tricontour(dsMesh.xCell / 1.e3, dsMesh.yCell / 1.e3, + contour_field, + colors='grey', levels=numpy.arange(50., 700., 50.)) plt.colorbar(localPatches, extend='both') plt.axis([0, 500, 0, 1000]) ax.set_aspect('equal') @@ -829,7 +866,7 @@ def _plot_horiz_field(self, field, title, outFileName, oceanDomain=True, def _plot_vert_field(self, inX, inZ, field, title, outFileName, vmin=None, vmax=None, figsize=(9, 5), cmap=None, - show_boundaries=True): + show_boundaries=True, cmap_scale='linear'): try: os.makedirs(os.path.dirname(outFileName)) except OSError: @@ -840,24 +877,40 @@ def _plot_vert_field(self, inX, inZ, field, title, outFileName, plt.figure(figsize=figsize) ax = plt.subplot(111) - if show_boundaries: - z_mask = numpy.ones(self.X.shape) - z_mask[0:-1, 0:-1] *= numpy.where(self.sectionMask, 1., numpy.nan) + z_mask = numpy.ones(self.X.shape) + z_mask[0:-1, 0:-1] *= numpy.where(self.sectionMask, 1., numpy.nan) - tIndex = 0 - Z = numpy.array(self.Z[tIndex, :, :]) - Z *= z_mask - X = self.X + tIndex = 0 + Z = numpy.array(self.Z[tIndex, :, :]) + Z *= z_mask + X = self.X - plt.fill_between(1e-3 * X[0, :], self.zBotSection, y2=0, - facecolor='lightsteelblue', zorder=2) - plt.fill_between(1e-3 * X[0, :], self.zBotSection, y2=-750, - facecolor='grey', zorder=1) + plt.fill_between(1e-3 * X[0, :], self.zBotSection, y2=0, + facecolor='lightsteelblue', zorder=2) + plt.fill_between(1e-3 * X[0, :], self.zBotSection, y2=-750, + facecolor='grey', zorder=1) + if show_boundaries: for z_index in range(1, X.shape[0]): plt.plot(1e-3 * X[z_index, :], Z[z_index, :], 'k', zorder=4) plt.pcolormesh(1e-3 * inX, inZ, field, vmin=vmin, vmax=vmax, cmap=cmap, zorder=3) plt.colorbar() + x1, x2, y1, y2 = 455, 470, -640, -540 + axins = ax.inset_axes([0.01, 0.6, 0.3, 0.39]) + axins.fill_between(1e-3 * X[0, :], self.zBotSection, y2=0, + facecolor='lightsteelblue', zorder=2) + axins.fill_between(1e-3 * X[0, :], self.zBotSection, y2=-750, + facecolor='grey', zorder=1) + if show_boundaries: + for z_index in range(1, X.shape[0]): + axins.plot(1e-3 * X[z_index, :], Z[z_index, :], 'k', zorder=4) + axins.pcolormesh(1e-3 * inX, inZ, field, vmin=vmin, vmax=vmax, + cmap=cmap, zorder=3) + axins.set_xlim(x1, x2) + axins.set_ylim(y1, y2) + axins.set_xticklabels([]) + axins.set_yticklabels([]) + ax.indicate_inset_zoom(axins, edgecolor="black") ax.autoscale(tight=True) plt.ylim([numpy.amin(inZ), 20]) plt.xlim([400, 800]) @@ -908,6 +961,15 @@ def _compute_section_x_z(self): layerThickness[:, zIndex]) self.Z[tIndex, zIndex, :] = self.Z[tIndex, zIndex + 1, :] + \ layerThicknessSection + if 'timeMonthly_avg_landIceDraft' in self.ds: + var = 'timeMonthly_avg_landIceDraft' + else: + var = 'landIceDraft' + landIceDraft = self.ds[var].isel( + Time=tIndex, nCells=self.sectionCellIndices) + landIceDraft = landIceDraft.values * self.sectionMask[0, :].T + landIceDraft = numpy.nan_to_num(landIceDraft) + self.landIceDraft = _interp_extrap_corner(landIceDraft) def _compute_cell_patches(dsMesh, mask): diff --git a/docs/developers_guide/ocean/api.rst b/docs/developers_guide/ocean/api.rst index a2e1845d54..a9f50a81cb 100644 --- a/docs/developers_guide/ocean/api.rst +++ b/docs/developers_guide/ocean/api.rst @@ -1042,7 +1042,9 @@ ocean framework haney.compute_haney_number - iceshelf.compute_land_ice_pressure_and_draft + iceshelf.compute_land_ice_draft_from_pressure + iceshelf.compute_land_ice_pressure_from_draft + iceshelf.compute_land_ice_pressure_from_thickness iceshelf.adjust_ssh particles.write diff --git a/docs/users_guide/ocean/test_groups/isomip_plus.rst b/docs/users_guide/ocean/test_groups/isomip_plus.rst index 567fa600b5..e53a4940b5 100644 --- a/docs/users_guide/ocean/test_groups/isomip_plus.rst +++ b/docs/users_guide/ocean/test_groups/isomip_plus.rst @@ -125,6 +125,9 @@ The ``isomip_plus`` test cases share the following config options: # considered a land-ice cell by MPAS-Ocean (landIceMask == 1). min_land_ice_fraction = 0.5 + # the density of ice prescribed in ISOMIP+ + ice_density = 918 + # the initial temperature at the sea surface init_top_temp = -1.9 # the initial temperature at the sea floor