Source code for compass.ocean.tests.isomip_plus.initial_state

import xarray
import numpy
import os
import shutil

from mpas_tools.planar_hex import make_planar_hex_mesh
from mpas_tools.translate import translate
from mpas_tools.io import write_netcdf
from mpas_tools.mesh.conversion import convert, cull
from mpas_tools.cime.constants import constants

from compass.step import Step
from compass.ocean.vertical import init_vertical_coord
from compass.ocean.vertical.grid_1d import generate_1d_grid
from compass.ocean.iceshelf import compute_land_ice_pressure_and_draft
from compass.ocean.tests.isomip_plus.geom import process_input_geometry, \
    interpolate_geom, interpolate_ocean_mask
from compass.ocean.tests.isomip_plus.viz.plot import MoviePlotter


[docs]class InitialState(Step): """ A step for creating a mesh and initial condition for ISOMIP+ test cases Attributes ---------- resolution : float The horizontal resolution (km) of the test case experiment : {'Ocean0', 'Ocean1', 'Ocean2'} The ISOMIP+ experiment vertical_coordinate : str The type of vertical coordinate (``z-star``, ``z-level``, etc.) time_varying_forcing : bool Whether the run includes time-varying land-ice forcing """
[docs] def __init__(self, test_case, resolution, experiment, vertical_coordinate, time_varying_forcing): """ Create the step Parameters ---------- test_case : compass.TestCase The test case this step belongs to resolution : float The horizontal resolution (km) of the test case experiment : {'Ocean0', 'Ocean1', 'Ocean2'} The ISOMIP+ experiment vertical_coordinate : str The type of vertical coordinate (``z-star``, ``z-level``, etc.) time_varying_forcing : bool Whether the run includes time-varying land-ice forcing """ super().__init__(test_case=test_case, name='initial_state') self.resolution = resolution self.experiment = experiment self.vertical_coordinate = vertical_coordinate self.time_varying_forcing = time_varying_forcing if experiment in ['Ocean0', 'Ocean1']: self.add_input_file(filename='input_geometry.nc', target='Ocean1_input_geom_v1.01.nc', database='initial_condition_database') elif experiment == 'Ocean2': self.add_input_file(filename='input_geometry.nc', target='Ocean2_input_geom_v1.01.nc', database='initial_condition_database') else: raise ValueError('Unknown ISOMIP+ experiment {}'.format( experiment)) for file in ['base_mesh.nc', 'culled_mesh.nc', 'culled_graph.info', 'initial_state.nc', 'init_mode_forcing_data.nc']: self.add_output_file(file)
[docs] def run(self): """ Run this step of the test case """ config = self.config logger = self.logger section = config['isomip_plus'] nx = section.getint('nx') ny = section.getint('ny') dc = section.getfloat('dc') filter_sigma = section.getfloat('topo_smoothing')*self.resolution min_ice_thickness = section.getfloat('min_ice_thickness') min_land_ice_fraction = section.getfloat('min_land_ice_fraction') draft_scaling = section.getfloat('draft_scaling') process_input_geometry('input_geometry.nc', 'input_geometry_processed.nc', filterSigma=filter_sigma, minIceThickness=min_ice_thickness, scale=draft_scaling) dsMesh = make_planar_hex_mesh(nx=nx+2, ny=ny+2, dc=dc, nonperiodic_x=False, nonperiodic_y=False) translate(mesh=dsMesh, yOffset=-2*dc) write_netcdf(dsMesh, 'base_mesh.nc') dsGeom = xarray.open_dataset('input_geometry_processed.nc') min_ocean_fraction = config.getfloat('isomip_plus', 'min_ocean_fraction') dsMask = interpolate_ocean_mask(dsMesh, dsGeom, min_ocean_fraction) dsMesh = cull(dsMesh, dsInverse=dsMask, logger=logger) dsMesh.attrs['is_periodic'] = 'NO' dsMesh = convert(dsMesh, graphInfoFileName='culled_graph.info', logger=logger) write_netcdf(dsMesh, 'culled_mesh.nc') ds = interpolate_geom(dsMesh, dsGeom, min_ocean_fraction) for var in ['landIceFraction']: ds[var] = ds[var].expand_dims(dim='Time', axis=0) ds['landIceMask'] = \ (ds.landIceFraction >= min_land_ice_fraction).astype(int) 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 self.time_varying_forcing: self._write_time_varying_forcing(ds_init=ds) ds['bottomDepth'] = -ds.bottomDepthObserved section = config['isomip_plus'] min_column_thickness = section.getfloat('min_column_thickness') min_levels = section.getint('minimum_levels') interfaces = generate_1d_grid(config) # Deepen the bottom depth to maintain the minimum water-column # thickness min_depth = numpy.maximum(-ds.ssh + min_column_thickness, interfaces[min_levels+1]) ds['bottomDepth'] = numpy.maximum(ds.bottomDepth, min_depth) init_vertical_coord(config, ds) ds['modifyLandIcePressureMask'] = \ (ds['landIceFraction'] > 0.01).astype(int) max_bottom_depth = -config.getfloat('vertical_grid', 'bottom_depth') frac = (0. - ds.zMid) / (0. - max_bottom_depth) # compute T, S init_top_temp = section.getfloat('init_top_temp') init_bot_temp = section.getfloat('init_bot_temp') init_top_sal = section.getfloat('init_top_sal') init_bot_sal = section.getfloat('init_bot_sal') ds['temperature'] = (1.0 - frac) * init_top_temp + frac * init_bot_temp ds['salinity'] = (1.0 - frac) * init_top_sal + frac * init_bot_sal # compute coriolis coriolis_parameter = section.getfloat('coriolis_parameter') ds['fCell'] = coriolis_parameter*xarray.ones_like(ds.xCell) ds['fEdge'] = coriolis_parameter*xarray.ones_like(ds.xEdge) ds['fVertex'] = coriolis_parameter*xarray.ones_like(ds.xVertex) normalVelocity = xarray.zeros_like(ds.xEdge) normalVelocity = normalVelocity.broadcast_like(ds.refBottomDepth) normalVelocity = normalVelocity.transpose('nEdges', 'nVertLevels') ds['normalVelocity'] = normalVelocity.expand_dims(dim='Time', axis=0) write_netcdf(ds, 'initial_state.nc') plot_folder = '{}/plots'.format(self.work_dir) if os.path.exists(plot_folder): shutil.rmtree(plot_folder) # plot a few fields section_y = 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, expt=self.experiment, sectionY=section_y, dsMesh=ds, ds=ds, showProgress=show_progress) plotter.plot_3d_field_top_bot_section( ds.zMid, nameInTitle='zMid', prefix='zmid', units='m', vmin=-720., vmax=0., cmap='cmo.deep_r') 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') # compute restoring dsForcing = xarray.Dataset() restore_top_temp = section.getfloat('restore_top_temp') restore_bot_temp = section.getfloat('restore_bot_temp') restore_top_sal = section.getfloat('restore_top_sal') restore_bot_sal = section.getfloat('restore_bot_sal') dsForcing['temperatureInteriorRestoringValue'] = \ (1.0 - frac) * restore_top_temp + frac * restore_bot_temp dsForcing['salinityInteriorRestoringValue'] = \ (1.0 - frac) * restore_top_sal + frac * restore_bot_sal restore_rate = section.getfloat('restore_rate') restore_xmin = section.getfloat('restore_xmin') restore_xmax = section.getfloat('restore_xmax') frac = numpy.maximum( (ds.xCell - restore_xmin)/(restore_xmax-restore_xmin), 0.) frac = frac.broadcast_like(dsForcing.temperatureInteriorRestoringValue) # convert from 1/days to 1/s dsForcing['temperatureInteriorRestoringRate'] = \ frac * restore_rate / constants['SHR_CONST_CDAY'] dsForcing['salinityInteriorRestoringRate'] = \ dsForcing.temperatureInteriorRestoringRate # compute "evaporation" restore_evap_rate = section.getfloat('restore_evap_rate') mask = numpy.logical_and(ds.xCell >= restore_xmin, ds.xCell <= restore_xmax) mask = mask.expand_dims(dim='Time', axis=0) # convert to m/s, negative for evaporation rather than precipitation evap_rate = -restore_evap_rate/(constants['SHR_CONST_CDAY']*365) # PSU*m/s to kg/m^2/s sflux_factor = 1. # C*m/s to W/m^2 hflux_factor = 1./(ref_density*constants['SHR_CONST_CPSW']) dsForcing['evaporationFlux'] = mask*ref_density*evap_rate dsForcing['seaIceSalinityFlux'] = \ mask*evap_rate*restore_top_sal/sflux_factor dsForcing['seaIceHeatFlux'] = \ mask*evap_rate*restore_top_temp/hflux_factor write_netcdf(dsForcing, 'init_mode_forcing_data.nc')
def _write_time_varying_forcing(self, ds_init): """ Write time-varying land-ice forcing and update the initial condition """ config = self.config dates = config.get('isomip_plus_forcing', 'dates') 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 = xarray.Dataset() ds_out['xtime'] = ('Time', dates) ds_out['xtime'] = ds_out.xtime.astype('S') landIceDraft = list() landIcePressure = list() landIceFraction = list() for scale in scales: landIceDraft.append(scale*ds_init.landIceDraft) landIcePressure.append(scale*ds_init.landIcePressure) landIceFraction.append(ds_init.landIceFraction) ds_out['landIceDraftForcing'] = xarray.concat(landIceDraft, 'Time') ds_out.landIceDraftForcing.attrs['units'] = 'm' ds_out.landIceDraftForcing.attrs['long_name'] = \ 'The approximate elevation of the land ice-ocean interface' ds_out['landIcePressureForcing'] = \ xarray.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'] = \ xarray.concat(landIceFraction, 'Time') ds_out.landIceFractionForcing.attrs['units'] = 'unitless' ds_out.landIceFractionForcing.attrs['long_name'] = \ 'The fraction of each cell covered by 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