gphys 1.5.5 → 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 -7595
- data/LICENSE.txt +1 -1
- data/bin/gpcut +12 -0
- data/bin/gpvect +88 -77
- data/bin/gpview +200 -103
- data/ext/numru/ganalysis/ext_init.c +9 -0
- data/ext/numru/ganalysis/extconf.rb +43 -0
- data/ext/numru/ganalysis/narray_ext_dfloat.c +84 -0
- data/ext/numru/ganalysis/pde_ext.c +130 -0
- 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/gphys.gemspec +1 -0
- data/lib/numru/dclext.rb +78 -16
- data/lib/numru/ganalysis/beta_plane.rb +6 -4
- data/lib/numru/ganalysis/eof.rb +63 -41
- data/lib/numru/ganalysis/fitting.rb +109 -30
- 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 +13 -0
- data/lib/numru/ganalysis/pde.rb +109 -0
- data/lib/numru/ganalysis/planet.rb +136 -1
- data/lib/numru/ganalysis/qg.rb +224 -3
- data/lib/numru/ggraph.rb +95 -23
- data/lib/numru/gphys/axis.rb +4 -2
- data/lib/numru/gphys/gphys.rb +6 -5
- 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/lib/numru/gphys.rb +1 -0
- data/testdata/pres.jan.nc +0 -0
- metadata +13 -4
@@ -0,0 +1,205 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
=begin
|
3
|
+
= Lomb-Scargle periodgram
|
4
|
+
=end
|
5
|
+
|
6
|
+
require "numru/gphys"
|
7
|
+
|
8
|
+
module NumRu
|
9
|
+
module GAnalysis
|
10
|
+
module LombScargle
|
11
|
+
module_function
|
12
|
+
|
13
|
+
# = Lomb-Scargle periodgram
|
14
|
+
#
|
15
|
+
# ARGUMENTS
|
16
|
+
# * y [NArray or NArrayMiss] : data
|
17
|
+
# * dim [Integer] : the dimension to apply LS
|
18
|
+
# * t [1D NArray] : "time" (the coordinate values along dim)
|
19
|
+
# * f [Numric or 1D NArray] : frequency. If 1D NArray, the frequency
|
20
|
+
# dimension is added to the end of outputs
|
21
|
+
#
|
22
|
+
# OUTPUTS
|
23
|
+
# * a : coef of cos
|
24
|
+
# * b : coef of sin (sqrt(a**2+b**2) is the amplitude)
|
25
|
+
# * reconst : reconstructed y (fitting result)
|
26
|
+
# * cos [NArray or NArrayMiss]: cos(2*pi*f*t + ph)
|
27
|
+
# (useful if you want to reconstruct for a part of f)
|
28
|
+
# * sin [NArray or NArrayMiss]: sin(2*pi*f*t + ph)
|
29
|
+
# (useful if you want to reconstruct for a part of f)
|
30
|
+
# * cc [NArray or NArrayMiss]: normarization factor sum(cos**2)
|
31
|
+
# (needed to derive power spectrum)
|
32
|
+
# * ss [NArray or NArrayMiss]: normarization factor sum(sin**2)
|
33
|
+
# (needed to derive power spectrum)
|
34
|
+
# * ph [NArray or NArrayMiss]: phase (for each f and,
|
35
|
+
# if with miss, for each grid point)
|
36
|
+
def lomb_scargle(y, dim, t, f)
|
37
|
+
rank = y.rank
|
38
|
+
if y.respond_to?(:get_mask)
|
39
|
+
mask = y.get_mask
|
40
|
+
else
|
41
|
+
mask = nil
|
42
|
+
end
|
43
|
+
ph, ot_ph = derive_phase(t,f,dim,rank,mask)
|
44
|
+
cos = Misc::EMath.cos( ot_ph )
|
45
|
+
sin = Misc::EMath.sin( ot_ph )
|
46
|
+
cc = (cos*cos).sum(dim)
|
47
|
+
ss = (sin*sin).sum(dim)
|
48
|
+
#p "%%%", (cos*sin).sum(dim), cc, ss
|
49
|
+
if !f.is_a?(Numeric)
|
50
|
+
y = y.newdim(-1)
|
51
|
+
end
|
52
|
+
a = (y*cos).sum(dim) / cc
|
53
|
+
b = (y*sin).sum(dim) / ss
|
54
|
+
reconst = (a.newdim(-2)*cos).sum(-1) + (b.newdim(-2)*sin).sum(-1)
|
55
|
+
[a,b,reconst,cos,sin,cc,ss,ph]
|
56
|
+
end
|
57
|
+
|
58
|
+
def derive_phase(t,f,dim,rank,mask=nil)
|
59
|
+
multiple_f = !f.is_a?(Numeric)
|
60
|
+
if multiple_f
|
61
|
+
o2t = 4*Math::PI * f.newdim(0) * t.newdim(-1) # 2 omega t
|
62
|
+
mask = mask.newdim(-1) if mask
|
63
|
+
nf = f.length
|
64
|
+
else
|
65
|
+
o2t = 4*Math::PI * f * t # 2 omega t
|
66
|
+
end
|
67
|
+
with_miss = (mask && mask.count_false > 0)
|
68
|
+
if dim >= 1
|
69
|
+
o2t = o2t.newdim!( *([0]*dim) )
|
70
|
+
end
|
71
|
+
if dim < rank-1
|
72
|
+
o2t = o2t.newdim!( *([dim+1]*(rank-1-dim)) )
|
73
|
+
end
|
74
|
+
if with_miss
|
75
|
+
# sampling is not common for all grid points
|
76
|
+
expander = NArray.float( *mask.shape )
|
77
|
+
o2t += expander # duplicates o2t for all grid points
|
78
|
+
if multiple_f
|
79
|
+
expander = NArray.byte(*([1]*rank+[nf])) # duplicates mask for all f
|
80
|
+
mask += expander
|
81
|
+
end
|
82
|
+
o2t = NArrayMiss.to_nam(o2t,mask)
|
83
|
+
end
|
84
|
+
ph = 0.5* Misc::EMath::atan2( Misc::EMath.sin(o2t).sum(dim),
|
85
|
+
-Misc::EMath.cos(o2t).sum(dim)).newdim(dim)
|
86
|
+
ot_ph = o2t*0.5 + ph # omega*t + ph
|
87
|
+
[ph, ot_ph]
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
class GPhys
|
93
|
+
# = Lomb-Scargle periodgram
|
94
|
+
#
|
95
|
+
# The direct outputs are the amplitude of each wave components,
|
96
|
+
# ga as the coefficties for cos and gb as the coefficties for sin.
|
97
|
+
#
|
98
|
+
# LS power spectrum can be derived as
|
99
|
+
# pw = ga**2 * cc/(ntlen*df) + gb**2 * ss/(ntlen*df)
|
100
|
+
# = ga**2 * cc/(2*fmax) + gb**2 * ss/(2*fmax)
|
101
|
+
# Here, ntlen=2*f.length (equivalent data length from frequency sampling).
|
102
|
+
#
|
103
|
+
# ARGUMENTS
|
104
|
+
# * dim [Integer or String] : dimension to apply fitting
|
105
|
+
# * fmax [Numeric]: max frequency (sampling frequencies [df, 2*df,...,fmax])
|
106
|
+
# fmax should be a multiple of df
|
107
|
+
# * df [Numeric/nil]: frequency increment (If nil, set to fmax: single freq)
|
108
|
+
# * f_long_name, f_name : long_name and name of the "frequency" axis.
|
109
|
+
# You may want to specify them if the dimension is not time.
|
110
|
+
#
|
111
|
+
# OUTPUTS
|
112
|
+
# * ga [GPhys]: coef of cos
|
113
|
+
# * gb [GPhys]: coef of sin (sqrt(a**2+b**2) is the amplitude)
|
114
|
+
# * greconst [GPhys]: reconstructed y (fitting result)
|
115
|
+
# * cos [NArray or NArrayMiss]: cos(2*pi*f*t + ph)
|
116
|
+
# (useful if you want to reconstruct for a part of f)
|
117
|
+
# * sin [NArray or NArrayMiss]: sin(2*pi*f*t + ph)
|
118
|
+
# (useful if you want to reconstruct for a part of f)
|
119
|
+
# * cc [NArray or NArrayMiss]: normarization factor sum(cos**2)
|
120
|
+
# (needed to derive power spectrum)
|
121
|
+
# * ss [NArray or NArrayMiss]: normarization factor sum(sin**2)
|
122
|
+
# (needed to derive power spectrum)
|
123
|
+
# * ph [NArray or NArrayMiss]: phase (for each f and,
|
124
|
+
# if with miss, for each grid point)
|
125
|
+
# * f [1D NArray]: frequencies derived from df and fmax
|
126
|
+
|
127
|
+
def lomb_scargle(dim, fmax, df=nil, f_long_name="frequency", f_name="f")
|
128
|
+
|
129
|
+
#< prep >
|
130
|
+
df = fmax if df.nil?
|
131
|
+
dim = dim_index( dim )
|
132
|
+
ct = coord(dim)
|
133
|
+
t = ct.val
|
134
|
+
nf = (fmax/df).round
|
135
|
+
f = df * (NArray.float(nf).indgen!+1.0)
|
136
|
+
y = val
|
137
|
+
|
138
|
+
#< do it >
|
139
|
+
a, b, reconst, cos, sin, cc, ss, ph =
|
140
|
+
GAnalysis::LombScargle::lomb_scargle(y, dim, t, f)
|
141
|
+
|
142
|
+
#< to gphys >
|
143
|
+
tun = Units.new( coord(dim).units.to_s.sub(/ *since.*$/,'') )
|
144
|
+
fun = (tun**(-1)).to_s
|
145
|
+
fax = Axis.new.set_pos(
|
146
|
+
VArray.new(f, {"long_name"=>f_long_name,"units"=>fun}, f_name) )
|
147
|
+
oaxes = (0...rank).map{|d| axis(d)}
|
148
|
+
caxes = oaxes.dup
|
149
|
+
caxes[dim] = fax
|
150
|
+
cgrid = Grid.new(*caxes) # grid of fitting (~Fourier) coefficients
|
151
|
+
un = units.to_s
|
152
|
+
ga = GPhys.new( cgrid,
|
153
|
+
VArray.new(a,{"long_name"=>"coef a","units"=>un},"a") )
|
154
|
+
gb = GPhys.new( cgrid,
|
155
|
+
VArray.new(b,{"long_name"=>"coef b","units"=>un},"b") )
|
156
|
+
greconst = GPhys.new( grid, VArray.new(reconst,data,name) )
|
157
|
+
[ga, gb, greconst, cos, sin, cc, ss, ph, f]
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
162
|
+
|
163
|
+
################################################
|
164
|
+
##### test part ######
|
165
|
+
if $0 == __FILE__
|
166
|
+
require "numru/ggraph"
|
167
|
+
include NumRu
|
168
|
+
include Misc::EMath
|
169
|
+
|
170
|
+
cx = VArray.new(NArray.float(2).indgen!, {"units"=>"1","long_name"=>"x"}, "x")
|
171
|
+
|
172
|
+
## unequal spacing : fitting
|
173
|
+
#t = NArray.to_na( [0.0, 0.1, 1, 1.5, 3, 3.3, 3.8, 6, 6.3, 7, 7,5, 8, 9] )
|
174
|
+
|
175
|
+
## equal spacing with missing
|
176
|
+
#t = NArray.to_na( [0.0, 2, 3, 4, 5, 6, 7, 8, 9] )
|
177
|
+
|
178
|
+
# equal spacing: equal to DFT if f is multiples of 1/10
|
179
|
+
t = NArray.float(10).indgen!
|
180
|
+
|
181
|
+
ct = VArray.new(t, {"units"=>"days","long_name"=>"time"},"t")
|
182
|
+
grid = Grid.new( Axis.new.set_pos(cx), Axis.new.set_pos(ct) )
|
183
|
+
nt = t.length
|
184
|
+
|
185
|
+
v = NArrayMiss.float(2,nt)
|
186
|
+
f = 1.0/10.0 # base frequency
|
187
|
+
o = 2*PI*f
|
188
|
+
v[0,true] = sin(o*t) + 0.4*sin(2*o*t) + 0.2*cos(2*o*t) + 0.3*cos(3*o*t)
|
189
|
+
v[1,true] = cos(o*t) + 0.2*sin(2*o*t) + 0.2*cos(2*o*t) + 2*sin(3*o*t)
|
190
|
+
|
191
|
+
v.invalidation(1,1) # then, v[1,true] fitting will be different from DFT
|
192
|
+
|
193
|
+
y = VArray.new(v, {"units"=>"m/s","long_name"=>"y"}, "y")
|
194
|
+
gp = GPhys.new(grid, y)
|
195
|
+
|
196
|
+
df = f
|
197
|
+
fmax = 4*df
|
198
|
+
#df = f / 2
|
199
|
+
#fmax = 8*df
|
200
|
+
ga, gb, greconst, cos, sin, ph = gp.lomb_scargle("t",fmax,df)
|
201
|
+
p "**", ga, ph
|
202
|
+
ampsp = ga**2 + gb**2
|
203
|
+
amp = Misc::EMath.sqrt(ampsp.val)
|
204
|
+
p "&&", amp, ampsp.val.sum(-1)/2, ((greconst.val - v)**2).mean(-1)
|
205
|
+
end
|
@@ -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
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require "narray"
|
2
|
+
require "numru/ganalysis_ext"
|
3
|
+
|
4
|
+
class NArray
|
5
|
+
def cum_sum(dim)
|
6
|
+
case self.typecode
|
7
|
+
when DFLOAT
|
8
|
+
NumRu::NArrayExt.cum_sum_dfloat(self,dim)
|
9
|
+
else
|
10
|
+
raise "Sorry, this narray type #{self.typecode} is not supported."
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require "numru/gphys"
|
2
|
+
require "numru/ganalysis_ext"
|
3
|
+
|
4
|
+
module NumRu
|
5
|
+
module GAnalysis
|
6
|
+
module PDE
|
7
|
+
module_function
|
8
|
+
#
|
9
|
+
# = Solve 2-dim 2nd order PDE by the succesive over-relaxation method
|
10
|
+
#
|
11
|
+
# ARGUMENTS.
|
12
|
+
# * z [in-out, 2D dfloat NArray] : When in, initial values. When out,
|
13
|
+
# the result. Boundary values are not changed (Dirichlet problem)
|
14
|
+
# (developper's memo: Neuman cond can be supported as an option)
|
15
|
+
# * a, b, c, d, e, f [in, 2D dfloat NArray with the same shape as z] :
|
16
|
+
# Specifies the PDF as
|
17
|
+
# a z_xx + 2*b z_xy + c z_yy + d z_x + e z_y = f
|
18
|
+
# ^^^
|
19
|
+
# Please note that 2*b is the coefficient of z_xy.
|
20
|
+
# x is the first (0th) NArray dimension.
|
21
|
+
# * dx, dy [Float] : grid spacings
|
22
|
+
# * ome [Float] : the over-relaxation parameter 1<=ome<2.
|
23
|
+
# For a large-scale poisson equation, should be ~ 2 (e.g. 1.9).
|
24
|
+
# * eps [optional, Float] : conversion threshold (e.g. 1e-5).
|
25
|
+
# Compared with |increment|/|F| (| | is the L2 norm).
|
26
|
+
# When |f| == 0 (homogenous), compared simply with |increment|.
|
27
|
+
# * maxi [optional, Integer]: max iteration times(default, 5*[nx,ny].max).
|
28
|
+
# Since SOR (or the Gauss-Seidel method) acts like a stepwise diffusion,
|
29
|
+
# it must be greater than the along-x and along-y grid sizes.
|
30
|
+
# * ignore_non_eliptic : if true (non-nil,non-false), do not raise
|
31
|
+
# error when the coeeficients are not entirely eliptic.
|
32
|
+
#
|
33
|
+
# RETURN VALUE
|
34
|
+
# * |last increment|/|F| of |last increment| (to be compared with eps)
|
35
|
+
#
|
36
|
+
def SOR_2D_2ndorder(z, a, b, c, d, e, f, dx, dy, ome, eps: nil, maxi: nil,
|
37
|
+
ignore_non_eliptic: false)
|
38
|
+
lf = Math.sqrt( (f**2).sum ) # L2 norm of f
|
39
|
+
lf = 1.0 if lf==0 # when |f|==0 (homogeneous), no-division by |f|.
|
40
|
+
res = nil
|
41
|
+
if !maxi
|
42
|
+
maxi = [ z.shape[0], z.shape[1] ].max * 5 # default max itr times
|
43
|
+
end
|
44
|
+
convd = false
|
45
|
+
for i in 0...maxi
|
46
|
+
res = SOR_2D_2ndorder_1step(z, a, b, c, d, e, f, dx, dy, ome,
|
47
|
+
ignore_non_eliptic)
|
48
|
+
#p res/lf, z
|
49
|
+
if (eps && res/lf < eps)
|
50
|
+
convd = true
|
51
|
+
break
|
52
|
+
end
|
53
|
+
end
|
54
|
+
if (eps && !convd)
|
55
|
+
raise("Max iteration times (#{maxi}) reached before convergence" +
|
56
|
+
" (#{res/lf} > #{eps}). Change ome (#{ome}) or specify maxi.")
|
57
|
+
end
|
58
|
+
#p i,res/lf #, z
|
59
|
+
res/lf
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
################################################
|
66
|
+
##### test part ######
|
67
|
+
if $0 == __FILE__
|
68
|
+
require "numru/ggraph"
|
69
|
+
include NumRu
|
70
|
+
|
71
|
+
#nx = ny = 9
|
72
|
+
nx = ny = 91
|
73
|
+
z = NArray.float(nx,ny)
|
74
|
+
a = c = NArray.float(nx,ny).fill!(1.0)
|
75
|
+
b = d = e = NArray.float(nx,ny)
|
76
|
+
f = NArray.float(nx,ny)
|
77
|
+
f[nx/2,ny/2] = 1.0
|
78
|
+
dx = dy = 1.0
|
79
|
+
#ome = 1.9
|
80
|
+
#GAnalysis::PDE.SOR_2D_2ndorder(z, a, b, c, d, e, f, dx, dy, ome)
|
81
|
+
=begin
|
82
|
+
(1.0..1.91).step(0.1){|ome|
|
83
|
+
puts "ome = #{ome}"
|
84
|
+
GAnalysis::PDE.SOR_2D_2ndorder(z, a, b, c, d, e, f, dx, dy, ome)
|
85
|
+
}
|
86
|
+
=end
|
87
|
+
eps = 1e-6
|
88
|
+
ome = 1.95
|
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
|
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
|