Skip to content

Commit 5a98616

Browse files
committed
Add a step for computing forcing
The step only computes restoring at the "northern" boundary so far.
1 parent 3dccfd6 commit 5a98616

File tree

4 files changed

+183
-1
lines changed

4 files changed

+183
-1
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
from polaris.ocean.tasks.isomip_plus.init.forcing import Forcing
12
from polaris.ocean.tasks.isomip_plus.init.init import Init
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
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')

polaris/ocean/tasks/isomip_plus/isomip_plus_init.cfg

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,17 @@ min_land_ice_fraction = 0.0
6262
# Coriolis parameter (1/s) for entire domain
6363
coriolis_parameter = -1.409e-4
6464

65+
# config options for ISOMIP+ forcing
66+
[isomip_plus_forcing]
67+
68+
# restoring rate (1/days) at the open-ocean boundary
69+
restore_rate = 10.0
70+
71+
# the "evaporation" rate (m/yr) near the open-ocean boundary used to keep sea
72+
# level from rising
73+
restore_evap_rate = 200.0
74+
75+
# southern boundary of restoring region (m)
76+
restore_xmin = 790e3
77+
# northern boundary of restoring region (m)
78+
restore_xmax = 800e3

polaris/ocean/tasks/isomip_plus/isomip_plus_test.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from polaris import Task
2-
from polaris.ocean.tasks.isomip_plus.init import Init
2+
from polaris.ocean.tasks.isomip_plus.init import Forcing, Init
33

44

55
class IsomipPlusTest(Task):
@@ -92,6 +92,15 @@ def __init__(self, component, resdir, resolution, experiment,
9292
vertical_coordinate=vertical_coordinate,
9393
thin_film=thin_film))
9494

95+
self.add_step(Forcing(component=component,
96+
indir=subdir,
97+
culled_mesh=shared_steps['topo/cull_mesh'],
98+
topo=shared_steps['topo_final'],
99+
resolution=resolution,
100+
experiment=experiment,
101+
vertical_coordinate=vertical_coordinate,
102+
thin_film=thin_film))
103+
95104
def configure(self):
96105
"""
97106
Modify the configuration options for this test case.

0 commit comments

Comments
 (0)