Open jrleeman opened 6 years ago
Does Bluestein 1992 or 1993 have this (since I think we have been citing those texts for many of the dynamics calculations), and if so, what page?
I wouldn't be surprised if it is in there and/or Holton. All of these are on the shelf in my office, feel free to borrow.
It's in Bluestein 1992 at the bottom of p. 173, the first term of (4.1.98).
This one is tricky because of the time derivative. By the way, it seems the current declarative interface is limited to one time (please correct me if I'm wrong). In GEMPAK, you would specify two times to plot this (the endpoints of the time interval). Similarly, in GEMPAK you would specify two levels for all the layer-based calculations (such as the thermal wind). Are there plans to generalize the declarative interface to allow these kinds of calculations, or should I open an issue?
@sgdecker I think we're seeing two options here in general:
metpy.calc
) and hand the resulting DataArray
to the declarative interfaceWe're not necessarily wanting to have an entire domain specific language for specifying calculations like GEMPAK did. That's what Python is for.
Would possible implementations be limited to functions? I'm wondering about a calling convention like (my time strings may not be correct syntax):
data.metpy.sel(time=slice('2021-08-24:12:00','2021-08-24:18:00')).ddt(dim='time')
to compute the time derivative for every DataArray in the xarray Dataset data
, or if data
is a DataArray, just that DataArray.
I suppose this could be a function:
time_der = ddt(data, slice('2021-08-24:12:00','2021-08-24:18:00'))
And the isallobaric wind (or any other computation that requires a time interval) would have a similar interface.
Couldn't ddt
be done today (with the properly aggregated dataset) using metpy.calc.first_derivative(data_array, axis='time')
?
Ahh, I looked at the GEMPAK Conversion Guide too quickly. That looks correct. BTW, what's the proper process to turn the "Tested against GEMPAK" column from red to green? Maybe open an issue that demonstrates the equivalence and a linked pull request that edits the table?
I'd say just the PR. If one is feeling particularly motivated, adding a test case for the relevant function that tests with values validated from GEMPAK? (Depends if that's meaningfully different from existing tests.)
Actually, metpy.calc.first_derivative
isn't a drop in replacement for ddt
, as first_derivative
requires three points, but ddt
uses only two:
https://github.com/Unidata/gempak/blob/92f9a3a8ee667ec49a9082f44380e27f61ca716b/gempak/source/diaglib/df/dfddt.c
Given the coarse time resolution in most gridded datasets, this is somewhat of a limitation. For ddx
and ddy
, GEMPAK uses centered differences, so those cases are much closer to what first_derivative
does. But ddt
is a bit different.
It's not that one is right and the other is wrong, but they are different.
By the way, as I try to see how different the results are, I am noticing that first_derivative
strips all the attributes from my DataArrary. The result then doesn't play nice with the declarative plotting interface. Here is what I am trying:
import datetime
import xarray as xr
from metpy.io import GempakGrid
from metpy.calc import first_derivative
from metpy.plots import ContourPlot, MapPanel, PanelContainer
MODEL = '/ldmdata/gempak/model/'
gem_data = GempakGrid(MODEL + 'nam/21082312_nam211.gem')
heights = gem_data.gdxarray(parameter='hght', level=500, coordinate='pres')
z_ds = xr.concat(heights, dim='time')
data = z_ds.sel(time=slice('2021-08-23-12:00','2021-08-24-12:00',2))
print(data)
dzdt = first_derivative(data, axis='time')
print(dzdt)
# Simple plot as sanity check works
#dzdt.sel(time='2021-08-24-00:00').plot()
# Try a more complete plot with declarative interface
ht_fall = dzdt.sel(time='2021-08-24-00:00').rename('dzdt') * 1e4
cntr = ContourPlot()
cntr.data = ht_fall
cntr.field = 'dzdt'
cntr.level = 500
cntr.time = datetime.datetime(2021,8,24)
cntr.contours = list(range(-20, 20, 4))
cntr.linecolor = 'black'
cntr.linestyle = 'solid'
cntr.clabels = True
panel = MapPanel()
panel.area = [-125, -74, 20, 55]
panel.projection = 'lcc'
panel.layers = ['states', 'coastline', 'borders']
panel.title = 'dzdt at {plot_time}'
panel.plots = [cntr]
pc = PanelContainer()
pc.size = (15, 15)
pc.panels = [panel]
pc.show()
with output:
<xarray.DataArray 'z' (time: 3, pres: 1, y: 65, x: 93)>
array([[[[5845.124 , 5847.4604, 5841.7964, ..., 5853.14 , 5853.14 ,
5850.4204],
[5846.116 , 5847.5884, 5847.668 , ..., 5856.372 , 5855.492 ,
5855.4604],
[5849.7964, 5850.036 , 5850.116 , ..., 5860.1323, 5859.284 ,
5858.868 ],
...,
[5617.7 , 5599.844 , 5584.1 , ..., 5662.724 , 5645.7163,
5626.9644],
[5608.0044, 5591.9243, 5578.532 , ..., 5653.3003, 5637.06 ,
5619.188 ],
[5601.3804, 5588.356 , 5578.804 , ..., 5645.076 , 5629.9243,
5613.1084]]],
[[[5839.3477, 5843.124 , 5843.204 , ..., 5851.876 , 5851.652 ,
5855.876 ],
[5844.308 , 5845.668 , 5845.796 , ..., 5859.796 , 5858.308 ,
5856.9478],
[5847.892 , 5848.6597, 5848.644 , ..., 5860.7397, 5861.3 ,
...
5642.468 ],
[5620.98 , 5615.204 , 5610.148 , ..., 5640.9478, 5637.028 ,
5628.58 ],
[5606.9 , 5604.4517, 5597.812 , ..., 5629.124 , 5624.9316,
5616.772 ]]],
[[[5843.7007, 5840.405 , 5842.6606, ..., 5852.7246, 5850.5327,
5849.333 ],
[5844.613 , 5843.509 , 5842.7246, ..., 5854.5166, 5854.837 ,
5851.9565],
[5845.2847, 5844.773 , 5843.253 , ..., 5857.189 , 5855.9565,
5854.8047],
...,
[5626.773 , 5617.3647, 5608.9326, ..., 5664.5166, 5658.341 ,
5650.885 ],
[5607.525 , 5598.693 , 5590.677 , ..., 5648.085 , 5641.6367,
5633.381 ],
[5587.8926, 5580.277 , 5573.5566, ..., 5632.1167, 5625.0767,
5616.1006]]]], dtype=float32)
Coordinates:
* time (time) datetime64[ns] 2021-08-23T12:00:00 ... 2021-08-24T12:00:00
* pres (pres) int64 500
* x (x) float32 -4.226e+06 -4.145e+06 -4.064e+06 ... 3.17e+06 3.251e+06
* y (y) float32 2.035e+06 2.117e+06 2.198e+06 ... 7.155e+06 7.237e+06
Attributes: (12/17)
crs_wkt: PROJCRS["unknown",BASEGEOGCRS["unknown",D...
semi_major_axis: 6371200.0
semi_minor_axis: 6371200.0
inverse_flattening: 0.0
reference_ellipsoid_name: unknown
longitude_of_prime_meridian: 0.0
... ...
standard_parallel: (25.0, 25.0)
latitude_of_projection_origin: 0.0
longitude_of_central_meridian: -95.0
false_easting: 0.0
false_northing: 0.0
grid_type: forecast
<xarray.DataArray (time: 3, pres: 1, y: 65, x: 93)>
<Quantity([[[[-2.50950566e-04 -1.19097674e-04 5.51689996e-05 ... -5.37165889e-05
-3.87234158e-05 2.65158194e-04]
[-6.63079156e-05 -4.16904026e-05 -2.94551143e-05 ... 1.79985894e-04
1.37950756e-04 1.09411169e-04]
[-3.59429253e-05 -2.80874747e-06 1.12802011e-05 ... 6.21880425e-05
1.31830286e-04 1.44054272e-04]
...
[ 7.13495325e-04 1.12608733e-03 1.51332714e-03 ... -2.71126076e-04
7.97808612e-05 4.40894233e-04]
[ 6.06271249e-04 9.99428078e-04 1.32312916e-03 ... -5.11514169e-04
-5.44625741e-05 2.70549633e-04]
[ 4.11642569e-04 8.38679561e-04 9.40726951e-04 ... -5.88531494e-04
-1.75035265e-04 1.34978118e-04]]]
[[[-1.64738408e-05 -8.16627785e-05 1.00029839e-05 ... -4.80934426e-06
-3.01784939e-05 -1.25856753e-05]
[-1.74006709e-05 -4.72174750e-05 -5.72148076e-05 ... -2.14753328e-05
-7.58418330e-06 -4.05544705e-05]
[-5.22189670e-05 -6.09164768e-05 -7.94361256e-05 ... -3.40666594e-05
...
1.46117034e-04 2.76856599e-04]
[-5.54967810e-06 7.83397533e-05 1.40561704e-04 ... -6.03626393e-05
5.29706037e-05 1.64269341e-04]
[-1.56108715e-04 -9.35081199e-05 -6.07356319e-05 ... -1.49993896e-04
-5.61071325e-05 3.46317998e-05]]]
[[[ 2.18002884e-04 -4.42278827e-05 -3.51630317e-05 ... 4.40979004e-05
-2.16335720e-05 -2.90329545e-04]
[ 3.15065737e-05 -5.27445475e-05 -8.49745009e-05 ... -2.22936560e-04
-1.53119123e-04 -1.90520110e-04]
[-6.84950087e-05 -1.19024206e-04 -1.70152452e-04 ... -1.30321361e-04
-2.08858914e-04 -2.38116229e-04]
...
[-5.03477874e-04 -7.20520020e-04 -9.38500298e-04 ... 3.12618679e-04
2.12453206e-04 1.12818965e-04]
[-6.17370605e-04 -8.42748571e-04 -1.04200575e-03 ... 3.90788891e-04
1.60403781e-04 5.79890498e-05]
[-7.23859999e-04 -1.02569580e-03 -1.06219822e-03 ... 2.88543701e-04
6.28209997e-05 -6.57145182e-05]]]], '1 / second')>
Coordinates:
* time (time) datetime64[ns] 2021-08-23T12:00:00 ... 2021-08-24T12:00:00
* pres (pres) int64 500
* x (x) float32 -4.226e+06 -4.145e+06 -4.064e+06 ... 3.17e+06 3.251e+06
* y (y) float32 2.035e+06 2.117e+06 2.198e+06 ... 7.155e+06 7.237e+06
Traceback (most recent call last):
File "/home/decker/classes/met433/new_labs/ddt_test.py", line 47, in <module>
pc.show()
File "/home/decker/local/miniconda3/envs/met433/lib/python3.9/site-packages/metpy/plots/declarative.py", line 589, in show
self.draw()
File "/home/decker/local/miniconda3/envs/met433/lib/python3.9/site-packages/metpy/plots/declarative.py", line 576, in draw
panel.draw()
File "/home/decker/local/miniconda3/envs/met433/lib/python3.9/site-packages/metpy/plots/declarative.py", line 846, in draw
p.draw()
File "/home/decker/local/miniconda3/envs/met433/lib/python3.9/site-packages/metpy/plots/declarative.py", line 1137, in draw
self._build()
File "/home/decker/local/miniconda3/envs/met433/lib/python3.9/site-packages/metpy/plots/declarative.py", line 1300, in _build
x_like, y_like, imdata = self.plotdata
File "/home/decker/local/miniconda3/envs/met433/lib/python3.9/site-packages/metpy/plots/declarative.py", line 1121, in plotdata
plot_x_dim = self.griddata.metpy.find_axis_number('x')
File "/home/decker/local/miniconda3/envs/met433/lib/python3.9/site-packages/metpy/plots/declarative.py", line 1086, in griddata
data = self.data.metpy.parse_cf(self.field)
AttributeError: 'MetPyDataArrayAccessor' object has no attribute 'parse_cf'
Try not setting cntr.field
since you're passing in a DataArray
.
That's a good point about the 2 vs. 3 points. I'd be happy to merge something adding something functionally equivalent to ddt
--naming it well will probably be the biggest challenge. The implementation (which can re-use @xarray_derivative_wrap
and _process_deriv_args
) is pretty much np.diff(f) / delta
.
OK, I was overspecifying things. I also needed to drop the time specification, but my revised version still isn't happy:
import datetime
import xarray as xr
from metpy.io import GempakGrid
from metpy.calc import first_derivative
from metpy.plots import ContourPlot, MapPanel, PanelContainer
import cartopy.crs as ccrs
GRID211 = ccrs.LambertConformal(central_longitude=-95, standard_parallels=[25,25])
MODEL = '/ldmdata/gempak/model/'
gem_data = GempakGrid(MODEL + 'nam/21082312_nam211.gem')
heights = gem_data.gdxarray(parameter='hght', level=500, coordinate='pres')
z_ds = xr.concat(heights, dim='time')
data = z_ds.sel(time=slice('2021-08-23-12:00','2021-08-24-12:00',2))
dzdt = first_derivative(data, axis='time')
ht_fall = dzdt.sel(time='2021-08-24-00:00') * 1e4
ht_fall.attrs['crs'] = GRID211 # Error occurs with or without this line
cntr = ContourPlot()
cntr.data = ht_fall
cntr.level = 500
cntr.contours = list(range(-20, 20, 4))
cntr.linecolor = 'black'
cntr.linestyle = 'solid'
cntr.clabels = True
panel = MapPanel()
panel.area = [-125, -74, 20, 55]
panel.projection = 'lcc'
panel.layers = ['states', 'coastline', 'borders']
panel.title = 'dzdt at {plot_time}'
panel.plots = [cntr]
pc = PanelContainer()
pc.size = (15, 15)
pc.panels = [panel]
pc.show()
With output:
Traceback (most recent call last):
File "/home/decker/classes/met433/new_labs/ddt_test.py", line 41, in <module>
pc.show()
File "/home/decker/local/miniconda3/envs/met433/lib/python3.9/site-packages/metpy/plots/declarative.py", line 589, in show
self.draw()
File "/home/decker/local/miniconda3/envs/met433/lib/python3.9/site-packages/metpy/plots/declarative.py", line 576, in draw
panel.draw()
File "/home/decker/local/miniconda3/envs/met433/lib/python3.9/site-packages/metpy/plots/declarative.py", line 846, in draw
p.draw()
File "/home/decker/local/miniconda3/envs/met433/lib/python3.9/site-packages/metpy/plots/declarative.py", line 1137, in draw
self._build()
File "/home/decker/local/miniconda3/envs/met433/lib/python3.9/site-packages/metpy/plots/declarative.py", line 1302, in _build
kwargs = self.parent.plot_kwargs
File "/home/decker/local/miniconda3/envs/met433/lib/python3.9/site-packages/metpy/plots/declarative.py", line 814, in plot_kwargs
dataproj = self.plots[0].griddata.metpy.cartopy_crs
File "/home/decker/local/miniconda3/envs/met433/lib/python3.9/site-packages/metpy/xarray.py", line 238, in cartopy_crs
return self.crs.to_cartopy()
File "/home/decker/local/miniconda3/envs/met433/lib/python3.9/site-packages/metpy/xarray.py", line 233, in crs
raise AttributeError('crs attribute is not available.')
AttributeError: crs attribute is not available.
This is drifting away from the original issue, but I am attempting to understand in general how to combine calculations with GempakGrid
objects and the declarative interface (if it is possible).
Change:
ht_fall.attrs['crs'] = GRID211
to
ht_fall.metpy.assign_crs(grid_mapping_name='lambert_conformal_conic',
standard_parallel=[25, 25], longitude_of_central_meridian=-95)
See the xarray tutorial. This uses the names from CF metadata.
Should the parameters of the grid be within the GEMPAK file somewhere?
Thanks for the pointer to the documentation!
I think there are two issues:
1) The DataArrays from the GEMPAK reader do contain the key attributes (grid_mapping_name, standard_parallel, and longitude_of_central_meridian), but not in the form of a metpy_crs coordinate. I will draw upon the tutorial and your comment to make sure I can do a simple no-calculation example (just plot 500-mb heights) in the most straightforward way I can come up with.
2) first_derivative
strips all those attributes away, so there is a need to re-attach them to the DataArray before trying to plot the result with the declarative interface.
I have success with the simple case!
import xarray as xr
from metpy.io import GempakGrid
from metpy.plots import ContourPlot, MapPanel, PanelContainer
MODEL = '/ldmdata/gempak/model/'
gem_data = GempakGrid(MODEL + 'nam/21082312_nam211.gem')
heights = gem_data.gdxarray(parameter='hght', level=500, coordinate='pres')
z_ds = xr.concat(heights, dim='time')
# This try fails with AttributeError: 'MetPyDataArrayAccessor' object has no attribute 'parse_cf'
# z_ds = z_ds.metpy.parse_cf()
z_ds = z_ds.metpy.assign_crs(grid_mapping_name=z_ds.grid_mapping_name,
standard_parallel=z_ds.standard_parallel,
longitude_of_central_meridian=z_ds.longitude_of_central_meridian,
latitude_of_projection_origin=z_ds.latitude_of_projection_origin)
z500 = z_ds.metpy.sel(time='2021-08-23 12:00', vertical=500)
cntr = ContourPlot()
cntr.data = z500
cntr.contours = list(range(5400, 6000, 60))
cntr.linecolor = 'black'
cntr.linestyle = 'solid'
cntr.clabels = True
panel = MapPanel()
panel.area = [-125, -74, 20, 55]
panel.projection = 'lcc'
panel.layers = ['states', 'coastline', 'borders']
panel.title = '500-mb heights'
panel.plots = [cntr]
pc = PanelContainer()
pc.size = (15, 15)
pc.panels = [panel]
pc.show()
But I am confused why parse_cf
doesn't work. Using assign_crs
seems like something gdxarray
should do. Am I overlooking anything here?
I will move on to the ddt/first_derivative
case next.
@sgdecker first_derivative
, like pretty much all operations, calculations, math, etc. done on DataArray
instances dumps attributes (no easy way to prevent this or to propagate them correctly). This is why parse_cf
converts that metadata into an object that gets stuffed into the coordinates.
The reason parse_cf
doesn't work is because that only exists on Datasets
, because of the way the grid metadata is specified in the CF specification. For CF, the grid mapping parameters (e.g. standard_parallel
, earth_radius
) are attributes of a scalar variable, e.g. MyProjection
, then all of the data variables reference that by having an attribute grid_mapping_name
with a value of "MyProjection"
. I agree that assign_crs
should be called by all of the xarray functions for the GEMPAK code--would you open a separate issue about that?
Hope you both don't mind me chiming in here:
That's a good point about the 2 vs. 3 points. I'd be happy to merge something adding something functionally equivalent to
ddt
--naming it well will probably be the biggest challenge. The implementation (which can re-use@xarray_derivative_wrap
and_process_deriv_args
) is pretty muchnp.diff(f) / delta
.
This won't be able to use @xarray_derivative_wrap
as-is, since the output won't be on the same points/coordinate as the input (size will be one smaller, so have to decide if this acts as forward, backward, or centered difference and determine the new coordinate array accordingly).
Also, not to prematurely generalize, but this strikes me as something that falls under the alternative calculations (https://github.com/Unidata/MetPy/issues/142) area. Perhaps some careful thought here in the API can lead the way for other alternative calculations?
- The DataArrays from the GEMPAK reader do contain the key attributes (grid_mapping_name, standard_parallel, and longitude_of_central_meridian), but not in the form of a metpy_crs coordinate. I will draw upon the tutorial and your comment to make sure I can do a simple no-calculation example (just plot 500-mb heights) in the most straightforward way I can come up with.
I'd consider this a bug in the GEMPAK reader. I'm not aware of attributes containing the grid mapping details being valid on the variable (DataArray) itself. Instead, since we're building this from a pyproj.CRS
anyway, we should just include it in the form of a metpy_crs
. I'll open another issue for this.
first_derivative
strips all those attributes away, so there is a need to re-attach them to the DataArray before trying to plot the result with the declarative interface.
Correct, calculations stripping attributes is intended behavior, because it is not safe to assume attributes are still valid on the output (they represent different physical variables). At least in my understanding, things that are preserved across operations are necessarily more coordinate-like, so it makes sense (like metpy_crs
) to make them actual coordinates.
But I am confused why
parse_cf
doesn't work. Usingassign_crs
seems like somethinggdxarray
should do. Am I overlooking anything here?
Since CF grid mappings work by reference to another variable on the dataset, it doesn't make sense for parse_cf
to exist for DataArrays (since the reference to the defining attributes is lost). As mentioned above, creating the metpy_crs
is indeed something the GEMPAK reader should do internally.
Oh wait, it looked like @dopplershift beat me to it on those points.
OK, the first_derivative
calculation is fine, too, since the metpy_crs
coordinate is retained even though the attributes go away (which is the point of metpy_crs
at least in part).
Actually, the comparison is a lot closer than I thought it would be (at least for this case):
The one use case where it would still be worthwhile to have a ddt
equivalent would be if you didn't have any time points to spare (for instance, you have model output at 3-hr intervals and want to compute ddt
(or something based on ddt
like the isallobaric wind) over a 3-hr interval).
And I see the new comments as well. I think the action items are:
first_derivative
is almost a ddt
replacementddt
metpy_crs
to ... well, Jon just took of this one! Hi all,
I wanted to follow up on the ddt and isallobaric wind function action items from August 2021. I am looking to plot the various components of the ageostrophic wind including the isallobaric/isallohypsic wind. There is already an existing function for the inertial advective wind which is great - thank you!
I have some preliminary working code for both the isallobaric and inertial diabatic terms, but upon looking through this discussion on ddt I am unsure of how accurate some of my calculations are. If ddt and the isallobaric wind have already been developed, I would really appreciate using those functions as opposed to reinventing the wheel.
At this point, I wonder if it would be easier to simply calculate both the isallobaric and inertial advective terms and subtract those from the ageostrophic wind to get the inertial diabatic component since calculating the diabatic heating rate is quite a process.
AMS glossary cites Haurwitz, B. 1941. Dynamic Meteorology. 155–159., but I'm sure Holton or similar will have this as well.
GEMPAK Docs
ISAL Isallobaric wind ISAL ( S ) = [ - DDT ( v (GEO(S)) ) / CORL, DDT ( u (GEO(S)) ) / CORL ]