Skip to content

Commit 239549b

Browse files
authored
Merge pull request #1102 from xylar/add-wind-stress-curl
Add task for plotting maps of wind stress curl
2 parents 29b3fa0 + 8cfeb05 commit 239549b

10 files changed

Lines changed: 460 additions & 9 deletions

File tree

docs/developers_guide/api.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ Ocean tasks
7878
ClimatologyMapArgoSalinity
7979
ClimatologyMapWaves
8080
ClimatologyMapCustom
81+
ClimatologyMapWindStressCurl
8182
GeojsonNetcdfTransects
8283
IndexNino34
8384
MeridionalHeatTransport
@@ -132,6 +133,8 @@ Ocean utilities
132133
add_standard_regions_and_subset
133134
get_standard_region_names
134135
compute_zmid
136+
compute_zinterface
137+
vector_cell_to_edge_isotropic
135138

136139

137140
Sea ice tasks

docs/users_guide/analysis_tasks.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ Analysis Tasks
2323
tasks/climatologyMapSSH
2424
tasks/climatologyMapVel
2525
tasks/climatologyMapWaves
26+
tasks/climatologyMapWindStressCurl
2627
tasks/climatologyMapWoa
2728

2829
tasks/antshipTransects
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
.. _task_climatologyMapWindStressCurl:
2+
3+
climatologyMapWindStressCurl
4+
============================
5+
6+
An analysis task for plotting maps of wind stress curl.
7+
8+
Component and Tags::
9+
10+
component: ocean
11+
tags: climatology, horizontalMap, windStressCurl, publicObs
12+
13+
Configuration Options
14+
---------------------
15+
16+
The following configuration options are available for this task::
17+
18+
[climatologyMapWindStressCurl]
19+
## options related to plotting horizontally remapped climatologies of
20+
## wind stress curl against control model results
21+
22+
# colormap for model/observations
23+
colormapNameResult = cmo.curl
24+
# whether the colormap is indexed or continuous
25+
colormapTypeResult = continuous
26+
# color indices into colormapName for filled contours
27+
# the type of norm used in the colormap
28+
normTypeResult = linear
29+
# A dictionary with keywords for the norm
30+
normArgsResult = {'vmin': -1e-6, 'vmax': 1e-6}
31+
32+
# colormap for differences
33+
colormapNameDifference = cmo.balance
34+
# whether the colormap is indexed or continuous
35+
colormapTypeDifference = continuous
36+
# the type of norm used in the colormap
37+
normTypeDifference = linear
38+
# A dictionary with keywords for the norm
39+
normArgsDifference = {'vmin': -2e-7, 'vmax': 2e-7}
40+
41+
# Months or seasons to plot (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct,
42+
# Nov, Dec, JFM, AMJ, JAS, OND, ANN)
43+
seasons = ['ANN']
44+
45+
# comparison grid(s) on which to plot analysis
46+
comparisonGrids = ['latlon']
47+
48+
For analysis focused on polar regions (using the ``--polar_regions`` flag),
49+
the following config options add Arctic and Antarctic comparison grids::
50+
51+
[climatologyMapWindStressCurl]
52+
## options related to plotting horizontally remapped climatologies of
53+
## wind stress curl against control model results
54+
55+
# comparison grid(s) on which to plot analysis
56+
comparisonGrids = ['latlon', 'arctic_extended', 'antarctic_extended']
57+
58+
For more details, see:
59+
* :ref:`config_colormaps`
60+
* :ref:`config_seasons`
61+
* :ref:`config_comparison_grids`
62+
63+
Example Result
64+
--------------
65+
66+
.. image:: examples/wind_stress_curl.png
67+
:width: 500 px
68+
:align: center
258 KB
Loading

mpas_analysis/__main__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,11 @@ def build_analysis_list(config, controlConfig):
189189
analyses.append(ocean.ClimatologyMapCustom(
190190
config, oceanClimatologyTasks['avg'], controlConfig))
191191

192+
193+
analyses.append(ocean.ClimatologyMapWindStressCurl(
194+
config, oceanClimatologyTasks['avg'], controlConfig)
195+
)
196+
192197
analyses.append(ocean.ConservationTask(
193198
config, controlConfig))
194199

mpas_analysis/default.cfg

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1691,6 +1691,37 @@ makeTables = False
16911691
iceShelvesInTable = []
16921692

16931693

1694+
[climatologyMapWindStressCurl]
1695+
## options related to plotting horizontally remapped climatologies of
1696+
## wind stress curl against control model results
1697+
1698+
# colormap for model/observations
1699+
colormapNameResult = cmo.curl
1700+
# whether the colormap is indexed or continuous
1701+
colormapTypeResult = continuous
1702+
# color indices into colormapName for filled contours
1703+
# the type of norm used in the colormap
1704+
normTypeResult = linear
1705+
# A dictionary with keywords for the norm
1706+
normArgsResult = {'vmin': -1e-6, 'vmax': 1e-6}
1707+
1708+
# colormap for differences
1709+
colormapNameDifference = cmo.balance
1710+
# whether the colormap is indexed or continuous
1711+
colormapTypeDifference = continuous
1712+
# the type of norm used in the colormap
1713+
normTypeDifference = linear
1714+
# A dictionary with keywords for the norm
1715+
normArgsDifference = {'vmin': -2e-7, 'vmax': 2e-7}
1716+
1717+
# Months or seasons to plot (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct,
1718+
# Nov, Dec, JFM, AMJ, JAS, OND, ANN)
1719+
seasons = ['ANN']
1720+
1721+
# comparison grid(s) on which to plot analysis
1722+
comparisonGrids = ['latlon']
1723+
1724+
16941725
[timeSeriesAntarcticMelt]
16951726
## options related to plotting time series of melt below Antarctic ice shelves
16961727

mpas_analysis/ocean/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@
2929
ClimatologyMapCustom
3030
)
3131

32+
from mpas_analysis.ocean.climatology_map_wind_stress_curl import (
33+
ClimatologyMapWindStressCurl
34+
)
35+
3236
from mpas_analysis.ocean.conservation import ConservationTask
3337

3438
from mpas_analysis.ocean.time_series_temperature_anomaly import \
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
# This software is open source software available under the BSD-3 license.
2+
#
3+
# Copyright (c) 2022 Triad National Security, LLC. All rights reserved.
4+
# Copyright (c) 2022 Lawrence Livermore National Security, LLC. All rights
5+
# reserved.
6+
# Copyright (c) 2022 UT-Battelle, LLC. All rights reserved.
7+
#
8+
# Additional copyright and license information can be found in the LICENSE file
9+
# distributed with this code, or at
10+
# https://raw.githubusercontent.com/MPAS-Dev/MPAS-Analysis/main/LICENSE
11+
12+
import xarray as xr
13+
14+
from mpas_tools.ocean.streamfunction.vorticity import (
15+
compute_vertically_integrated_vorticity,
16+
)
17+
18+
from mpas_analysis.ocean.utility import (
19+
vector_cell_to_edge_isotropic,
20+
vector_to_edge_normal,
21+
)
22+
from mpas_analysis.shared import AnalysisTask
23+
from mpas_analysis.shared.climatology import RemapMpasClimatologySubtask
24+
from mpas_analysis.shared.plot import PlotClimatologyMapSubtask
25+
26+
27+
class ClimatologyMapWindStressCurl(AnalysisTask):
28+
"""
29+
An analysis task for computing and plotting maps of the wind stress curl.
30+
"""
31+
32+
def __init__(self, config, mpas_climatology_task, control_config=None):
33+
"""
34+
Construct the analysis task.
35+
36+
Parameters
37+
----------
38+
config : mpas_tools.config.MpasConfigParser
39+
Configuration options
40+
41+
mpas_climatology_task : mpas_analysis.shared.climatology.MpasClimatologyTask
42+
The task that produced the climatology to be remapped and plotted
43+
44+
control_config : mpas_tools.config.MpasConfigParser, optional
45+
Configuration options for a control run (if any)
46+
""" # noqa: E501
47+
48+
field_name = 'windStressCurl'
49+
super().__init__(
50+
config=config,
51+
taskName='climatologyMapWindStressCurl',
52+
componentName='ocean',
53+
tags=[
54+
'climatology', 'horizontalMap', field_name, 'publicObs'
55+
],
56+
)
57+
58+
section_name = self.taskName
59+
60+
# read in what seasons we want to plot
61+
seasons = config.getexpression(section_name, 'seasons')
62+
if len(seasons) == 0:
63+
raise ValueError(
64+
f'config section {section_name} does not contain '
65+
f'valid list of seasons'
66+
)
67+
68+
comparison_grid_names = config.getexpression(
69+
section_name, 'comparisonGrids'
70+
)
71+
72+
if len(comparison_grid_names) == 0:
73+
raise ValueError(
74+
f'config section {section_name} does not contain '
75+
f'valid list of comparison grids'
76+
)
77+
78+
variable_list = list(RemapMpasWindStressCurl.VARIABLES)
79+
remap_climatology_subtask = RemapMpasWindStressCurl(
80+
mpasClimatologyTask=mpas_climatology_task,
81+
parentTask=self,
82+
climatologyName=field_name,
83+
variableList=variable_list,
84+
seasons=seasons,
85+
comparisonGridNames=comparison_grid_names,
86+
subtaskName='remapWindStressCurl',
87+
vertices=True,
88+
)
89+
90+
self.add_subtask(remap_climatology_subtask)
91+
92+
out_file_label = field_name
93+
field_title = 'Wind Stress Curl'
94+
remap_observations_subtask = None
95+
96+
mpas_field_name = field_name
97+
if control_config is None:
98+
ref_field_name = None
99+
ref_title_label = None
100+
diff_title_label = 'Model - Observations'
101+
102+
else:
103+
control_run_name = control_config.get('runs', 'mainRunName')
104+
ref_field_name = mpas_field_name
105+
ref_title_label = f'Control: {control_run_name}'
106+
diff_title_label = 'Main - Control'
107+
108+
for comparison_grid_name in comparison_grid_names:
109+
for season in seasons:
110+
# make a new subtask for this season and comparison grid
111+
subtask_name = f'plot{season}_{comparison_grid_name}'
112+
113+
subtask = PlotClimatologyMapSubtask(
114+
self, season, comparison_grid_name,
115+
remap_climatology_subtask, remap_observations_subtask,
116+
controlConfig=control_config, subtaskName=subtask_name)
117+
subtask.set_plot_info(
118+
outFileLabel=out_file_label,
119+
fieldNameInTitle=field_title,
120+
mpasFieldName=mpas_field_name,
121+
refFieldName=ref_field_name,
122+
refTitleLabel=ref_title_label,
123+
diffTitleLabel=diff_title_label,
124+
unitsLabel=r'N m$^{-3}$',
125+
imageCaption=field_title,
126+
galleryGroup='Wind Stress Curl',
127+
groupSubtitle=None,
128+
groupLink='wsc',
129+
galleryName=None,
130+
configSectionName=section_name)
131+
132+
self.add_subtask(subtask)
133+
134+
135+
class RemapMpasWindStressCurl(RemapMpasClimatologySubtask):
136+
"""
137+
A subtask for computing climatologies of the wind stress curl before
138+
it gets remapped to a comparison grid.
139+
"""
140+
141+
VARIABLES = (
142+
'timeMonthly_avg_windStressZonal',
143+
'timeMonthly_avg_windStressMeridional',
144+
)
145+
146+
def setup_and_check(self):
147+
"""
148+
Add the variables needed for computing wind stress curl to the
149+
climatology task
150+
"""
151+
super().setup_and_check()
152+
153+
# Add the variables and seasons, now that we have the variable list
154+
self.mpasClimatologyTask.add_variables(
155+
list(self.VARIABLES), self.seasons
156+
)
157+
158+
def customize_masked_climatology(self, climatology, season):
159+
"""
160+
Compute the wind stress curl and add it to the climatology.
161+
162+
Parameters
163+
----------
164+
climatology : xarray.Dataset
165+
the climatology data set
166+
167+
season : str
168+
The name of the season to be masked
169+
170+
Returns
171+
-------
172+
climatology : xarray.Dataset
173+
the modified climatology data set
174+
"""
175+
logger = self.logger
176+
177+
ds_mesh = xr.open_dataset(self.restartFileName)
178+
var_list = [
179+
'verticesOnEdge',
180+
'cellsOnVertex',
181+
'kiteAreasOnVertex',
182+
'angleEdge',
183+
'areaTriangle',
184+
'dcEdge',
185+
'edgesOnVertex',
186+
'verticesOnEdge',
187+
'latVertex',
188+
]
189+
ds_mesh = ds_mesh[var_list]
190+
191+
ws_zonal_cell = climatology['timeMonthly_avg_windStressZonal']
192+
ws_meridional_cell = (
193+
climatology['timeMonthly_avg_windStressMeridional']
194+
)
195+
ws_zonal_edge, ws_meridional_edge = vector_cell_to_edge_isotropic(
196+
ds_mesh, ws_zonal_cell, ws_meridional_cell
197+
)
198+
ws_normal_edge = vector_to_edge_normal(
199+
ds_mesh, ws_zonal_edge, ws_meridional_edge
200+
)
201+
202+
# despite the name, this is the curl operator
203+
wind_sress_curl, _ = compute_vertically_integrated_vorticity(
204+
ds_mesh, ws_normal_edge, logger
205+
)
206+
climatology['windStressCurl'] = wind_sress_curl
207+
climatology['windStressCurl'].attrs['units'] = 'N m-3'
208+
209+
# drop the original wind stress variables
210+
climatology = climatology.drop_vars(list(self.VARIABLES))
211+
212+
return climatology

0 commit comments

Comments
 (0)