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.
Files changed (41) hide show
  1. checksums.yaml +5 -5
  2. data/ChangeLog +0 -7595
  3. data/LICENSE.txt +1 -1
  4. data/bin/gpcut +12 -0
  5. data/bin/gpvect +88 -77
  6. data/bin/gpview +200 -103
  7. data/ext/numru/ganalysis/ext_init.c +9 -0
  8. data/ext/numru/ganalysis/extconf.rb +43 -0
  9. data/ext/numru/ganalysis/narray_ext_dfloat.c +84 -0
  10. data/ext/numru/ganalysis/pde_ext.c +130 -0
  11. data/ext/numru/gphys/dim_op.c +336 -44
  12. data/ext/numru/gphys/ext_init.c +6 -0
  13. data/ext/numru/gphys/interpo.c +326 -3
  14. data/gphys.gemspec +1 -0
  15. data/lib/numru/dclext.rb +78 -16
  16. data/lib/numru/ganalysis/beta_plane.rb +6 -4
  17. data/lib/numru/ganalysis/eof.rb +63 -41
  18. data/lib/numru/ganalysis/fitting.rb +109 -30
  19. data/lib/numru/ganalysis/histogram.rb +3 -3
  20. data/lib/numru/ganalysis/log_p.rb +20 -0
  21. data/lib/numru/ganalysis/lomb_scargle.rb +205 -0
  22. data/lib/numru/ganalysis/met_z.rb +132 -3
  23. data/lib/numru/ganalysis/narray_ext.rb +13 -0
  24. data/lib/numru/ganalysis/pde.rb +109 -0
  25. data/lib/numru/ganalysis/planet.rb +136 -1
  26. data/lib/numru/ganalysis/qg.rb +224 -3
  27. data/lib/numru/ggraph.rb +95 -23
  28. data/lib/numru/gphys/axis.rb +4 -2
  29. data/lib/numru/gphys/gphys.rb +6 -5
  30. data/lib/numru/gphys/gphys_dim_op.rb +69 -6
  31. data/lib/numru/gphys/gphys_fft.rb +30 -0
  32. data/lib/numru/gphys/gphys_io_common.rb +2 -0
  33. data/lib/numru/gphys/grads_gridded.rb +77 -29
  34. data/lib/numru/gphys/grib.rb +2 -2
  35. data/lib/numru/gphys/grib_params.rb +3 -3
  36. data/lib/numru/gphys/interpolate.rb +153 -1
  37. data/lib/numru/gphys/varraycomposite.rb +7 -4
  38. data/lib/numru/gphys/version.rb +1 -1
  39. data/lib/numru/gphys.rb +1 -0
  40. data/testdata/pres.jan.nc +0 -0
  41. 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
- v_val = v_val.to_na if v_val.is_a?(NArrayMiss)
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
- pcv_over_g, true, ps_over_g, vs_val)
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
- lam, phi, lond, latd = get_lambda_phi(s)
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