gphys 1.5.6 → 1.5.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/ChangeLog +0 -7672
- data/bin/gpcut +12 -0
- data/bin/gpvect +88 -77
- data/bin/gpview +200 -103
- data/ext/numru/gphys/dim_op.c +336 -44
- data/ext/numru/gphys/ext_init.c +6 -0
- data/ext/numru/gphys/interpo.c +326 -3
- data/lib/numru/dclext.rb +78 -16
- data/lib/numru/ganalysis/beta_plane.rb +6 -4
- data/lib/numru/ganalysis/eof.rb +61 -41
- data/lib/numru/ganalysis/fitting.rb +86 -23
- data/lib/numru/ganalysis/histogram.rb +3 -3
- data/lib/numru/ganalysis/log_p.rb +20 -0
- data/lib/numru/ganalysis/lomb_scargle.rb +205 -0
- data/lib/numru/ganalysis/met_z.rb +132 -3
- data/lib/numru/ganalysis/narray_ext.rb +1 -1
- data/lib/numru/ganalysis/pde.rb +20 -1
- data/lib/numru/ganalysis/planet.rb +136 -1
- data/lib/numru/ganalysis/qg.rb +224 -3
- data/lib/numru/ggraph.rb +89 -18
- data/lib/numru/gphys/axis.rb +4 -2
- data/lib/numru/gphys/gphys_dim_op.rb +69 -6
- data/lib/numru/gphys/gphys_fft.rb +30 -0
- data/lib/numru/gphys/gphys_io_common.rb +2 -0
- data/lib/numru/gphys/grads_gridded.rb +77 -29
- data/lib/numru/gphys/grib.rb +2 -2
- data/lib/numru/gphys/grib_params.rb +3 -3
- data/lib/numru/gphys/interpolate.rb +153 -1
- data/lib/numru/gphys/varraycomposite.rb +7 -4
- data/lib/numru/gphys/version.rb +1 -1
- data/testdata/pres.jan.nc +0 -0
- metadata +6 -4
@@ -48,7 +48,12 @@ module NumRu
|
|
48
48
|
|
49
49
|
pcv_val = pcv.val
|
50
50
|
v_val = v.val # should be NArray or NArrayMiss
|
51
|
-
|
51
|
+
if v_val.is_a?(NArrayMiss)
|
52
|
+
misval = 9.9692099683868690e+36
|
53
|
+
v_val = v_val.to_na(misval)
|
54
|
+
else
|
55
|
+
misval = nil
|
56
|
+
end
|
52
57
|
if pcv_val[0] > pcv_val[-1]
|
53
58
|
# reverse the p coordinate to the increasing order
|
54
59
|
pcv_val = pcv_val[-1..0]
|
@@ -67,7 +72,7 @@ module NumRu
|
|
67
72
|
vs_val = vs_val.to_na if vs_val.is_a?(NArrayMiss)
|
68
73
|
|
69
74
|
v_val, p_over_g, nzbound = GPhys.c_cap_by_boundary(v_val, zdim,
|
70
|
-
|
75
|
+
pcv_over_g, true, ps_over_g, vs_val, misval)
|
71
76
|
|
72
77
|
elsif zdim = SigmaCoord.find_sigma_d(v) # substitution, not comparison
|
73
78
|
# has a sigma coordnate
|
@@ -85,6 +90,12 @@ module NumRu
|
|
85
90
|
ps = ps.convert_units(pascal) if ps.units != pascal
|
86
91
|
sig_val = sig.val
|
87
92
|
v_val = v.val # should be NArray, not NArrayMiss (coz sigma)
|
93
|
+
if v_val.is_a?(NArrayMiss)
|
94
|
+
v_val = v_val.to_na
|
95
|
+
mask = v_val.get_mask
|
96
|
+
else
|
97
|
+
mask = nil
|
98
|
+
end
|
88
99
|
p_over_g = SigmaCoord.sig_ps2p(ps.val/grav, sig_val, zdim)
|
89
100
|
else
|
90
101
|
raise ArgumentError, "v does not have a p or sigma coordinate."
|
@@ -102,7 +113,7 @@ module NumRu
|
|
102
113
|
|
103
114
|
pc_over_g = pc_val / grav
|
104
115
|
|
105
|
-
rho_v_cum = GPhys.c_cum_integ_irreg(v_val, p_over_g, zdim, nzbound,
|
116
|
+
rho_v_cum = GPhys.c_cum_integ_irreg(v_val, mask, p_over_g, zdim, nzbound,
|
106
117
|
pc_over_g, nil)
|
107
118
|
|
108
119
|
#< zonal mean & latitudinal factor >
|
@@ -295,6 +306,124 @@ module NumRu
|
|
295
306
|
mstrm
|
296
307
|
end
|
297
308
|
|
309
|
+
# Integrate v with p to ps (where v==vs if vs is given)
|
310
|
+
#
|
311
|
+
# Normally, p and ps are pressure, but they are actually arbitrary.
|
312
|
+
# The assumption here is that the ps is the upper cap of p, and
|
313
|
+
# the integration with p is from the smallest p up to ps.
|
314
|
+
# fact is a factor. E.g., 1/gravity to get mass-weighted integration
|
315
|
+
# through dp/g = -\rho dz
|
316
|
+
#
|
317
|
+
# ARGUMENT
|
318
|
+
# * v [GPhys] a multi-dimensional GPhys
|
319
|
+
# * pdim [Integer or String] The dimension of p
|
320
|
+
# * ps [GPhys] the capping value of p (~surface pressure);
|
321
|
+
# rank must be one smaller than v's (no-missing data allowed)
|
322
|
+
# * vs [nil or GPhys] v at ps (shape must be ps's); if nil, v is
|
323
|
+
# interpolated. (If vs==nil, no extrapolation is made when ps>p.max)
|
324
|
+
# * fact [nil or UNumeric or..] factor to be multiplied afterwords
|
325
|
+
# (e.g., 1/Met.g)
|
326
|
+
# * name [nil or String] name to be set
|
327
|
+
# * long_name [nil or String] long_name to be set
|
328
|
+
#
|
329
|
+
# RETURN VALUE
|
330
|
+
# * a GPhys
|
331
|
+
def integrate_w_p_to_ps(v, pdim, ps, vs: nil, fact: nil,
|
332
|
+
name: nil, long_name: nil)
|
333
|
+
pdim = v.dim_index(pdim)
|
334
|
+
p = v.coord(pdim)
|
335
|
+
|
336
|
+
if ps
|
337
|
+
if p.units !~ ps.units
|
338
|
+
raise("units mis-match #{p.units} vs #{ps.units}")
|
339
|
+
end
|
340
|
+
if p.units != ps.units
|
341
|
+
p = p.convert_units(ps.units)
|
342
|
+
end
|
343
|
+
pv = p.val
|
344
|
+
psv = ps.val
|
345
|
+
if psv.is_a?(NArrayMiss)
|
346
|
+
raise("data missing exists in ps") if psv.count_invalid != 0
|
347
|
+
psv = psv.to_na
|
348
|
+
end
|
349
|
+
else
|
350
|
+
pv = p.val
|
351
|
+
psv = NArray.float(1).fill!(pv.max)
|
352
|
+
end
|
353
|
+
|
354
|
+
vv = v.val
|
355
|
+
if vv.is_a?(NArrayMiss)
|
356
|
+
mask = vv.get_mask
|
357
|
+
misval = 9.9692099683868690e+36 # near 15 * 2^119 (as nc fill val)
|
358
|
+
vv = vv.to_na(misval)
|
359
|
+
else
|
360
|
+
mask = nil
|
361
|
+
misval = nil
|
362
|
+
end
|
363
|
+
|
364
|
+
nzbound = nil
|
365
|
+
|
366
|
+
if ps
|
367
|
+
if pv[0] > pv[-1]
|
368
|
+
# reverse the p coordinate to the increasing order
|
369
|
+
pv = pv[-1..0]
|
370
|
+
vv = vv[ *([true]*pdim + [-1..0,false]) ]
|
371
|
+
end
|
372
|
+
if vs
|
373
|
+
vsv = vs.val
|
374
|
+
if vsv.is_a?(NArrayMiss)
|
375
|
+
raise("data missing exists in vs") if vsv.count_invalid != 0
|
376
|
+
vsv = vsv.to_na
|
377
|
+
end
|
378
|
+
else
|
379
|
+
vsv = nil
|
380
|
+
end
|
381
|
+
vv, pv, nzbound = GPhys.c_cap_by_boundary(vv, pdim,
|
382
|
+
pv, true, psv, vsv, misval)
|
383
|
+
mask_e = NArray.byte(*vv.shape).fill!(1) # pdim has get longer by one
|
384
|
+
mask_e[ *( [true]*pdim + [0..-2,false]) ] = mask
|
385
|
+
mask = mask_e
|
386
|
+
end
|
387
|
+
|
388
|
+
psv = psv.newdim!(pdim) if ps
|
389
|
+
for iidx in [0,20]
|
390
|
+
sel = [iidx,50,true,0]
|
391
|
+
vi = GPhys.c_cell_integ_irreg(vv, mask, pv, pdim, nzbound, psv, nil)
|
392
|
+
end
|
393
|
+
osh = v.shape.clone
|
394
|
+
osh.delete_at(pdim)
|
395
|
+
vi.reshape!(*osh)
|
396
|
+
|
397
|
+
data = VArray.new(vi, v.data, v.name)
|
398
|
+
data.units = v.units * p.units
|
399
|
+
data = data * fact if fact
|
400
|
+
data.name = name if name
|
401
|
+
data.long_name = long_name if long_name
|
402
|
+
|
403
|
+
grid = v.grid.delete_axes(pdim)
|
404
|
+
grid.set_lost_axes( Array.new ) # reset
|
405
|
+
GPhys.new(grid, data)
|
406
|
+
end
|
407
|
+
|
298
408
|
end
|
299
409
|
end
|
300
410
|
end
|
411
|
+
|
412
|
+
######################################
|
413
|
+
|
414
|
+
if $0 == __FILE__
|
415
|
+
require "numru/ggraph"
|
416
|
+
include NumRu
|
417
|
+
|
418
|
+
v = GPhys::IO.open("../../../testdata/T.jan.nc","T")
|
419
|
+
v = v.copy.replace_val( NArray.float(*v.shape).fill!(1.0) )
|
420
|
+
ps = GPhys::IO.open("../../../testdata/pres.jan.nc","pres")
|
421
|
+
ps = ps.convert_units("Pa")
|
422
|
+
pdim = GAnalysis::Met.find_prs_d(v)
|
423
|
+
vp = GAnalysis::MetZ.integrate_w_p_to_ps(v, pdim, ps)
|
424
|
+
p "ps", ps.val
|
425
|
+
p "result 1", vp.val+1000
|
426
|
+
vs = ps * 0.0
|
427
|
+
vp = GAnalysis::MetZ.integrate_w_p_to_ps(v, pdim, ps, vs: vs)
|
428
|
+
p "result 2", vp.val+1000
|
429
|
+
end
|
data/lib/numru/ganalysis/pde.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
require "numru/gphys"
|
2
|
-
require "numru/
|
2
|
+
require "numru/ganalysis_ext"
|
3
3
|
|
4
4
|
module NumRu
|
5
5
|
module GAnalysis
|
@@ -87,4 +87,23 @@ if $0 == __FILE__
|
|
87
87
|
eps = 1e-6
|
88
88
|
ome = 1.95
|
89
89
|
GAnalysis::PDE.SOR_2D_2ndorder(z, a, b, c, d, e, f, dx, dy, ome, eps: eps)
|
90
|
+
|
91
|
+
# -- make gphys object
|
92
|
+
nax = NArray.sfloat(nx).indgen!
|
93
|
+
hax = {'long_name'=>'x-coordinate', 'units'=>'grid number'}
|
94
|
+
vax = VArray.new(nax, hax, 'x')
|
95
|
+
axx = Axis.new.set_pos(vax)
|
96
|
+
|
97
|
+
nay = NArray.sfloat(ny).indgen!
|
98
|
+
hay = {'long_name'=>'y-coordinate', 'units'=>'grid number'}
|
99
|
+
vay = VArray.new(nay, hay, 'y')
|
100
|
+
axy = Axis.new.set_pos(vay)
|
101
|
+
|
102
|
+
haz = {'long_name'=>'', 'units'=>''}
|
103
|
+
vaz = VArray.new(z, haz, 'data')
|
104
|
+
gpz = GPhys.new(Grid.new(axx, axy), vaz)
|
105
|
+
|
106
|
+
GGraph.startup('iws'=>1)
|
107
|
+
GGraph.tone_and_contour gpz,true,'color_bar'=>true
|
108
|
+
GGraph.close
|
90
109
|
end
|
@@ -95,6 +95,82 @@ module NumRu
|
|
95
95
|
rot
|
96
96
|
end
|
97
97
|
|
98
|
+
# spherical divergence for entire rectagular regional data
|
99
|
+
def div_s_box(u,v, only_u: false, only_v: false,
|
100
|
+
xs0: false, xe0: false, ys0: false, ye0: false)
|
101
|
+
lam, phi, xd, yd = get_lambda_phi(u)
|
102
|
+
lon = u.coord(xd)
|
103
|
+
lat = u.coord(yd)
|
104
|
+
cos_phi = phi.cos
|
105
|
+
nx = lam.length
|
106
|
+
ny = phi.length
|
107
|
+
if nx>2
|
108
|
+
ixends = {0..-1=>(nx-1)}
|
109
|
+
else
|
110
|
+
ixends = true
|
111
|
+
end
|
112
|
+
if ny>2
|
113
|
+
iyends = {0..-1=>(ny-1)}
|
114
|
+
else
|
115
|
+
iyends = true
|
116
|
+
end
|
117
|
+
ub = u[*([true]*xd + [ixends, false])].copy # ends along lon
|
118
|
+
vb = v[*([true]*yd + [iyends, false])].copy # ends along lat
|
119
|
+
ub.axis(xd).set_pos(lam[ixends].data)
|
120
|
+
ub.axis(yd).set_pos(phi.data)
|
121
|
+
vb.axis(xd).set_pos(lam.data)
|
122
|
+
vb.axis(yd).set_pos(phi[iyends].data)
|
123
|
+
ubi = ub.integrate(yd) # trapezoidal integration by default
|
124
|
+
vbi = vb.integrate(xd) * cos_phi[iyends]
|
125
|
+
dlam = lam[-1].val - lam[0].val
|
126
|
+
sin_phib = phi[iyends].sin.val
|
127
|
+
dsin_phi = sin_phib[-1] - sin_phib[0]
|
128
|
+
if xd<yd
|
129
|
+
xdi = xd
|
130
|
+
ydi = yd-1
|
131
|
+
else
|
132
|
+
xdi = xd-1
|
133
|
+
ydi = yd
|
134
|
+
end
|
135
|
+
scalar=false
|
136
|
+
ubi.axis(xdi).set_pos(lon[ixends]) # to lon before the cut is recoreded
|
137
|
+
vbi.axis(ydi).set_pos(lat[iyends]) # to lat before the cut is recoreded
|
138
|
+
if ubi.rank>1
|
139
|
+
xe = ubi[*([true]*xdi+[-1,false])]
|
140
|
+
xs = ubi[*([true]*xdi+[0,false])]
|
141
|
+
ye = vbi[*([true]*ydi+[-1,false])]
|
142
|
+
ys = vbi[*([true]*ydi+[0,false])]
|
143
|
+
else
|
144
|
+
# to defer the result becomes a scalar
|
145
|
+
scalar=true
|
146
|
+
xe = ubi[*([true]*xdi+[-1..-1,false])]
|
147
|
+
xs = ubi[*([true]*xdi+[0..0,false])]
|
148
|
+
ye = vbi[*([true]*ydi+[-1..-1,false])]
|
149
|
+
ys = vbi[*([true]*ydi+[0..0,false])]
|
150
|
+
end
|
151
|
+
xe *= 0.0 if xe0
|
152
|
+
xs *= 0.0 if xs0
|
153
|
+
ye *= 0.0 if ye0
|
154
|
+
ys *= 0.0 if ys0
|
155
|
+
dub = xe - xs
|
156
|
+
dvb = ye - ys
|
157
|
+
if only_u
|
158
|
+
div = dub/(@@R*dlam*dsin_phi.abs)
|
159
|
+
elsif only_v
|
160
|
+
div = dvb/(@@R*dlam.abs*dsin_phi)
|
161
|
+
else
|
162
|
+
div = dub/(@@R*dlam*dsin_phi.abs) +
|
163
|
+
dvb/(@@R*dlam.abs*dsin_phi)
|
164
|
+
# (dlam*dsin_phi).abs is the area for the unit radius
|
165
|
+
end
|
166
|
+
if scalar
|
167
|
+
div = div[0]
|
168
|
+
end
|
169
|
+
div.long_name = "div(#{u.name},#{v.name})"
|
170
|
+
div.name = "div"
|
171
|
+
div
|
172
|
+
end
|
173
|
+
|
98
174
|
def vor_s(u,v)
|
99
175
|
vor = rot_s(u,v)
|
100
176
|
vor.long_name = "Relative Vorticity"
|
@@ -131,7 +207,7 @@ module NumRu
|
|
131
207
|
end
|
132
208
|
|
133
209
|
def grad_sy(s)
|
134
|
-
|
210
|
+
phi, latd = get_phi(s)
|
135
211
|
cos_phi = phi.cos
|
136
212
|
ys = s.cderiv(latd,latbc(phi),phi) / @@R
|
137
213
|
ys.long_name = "ygrad(#{s.name})"
|
@@ -196,6 +272,22 @@ module NumRu
|
|
196
272
|
[lam, phi, lond, latd]
|
197
273
|
end
|
198
274
|
|
275
|
+
def get_lambda(gp, err_raise=true)
|
276
|
+
lond = find_lon_dim(gp, err_raise)
|
277
|
+
lam = lond && gp.axis(lond).to_gphys.convert_units("rad") # lon in rad
|
278
|
+
lam.units = "" if lond # treat as non-dim
|
279
|
+
@@lam = lam
|
280
|
+
[lam, lond]
|
281
|
+
end
|
282
|
+
|
283
|
+
def get_phi(gp, err_raise=true)
|
284
|
+
latd = find_lat_dim(gp, err_raise)
|
285
|
+
phi = latd && gp.axis(latd).to_gphys.convert_units("rad") # lat in rad
|
286
|
+
phi.units = "" if latd # treat as non-dim
|
287
|
+
@@phi = phi
|
288
|
+
[phi, latd]
|
289
|
+
end
|
290
|
+
|
199
291
|
# Find longitude and latitude coordinates.
|
200
292
|
#
|
201
293
|
# ARGUMENTS
|
@@ -254,6 +346,49 @@ module NumRu
|
|
254
346
|
end
|
255
347
|
[lond,latd]
|
256
348
|
end
|
349
|
+
|
350
|
+
def find_lon_dim(gp, err_raise=false)
|
351
|
+
lond = nil
|
352
|
+
(0...gp.rank).each do |dim|
|
353
|
+
crd = gp.coord(dim)
|
354
|
+
if /^degrees?_east$/i =~ crd.get_att("units")
|
355
|
+
lond = dim
|
356
|
+
break
|
357
|
+
elsif ( ( /longitude/i =~ crd.long_name ||
|
358
|
+
/^lon/i =~ crd.name ||
|
359
|
+
(nm=crd.get_att("standard_name") && /longitude/i =~ nm ) &&
|
360
|
+
(crd.units =~ Units["degrees_east"]) ) )
|
361
|
+
lond = dim
|
362
|
+
break
|
363
|
+
end
|
364
|
+
end
|
365
|
+
if err_raise
|
366
|
+
raise("Longitude dimension was not found") if !lond
|
367
|
+
end
|
368
|
+
lond
|
369
|
+
end
|
370
|
+
|
371
|
+
def find_lat_dim(gp, err_raise=false)
|
372
|
+
latd = nil
|
373
|
+
(0...gp.rank).each do |dim|
|
374
|
+
crd = gp.coord(dim)
|
375
|
+
if /^degrees?_north$/i =~ crd.get_att("units")
|
376
|
+
latd = dim
|
377
|
+
break
|
378
|
+
elsif ( ( /latitude/i =~ crd.long_name ||
|
379
|
+
/^lat/i =~ crd.name ||
|
380
|
+
(nm=crd.get_att("standard_name") && /latitude/i =~ nm ) &&
|
381
|
+
(crd.units =~ Units["degrees_north"]) ) )
|
382
|
+
latd = dim
|
383
|
+
break
|
384
|
+
end
|
385
|
+
end
|
386
|
+
if err_raise
|
387
|
+
raise("Latitude dimension was not found") if !latd
|
388
|
+
end
|
389
|
+
latd
|
390
|
+
end
|
391
|
+
|
257
392
|
end
|
258
393
|
|
259
394
|
end
|
data/lib/numru/ganalysis/qg.rb
CHANGED
@@ -213,7 +213,217 @@ module NumRu
|
|
213
213
|
divh
|
214
214
|
end
|
215
215
|
|
216
|
+
# Wave activity flux by Takaya and Nakamura (2001) (TN2001)
|
217
|
+
#
|
218
|
+
# This method works by mix-in in QG (Cartesian) or QG_sphere (spherical).
|
219
|
+
# (so the call it either as QG::waf_tn2001(gph, gphbs, ..) or
|
220
|
+
# QG_sphere::waf_tn2001(gph, gphbs, ..) ).
|
221
|
+
#
|
222
|
+
# DEFINITIONS
|
223
|
+
#
|
224
|
+
# * if called through QG: a modified version of Eq (32) of TN2001
|
225
|
+
# * if called through QG_sphere: a modified version of Eq (38) of TN2001
|
226
|
+
# The modification of Eq (32) and (38) is as follows:
|
227
|
+
# * The multiplication by p is omitted. This is because
|
228
|
+
# it is more convenient to do it afterwords if needed
|
229
|
+
# (for example, to integrate with p would require not to have
|
230
|
+
# done this multiplication).
|
231
|
+
# * The C_u M term is not included as in convention.
|
232
|
+
# (but the multiplication by cos(phi) is done in QG_sphere).
|
233
|
+
# Note that this term vanishes for stationary waves, because C_u=0.
|
234
|
+
# * The normal vector (n1, n2) in TN2001 can be set flexibly.
|
235
|
+
# This vector is called (nx, ny) in this method, since n2 is used
|
236
|
+
# for the buoyancy frequency. In eqs. (32) and (38), (nx, ny)
|
237
|
+
# is prescribed as (nx, ny) = (U,V)/|U,V| where U is ubs and V is vbs.
|
238
|
+
# However, this relation is not necessarily valid, since (nx, ny)
|
239
|
+
# is originally attributed to the QGPV gradient of the basic flow.
|
240
|
+
# Therefore, in this method, (nx, ny) can be set arbitrary.
|
241
|
+
# Also, one can set (nx, ny) = (1, 0) or (0, 1) by setting the
|
242
|
+
# optional argument zonal or merid to true.
|
243
|
+
#
|
244
|
+
# ARGUMENTS
|
245
|
+
# * gph: geopotential height
|
246
|
+
# * gphbs: geopotential height (basic state). Basically, it needs to have
|
247
|
+
# the same spacial grid as gph, but it can be a meridional section
|
248
|
+
# (such as zonal mean), if the normal vector is specified by
|
249
|
+
# (ubs, vbs) or (nx, ny) or zonal or merid.
|
250
|
+
# * nx, ny: normalized vector to designate the direction of wave
|
251
|
+
# propagation. By default, set to the direction of (ubs, vbs),
|
252
|
+
# or set by zonal/merid (see below).
|
253
|
+
# * ubs, vbs: Basic state zonal and meridional winds to compute
|
254
|
+
# the normal vector (nx,ny) as (ubs,vbs)/sqrt(ubs**2 + vbs**2).
|
255
|
+
# Neglected if nx and ny are specified. By default ubs and vbs
|
256
|
+
# are derived geostrophically from gphbs
|
257
|
+
# * zonal: derive the flux in the zonal direction assuming (nx,ny)=(1,0)
|
258
|
+
# * merid: derive the flux in the meridional dir assuming (nx,ny)=(0,1)
|
259
|
+
# * n2: Brunt-Vaisala (buoyancy) frequency squared. By default, derived
|
260
|
+
# from gphbs
|
261
|
+
# * derive_M0: derive M (the wave-activity pseudomomentum of TN20001)
|
262
|
+
# when the wave is stationary (cp=0). You need to give ubs and vbs
|
263
|
+
# (or have them derived from gphbs by not using nx, ny, zonal or merid)
|
264
|
+
# to make this option available.
|
265
|
+
#
|
266
|
+
# RETURN VALUE
|
267
|
+
# * [wx, wy, wz, ret_hash] where [wx, wy, wz] are the WAF vector
|
268
|
+
# and ret_hash provides extra derived quantities such as the
|
269
|
+
# perturbation stream function (:pp), pseudomomentum M0 (:M0), etc.
|
270
|
+
#
|
271
|
+
# REFERENCE
|
272
|
+
# * Takaya and Namaura (2001): A formulation of a phase-independent
|
273
|
+
# wave-activity flux for stationary and migratory quasigeostrophic
|
274
|
+
# eddies on a zonally varying basic flow. J. Atmos. Sci, 58, 608-627.
|
275
|
+
#
|
276
|
+
def waf_tn2001(gph, gphbs, nx: nil, ny: nil, ubs: nil, vbs: nil,
|
277
|
+
zonal: false, merid: false, n2: nil,
|
278
|
+
derive_M0: false)
|
279
|
+
|
280
|
+
#< prepration >
|
281
|
+
if self==QG
|
282
|
+
f = f0
|
283
|
+
wgt = nil
|
284
|
+
elsif self==QG_sphere
|
285
|
+
f = f_mask0(gph)
|
286
|
+
wgt = cos_phi(gph)
|
287
|
+
else
|
288
|
+
raise("unsupported module to mix-in #{self}")
|
289
|
+
end
|
290
|
+
ret_hash = Hash.new
|
291
|
+
|
292
|
+
#< perturbation stream function >
|
293
|
+
gf = Met::g / f
|
294
|
+
pp = (gph - gphbs)*gf # psi' (perturbation stream function)
|
295
|
+
if /gpm/i =~ (us=pp.units.to_s)
|
296
|
+
pp.units = us.sub(/gpm/i,'m')
|
297
|
+
end
|
298
|
+
ret_hash[:pp] = pp
|
299
|
+
|
300
|
+
#< derive nx, ny if not given >
|
301
|
+
ul = psibs = nil
|
302
|
+
if zonal
|
303
|
+
nx = 1.0
|
304
|
+
ny = 0.0
|
305
|
+
elsif merid
|
306
|
+
nx = 0.0
|
307
|
+
ny = 1.0
|
308
|
+
elsif nx.nil? && ny.nil?
|
309
|
+
if !ubs || !vbs
|
310
|
+
psibs, gpref = gph2psi_gpref(gphbs)
|
311
|
+
if /gpm/i =~ (us=psibs.units.to_s)
|
312
|
+
psibs.units = us.sub(/gpm/i,'m')
|
313
|
+
end
|
314
|
+
ubs, vbs = psi2ug_vg(psibs)
|
315
|
+
ret_hash[:ubs] = ubs
|
316
|
+
ret_hash[:vbs] = vbs
|
317
|
+
ret_hash[:gpref] = gpref
|
318
|
+
end
|
319
|
+
ul = (ubs**2+vbs**2).sqrt
|
320
|
+
nx = ubs/ul
|
321
|
+
ny = vbs/ul
|
322
|
+
end
|
323
|
+
|
324
|
+
#< derive aspect ratio >
|
325
|
+
unless n2
|
326
|
+
n2 = LogP::gph2n2(gphbs)
|
327
|
+
ret_hash[:n2] = n2
|
328
|
+
end
|
329
|
+
eps = f**2/n2
|
330
|
+
|
331
|
+
#< cross terms --> WAF >
|
332
|
+
px,py = grad_h(pp)
|
333
|
+
pxx,pxy = grad_h(px)
|
334
|
+
pyy = grad_hy(py) unless ny==0.0
|
335
|
+
pz = LogP::pcdata_dz(pp)
|
336
|
+
pxz = LogP::pcdata_dz(px) unless nx==0.0
|
337
|
+
pyz = LogP::pcdata_dz(py) unless ny==0.0
|
338
|
+
ret_hash[:px] = px
|
339
|
+
ret_hash[:py] = py
|
340
|
+
|
341
|
+
wx = nil
|
342
|
+
unless nx==0.0
|
343
|
+
wx = px*px - pp*pxx
|
344
|
+
wy = px*py - pp*pxy
|
345
|
+
wz = ( px*pz - pp*pxz ) * eps
|
346
|
+
nx2 = nx/2.0
|
347
|
+
wx = wx * nx2
|
348
|
+
wy = wy * nx2
|
349
|
+
wz = wz * nx2
|
350
|
+
end
|
351
|
+
|
352
|
+
unless ny==0.0
|
353
|
+
wx_ = px*py - pp*pxy
|
354
|
+
wy_ = py*py - pp*pyy
|
355
|
+
wz_ = (py*pz - pp*pyz) * eps
|
356
|
+
ny2 = ny/2.0
|
357
|
+
wx_ = wx_ * ny2
|
358
|
+
wy_ = wy_ * ny2
|
359
|
+
wz_ = wz_ * ny2
|
360
|
+
if wx
|
361
|
+
wx += wx_
|
362
|
+
wy += wy_
|
363
|
+
wz += wz_
|
364
|
+
else
|
365
|
+
wx = wx_
|
366
|
+
wy = wy_
|
367
|
+
wz = wz_
|
368
|
+
end
|
369
|
+
end
|
216
370
|
|
371
|
+
if wgt
|
372
|
+
wx = wx * wgt
|
373
|
+
wy = wy * wgt
|
374
|
+
wz = wz * wgt
|
375
|
+
end
|
376
|
+
wx.name = "Wx"
|
377
|
+
wy.name = "Wy"
|
378
|
+
wz.name = "Wz"
|
379
|
+
wx.long_name = "WAFx"
|
380
|
+
wy.long_name = "WAFy"
|
381
|
+
wz.long_name = "WAFz"
|
382
|
+
|
383
|
+
#< derive M0 if required >
|
384
|
+
if derive_M0
|
385
|
+
raise("ubs and vbs are needed to derive M0") if !ubs || !vbs
|
386
|
+
ul = (ubs**2+vbs**2).sqrt unless ul
|
387
|
+
unless psibs
|
388
|
+
psibs, gpref = gph2psi_gpref(gphbs)
|
389
|
+
if /gpm/i =~ (us=psibs.units.to_s)
|
390
|
+
psibs.units = us.sub(/gpm/i,'m')
|
391
|
+
end
|
392
|
+
ret_hash[:gpref] = gpref
|
393
|
+
end
|
394
|
+
n2p = Planet::ave_s(n2)
|
395
|
+
if n2p.rank >= 2
|
396
|
+
pdim = Met.find_prs_d(n2p)
|
397
|
+
ds = Array.new
|
398
|
+
(0...n2p.rank).each do |d|
|
399
|
+
ds.push(d) unless d == pdim
|
400
|
+
end
|
401
|
+
n2p = n2p.mean(*ds)
|
402
|
+
end
|
403
|
+
qp, = psi2q(pp,n2p)
|
404
|
+
qbs, = psi2q(psibs,n2p)
|
405
|
+
qbx, qby = grad_h(qbs)
|
406
|
+
e2 = px**2 + py**2 + pz**2 * eps # 2*e
|
407
|
+
qbgrad = (qbx**2+qby**2).sqrt.running_mean(0,3).running_mean(1,3)
|
408
|
+
a = qp**2 / qbgrad / 4.0 # A of TN2001
|
409
|
+
e0 = e2 / ul / 4.0 # E of TN2001 when cp=0
|
410
|
+
m0 = a + e0
|
411
|
+
a.name = "A"
|
412
|
+
a.long_name = "A"
|
413
|
+
e0.name = "E0"
|
414
|
+
e0.long_name = "stationary E"
|
415
|
+
m0.name = "M0"
|
416
|
+
m0.long_name = "stationary TN2001 pseudomomentum"
|
417
|
+
ret_hash[:qp] = qp
|
418
|
+
ret_hash[:qbs] = qbs
|
419
|
+
ret_hash[:A] = a
|
420
|
+
ret_hash[:E0] = e0
|
421
|
+
ret_hash[:M0] = m0
|
422
|
+
end
|
423
|
+
|
424
|
+
#< return >
|
425
|
+
[wx, wy, wz, ret_hash]
|
426
|
+
end
|
217
427
|
end
|
218
428
|
|
219
429
|
######################################################
|
@@ -409,6 +619,12 @@ module NumRu
|
|
409
619
|
def grad_h(gphys)
|
410
620
|
@@bp.grad_h(gphys)
|
411
621
|
end
|
622
|
+
def grad_hy(gphys)
|
623
|
+
@@bp.grad_y(gphys)
|
624
|
+
end
|
625
|
+
def grad_hx(gphys)
|
626
|
+
@@bp.grad_x(gphys)
|
627
|
+
end
|
412
628
|
|
413
629
|
# horizontal divergence (Cartesian)
|
414
630
|
def div_h(gphys_u, gphys_v)
|
@@ -442,9 +658,8 @@ module NumRu
|
|
442
658
|
|
443
659
|
# geopotential height -> geostrophic winds
|
444
660
|
def gph2ug_vg(gph)
|
445
|
-
|
446
|
-
|
447
|
-
[ -gpy/f, gpx/f]
|
661
|
+
psi, gpref = gph2psi_gpref(gph)
|
662
|
+
psi2ug_vg(psi)
|
448
663
|
end
|
449
664
|
|
450
665
|
# geopotential height -> QG stream function and the reference geopotential
|
@@ -524,6 +739,12 @@ module NumRu
|
|
524
739
|
def grad_h(gphys)
|
525
740
|
Planet::grad_s(gphys)
|
526
741
|
end
|
742
|
+
def grad_hx(gphys)
|
743
|
+
Planet::grad_sx(gphys)
|
744
|
+
end
|
745
|
+
def grad_hy(gphys)
|
746
|
+
Planet::grad_sy(gphys)
|
747
|
+
end
|
527
748
|
|
528
749
|
# horizontal divergence (spherical)
|
529
750
|
def div_h(fx, fy)
|