|
| 1 | +import os |
| 2 | + |
| 3 | +import numpy as np |
| 4 | +import xarray as xr |
| 5 | +from mpas_tools.cime.constants import constants |
| 6 | +from mpas_tools.io import write_netcdf |
| 7 | + |
| 8 | +from polaris.step import Step |
| 9 | + |
| 10 | + |
| 11 | +class Forcing(Step): |
| 12 | + """ |
| 13 | + A step for creating forcing files for ISOMIP+ experiments |
| 14 | +
|
| 15 | + Attributes |
| 16 | + ---------- |
| 17 | + resolution : float |
| 18 | + The horizontal resolution (km) of the test case |
| 19 | +
|
| 20 | + experiment : {'ocean0', 'ocean1', 'ocean2', 'ocean3', 'ocean4'} |
| 21 | + The ISOMIP+ experiment |
| 22 | +
|
| 23 | + vertical_coordinate : str |
| 24 | + The type of vertical coordinate (``z-star``, ``z-level``, etc.) |
| 25 | +
|
| 26 | + thin_film: bool |
| 27 | + Whether the run includes a thin film below grounded ice |
| 28 | + """ |
| 29 | + def __init__(self, component, indir, culled_mesh, topo, resolution, |
| 30 | + experiment, vertical_coordinate, thin_film): |
| 31 | + """ |
| 32 | + Create the step |
| 33 | +
|
| 34 | + Parameters |
| 35 | + ---------- |
| 36 | + component : polaris.Component |
| 37 | + The component this step belongs to |
| 38 | +
|
| 39 | + indir : str, optional |
| 40 | + the directory the step is in, to which ``name`` will be appended |
| 41 | +
|
| 42 | + culled_mesh : polaris.Step |
| 43 | + The step that culled the MPAS mesh |
| 44 | +
|
| 45 | + topo : polaris.Step |
| 46 | + The step with topography data on the culled mesh |
| 47 | +
|
| 48 | + resolution : float |
| 49 | + The horizontal resolution (km) of the test case |
| 50 | +
|
| 51 | + experiment : {'ocean0', 'ocean1', 'ocean2', 'ocean3', 'ocean4'} |
| 52 | + The ISOMIP+ experiment |
| 53 | +
|
| 54 | + vertical_coordinate : str |
| 55 | + The type of vertical coordinate (``z-star``, ``z-level``, etc.) |
| 56 | +
|
| 57 | + thin_film: bool |
| 58 | + Whether the run includes a thin film below grounded ice |
| 59 | + """ |
| 60 | + super().__init__(component=component, name='forcing', indir=indir) |
| 61 | + self.resolution = resolution |
| 62 | + self.experiment = experiment |
| 63 | + self.vertical_coordinate = vertical_coordinate |
| 64 | + self.thin_film = thin_film |
| 65 | + |
| 66 | + self.add_input_file( |
| 67 | + filename='mesh.nc', |
| 68 | + work_dir_target=os.path.join(culled_mesh.path, 'culled_mesh.nc')) |
| 69 | + |
| 70 | + self.add_input_file( |
| 71 | + filename='topo.nc', |
| 72 | + work_dir_target=os.path.join(topo.path, 'topography_remapped.nc')) |
| 73 | + |
| 74 | + self.add_input_file(filename='init.nc', target='../init/init.nc') |
| 75 | + |
| 76 | + self.add_output_file('init.nc') |
| 77 | + |
| 78 | + def run(self): |
| 79 | + """ |
| 80 | + Create restoring and other forcing files |
| 81 | + """ |
| 82 | + self._compute_restoring() |
| 83 | + |
| 84 | + def _compute_restoring(self): |
| 85 | + config = self.config |
| 86 | + experiment = self.experiment |
| 87 | + |
| 88 | + ref_density = constants['SHR_CONST_RHOSW'] |
| 89 | + |
| 90 | + ds_mesh = xr.open_dataset('mesh.nc') |
| 91 | + ds_init = xr.open_dataset('init.nc') |
| 92 | + |
| 93 | + ds_forcing = xr.Dataset() |
| 94 | + |
| 95 | + section = config['isomip_plus'] |
| 96 | + if experiment in ['ocean0', 'ocean1', 'ocean3']: |
| 97 | + top_temp = section.getfloat('warm_top_temp') |
| 98 | + bot_temp = section.getfloat('warm_bot_temp') |
| 99 | + top_sal = section.getfloat('warm_top_sal') |
| 100 | + bot_sal = section.getfloat('warm_bot_sal') |
| 101 | + else: |
| 102 | + top_temp = section.getfloat('cold_top_temp') |
| 103 | + bot_temp = section.getfloat('cold_bot_temp') |
| 104 | + top_sal = section.getfloat('cold_top_sal') |
| 105 | + bot_sal = section.getfloat('cold_bot_sal') |
| 106 | + |
| 107 | + section = config['isomip_plus_forcing'] |
| 108 | + restore_rate = section.getfloat('restore_rate') |
| 109 | + restore_xmin = section.getfloat('restore_xmin') |
| 110 | + restore_xmax = section.getfloat('restore_xmax') |
| 111 | + restore_evap_rate = section.getfloat('restore_evap_rate') |
| 112 | + |
| 113 | + max_bottom_depth = -config.getfloat('vertical_grid', 'bottom_depth') |
| 114 | + z_frac = (0. - ds_init.zMid) / (0. - max_bottom_depth) |
| 115 | + |
| 116 | + ds_forcing['temperatureInteriorRestoringValue'] = \ |
| 117 | + (1.0 - z_frac) * top_temp + z_frac * bot_temp |
| 118 | + ds_forcing['salinityInteriorRestoringValue'] = \ |
| 119 | + (1.0 - z_frac) * top_sal + z_frac * bot_sal |
| 120 | + |
| 121 | + x_frac = np.maximum( |
| 122 | + ((ds_mesh.xIsomipCell - restore_xmin) / |
| 123 | + (restore_xmax - restore_xmin)), |
| 124 | + 0.) |
| 125 | + x_frac = x_frac.broadcast_like( |
| 126 | + ds_forcing.temperatureInteriorRestoringValue) |
| 127 | + |
| 128 | + # convert from 1/days to 1/s |
| 129 | + ds_forcing['temperatureInteriorRestoringRate'] = \ |
| 130 | + x_frac * restore_rate / constants['SHR_CONST_CDAY'] |
| 131 | + ds_forcing['salinityInteriorRestoringRate'] = \ |
| 132 | + ds_forcing.temperatureInteriorRestoringRate |
| 133 | + |
| 134 | + # compute "evaporation" |
| 135 | + mask = np.logical_and(ds_mesh.xIsomipCell >= restore_xmin, |
| 136 | + ds_mesh.xIsomipCell <= restore_xmax) |
| 137 | + mask = mask.expand_dims(dim='Time', axis=0) |
| 138 | + # convert to m/s, negative for evaporation rather than precipitation |
| 139 | + evap_rate = -restore_evap_rate / (constants['SHR_CONST_CDAY'] * 365) |
| 140 | + # PSU*m/s to kg/m^2/s |
| 141 | + sflux_factor = 1. |
| 142 | + # C*m/s to W/m^2 |
| 143 | + hflux_factor = 1. / (ref_density * constants['SHR_CONST_CPSW']) |
| 144 | + ds_forcing['evaporationFlux'] = mask * ref_density * evap_rate |
| 145 | + ds_forcing['seaIceSalinityFlux'] = \ |
| 146 | + mask * evap_rate * top_sal / sflux_factor |
| 147 | + ds_forcing['seaIceHeatFlux'] = \ |
| 148 | + mask * evap_rate * top_temp / hflux_factor |
| 149 | + |
| 150 | + if self.vertical_coordinate == 'single-layer': |
| 151 | + x_max = np.max(ds_mesh.xIsomipCell.values) |
| 152 | + ds_forcing['tidalInputMask'] = xr.where( |
| 153 | + ds_mesh.xIsomipCell > (x_max - 0.6 * self.resolution * 1e3), |
| 154 | + 1.0, 0.0) |
| 155 | + else: |
| 156 | + ds_forcing['tidalInputMask'] = xr.zeros_like(ds_mesh.xIsomipCell) |
| 157 | + |
| 158 | + write_netcdf(ds_forcing, 'init_mode_forcing_data.nc') |
0 commit comments