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
data/lib/numru/ganalysis/eof.rb
CHANGED
@@ -44,6 +44,7 @@ module NumRu
|
|
44
44
|
# it is cosine of latitude when the first two axes of the +gphys+ are "lon" and "lat" and +disable_weight+ option is not +true+,
|
45
45
|
# else 1.
|
46
46
|
# * disable_weight: See weight option.
|
47
|
+
# * no_pc: do not compute primary components
|
47
48
|
#
|
48
49
|
# == Return values
|
49
50
|
# +eof+:: GPhys object for array of EOF vectors.
|
@@ -61,10 +62,12 @@ module NumRu
|
|
61
62
|
dims = args
|
62
63
|
opts = Hash.new
|
63
64
|
end
|
64
|
-
|
65
|
+
no_pc = ( opts[:no_pc] || opts["no_pc"] )
|
66
|
+
|
67
|
+
dims = dims.map{|dim| gphys.dim_index(dim) } # dimensions to vary (~"time")
|
65
68
|
n = 1
|
66
69
|
n_lost = 1
|
67
|
-
dims1 = Array.new
|
70
|
+
dims1 = Array.new # vector dimensions (~"spatial")
|
68
71
|
shape1 = Array.new
|
69
72
|
gphys.shape.each_with_index{|s,i|
|
70
73
|
if dims.include?(i)
|
@@ -75,35 +78,41 @@ module NumRu
|
|
75
78
|
shape1.push s
|
76
79
|
end
|
77
80
|
}
|
78
|
-
|
79
|
-
new_index = NArray.sint(*
|
81
|
+
new_grid_wo_modes = gphys.instance_variable_get("@grid").delete_axes(dims, "covariance matrix").copy
|
82
|
+
new_index = NArray.sint(*new_grid_wo_modes.shape).indgen
|
80
83
|
index = NArray.object(gphys.rank)
|
81
84
|
index[dims] = true
|
82
85
|
|
86
|
+
wgtun = ""
|
83
87
|
if w = (opts[:weight] || opts["weight"])
|
84
88
|
if GPhys === w
|
85
89
|
w = w.val
|
90
|
+
wgtun = w.units
|
86
91
|
end
|
87
92
|
unless NArray === w
|
88
93
|
raise "weight must be NArray of GPhys"
|
89
94
|
end
|
90
|
-
unless w.shape ==
|
95
|
+
unless w.shape == new_grid_wo_modes.shape
|
91
96
|
raise "shape of weight is invalid"
|
92
97
|
end
|
93
|
-
|
94
|
-
w.reshape
|
95
|
-
elsif
|
96
|
-
nc0 =
|
97
|
-
nc1 =
|
98
|
+
w_orgshape = w
|
99
|
+
w = w.reshape(n)
|
100
|
+
elsif new_grid_wo_modes.rank >= 2
|
101
|
+
nc0 = new_grid_wo_modes.coord(0)
|
102
|
+
nc1 = new_grid_wo_modes.coord(1)
|
98
103
|
if !(opts[:disable_weight]||opts["disable_weight"]) &&
|
99
104
|
( /^lon/ =~ nc0.name || /^degree.*_east/ =~ nc0.units.to_s ) &&
|
100
105
|
( /^lat/ =~ nc1.name || /^degree.*_north/ =~ nc1.units.to_s )
|
101
106
|
rad = NumRu::Units.new("radian")
|
102
107
|
nlon = nc0.length
|
103
108
|
lat = nc1.convert_units(rad).val
|
104
|
-
|
105
|
-
|
106
|
-
|
109
|
+
w2 = NMath::cos(lat)
|
110
|
+
eps = 1e-10 # a value whose sqrt is negligibly small as cosine
|
111
|
+
w2[ w2.le(0) ] = eps
|
112
|
+
w = NMath.sqrt(w2) # sqrt(cos(lat))
|
113
|
+
w = NArray.new(lat.typecode,nlon).fill!(1) * w.reshape(1,lat.length)
|
114
|
+
w_orgshape = w
|
115
|
+
w = w.reshape(w.length)
|
107
116
|
else
|
108
117
|
w = nil
|
109
118
|
end
|
@@ -111,27 +120,9 @@ module NumRu
|
|
111
120
|
w = nil
|
112
121
|
end
|
113
122
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
n.times{|n1|
|
118
|
-
index[dims1] = ind
|
119
|
-
val = gphys[*index].val
|
120
|
-
val.reshape!(n_lost)
|
121
|
-
vm = val.mean
|
122
|
-
val -= vm unless vm.nil?
|
123
|
-
ary[true,n1] = val
|
124
|
-
break if n1==n-1
|
125
|
-
ind[0] += 1
|
126
|
-
ind_rank.times{|i|
|
127
|
-
if ind[i] == shape1[i]
|
128
|
-
ind[i] = 0
|
129
|
-
ind[i+1] += 1
|
130
|
-
else
|
131
|
-
break
|
132
|
-
end
|
133
|
-
}
|
134
|
-
}
|
123
|
+
val = gphys.val
|
124
|
+
ary = val.transpose( *(dims + dims1) ).reshape!(n_lost, n)
|
125
|
+
ary -= ary.mean(0).newdim(0) # remove "temporal" mean
|
135
126
|
ary = ary.mul!(w.reshape(1,n)) if w
|
136
127
|
|
137
128
|
nmodes = opts[:nmodes] || opts["nmodes"] || n
|
@@ -150,7 +141,7 @@ module NumRu
|
|
150
141
|
nn += 1
|
151
142
|
end
|
152
143
|
}
|
153
|
-
ary = nil # for GC
|
144
|
+
ary = nil if no_pc # for GC
|
154
145
|
print "start calc eigen vector\n" if $DEBUG
|
155
146
|
val, vec = SSL2.seig2(nary,nmodes)
|
156
147
|
when "lapack"
|
@@ -161,7 +152,7 @@ module NumRu
|
|
161
152
|
nary[n0...n,n0] = (ary[true,n0...n].mul_add(ary[true,n0],0)/(n_lost-1)).get_array!
|
162
153
|
total_var += nary[n0,n0]
|
163
154
|
}
|
164
|
-
ary = nil # for GC
|
155
|
+
ary = nil if no_pc # for GC
|
165
156
|
print "start calc eigen vector\n" if $DEBUG
|
166
157
|
case nary.typecode
|
167
158
|
when NArray::DFLOAT
|
@@ -171,8 +162,8 @@ module NumRu
|
|
171
162
|
m, val, vec, isuppz, work, iwork, info, = NumRu::Lapack.ssyevr("V", "I", "L", nary, 0, 0, n-nmodes+1, n, 0.0, -1, -1)
|
172
163
|
m, val, vec, = NumRu::Lapack.ssyevr("V", "I", "L", nary, 0, 0, n-nmodes+1, n, 0.0, work[0], iwork[0])
|
173
164
|
end
|
174
|
-
val = val[-1..0]
|
175
165
|
vec = vec[true,-1..0]
|
166
|
+
val = val[0...nmodes][-1..0]
|
176
167
|
when "gsl"
|
177
168
|
print "start calc covariance matrix\n" if $DEBUG
|
178
169
|
nary = NArray.new(gphys.typecode,n,n)
|
@@ -180,7 +171,7 @@ module NumRu
|
|
180
171
|
nary[n0...n,n0] = (ary[true,n0...n].mul_add(ary[true,n0],0)/(n_lost-1)).get_array!
|
181
172
|
nary[n0,n0...n] = nary[n0...n,n0]
|
182
173
|
}
|
183
|
-
ary = nil # for GC
|
174
|
+
ary = nil if no_pc # for GC
|
184
175
|
print "start calc eigen vector\n" if $DEBUG
|
185
176
|
val, vec = GSL::Eigen::symmv(nary.to_gm)
|
186
177
|
GSL::Eigen.symmv_sort(val, vec, GSL::Eigen::SORT_VAL_DESC)
|
@@ -190,15 +181,32 @@ module NumRu
|
|
190
181
|
val = val[0...nmodes]
|
191
182
|
end
|
192
183
|
|
193
|
-
axes =
|
184
|
+
axes = new_grid_wo_modes.instance_variable_get('@axes')
|
194
185
|
axis_order = Axis.new
|
195
186
|
axis_order.pos = VArray.new(NArray.sint(nmodes).indgen(1),
|
196
187
|
{}, "mode")
|
197
|
-
axes
|
188
|
+
axes = axes + [ axis_order ]
|
198
189
|
new_grid = Grid.new(*axes)
|
190
|
+
|
191
|
+
unless no_pc
|
192
|
+
mary = NMatrix.new(ary.typecode, *ary.shape)
|
193
|
+
mary[true,true] = ary.to_na
|
194
|
+
mvec = NMatrix.new(vec.typecode, *vec.shape)
|
195
|
+
mvec[true,true] = vec
|
196
|
+
mpc = mvec * mary # use matrix multiplication
|
197
|
+
pc = NArray.new(mpc.typecode, *mpc.shape)
|
198
|
+
pc[true,true] = mpc
|
199
|
+
pc /= pc.stddev(0).newdim!(0) # normalization
|
200
|
+
vary = VArray.new(pc, {"long_name"=>"Primary component",
|
201
|
+
"units"=>"1"}, "PC")
|
202
|
+
grid = Grid.new( *dims.map{|d| gphys.grid.axis(d)} + axes[-1..-1] )
|
203
|
+
pc = GPhys.new( grid, vary )
|
204
|
+
end
|
205
|
+
|
199
206
|
vec /= w if w
|
200
207
|
vec.reshape!(*new_grid.shape)
|
201
208
|
vec *= NMath::sqrt( val.reshape( *([1]*(axes.length-1)+[nmodes]) ) )
|
209
|
+
# multiply a sqrt(eigen value) -> variance same as in the input data
|
202
210
|
va_eof = VArray.new(vec,
|
203
211
|
{"long_name"=>"EOF vector","units"=>gphys.units.to_s },
|
204
212
|
"EOF")
|
@@ -209,7 +217,19 @@ module NumRu
|
|
209
217
|
"rate")
|
210
218
|
rate = GPhys.new(Grid.new(axis_order), va_rate)
|
211
219
|
|
212
|
-
|
220
|
+
if w
|
221
|
+
w = w_orgshape
|
222
|
+
if w.rank == new_grid_wo_modes.rank
|
223
|
+
v = VArray.new(w, {"long_name"=>"weight",
|
224
|
+
"units"=>wgtun}, "weight" )
|
225
|
+
w = GPhys.new( new_grid_wo_modes,
|
226
|
+
v )
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
ret = [eof, rate, w]
|
231
|
+
ret.push(pc) unless no_pc
|
232
|
+
return ret
|
213
233
|
end
|
214
234
|
|
215
235
|
def eof2(gphys1, gphys2, *args)
|
@@ -96,6 +96,11 @@ module NumRu
|
|
96
96
|
# this argument can be used to specify the dimensions that are
|
97
97
|
# not included in +grid_locs+ and are treated as independent, so
|
98
98
|
# the fitting is made for each of their component.
|
99
|
+
# * +with_offset+ (optional) if true (default), constant offset is derived.
|
100
|
+
# If false, offset is not treated.
|
101
|
+
# * +wgt_func+ (optional) [nil (defualt) or Proc] If Proc, weighting function
|
102
|
+
# to conduct the weighted least square fit. It should conform with the
|
103
|
+
# fitting functions.
|
99
104
|
#
|
100
105
|
# Note that the sum of the lengths of +grid_locs+, +ensemble_dims+ and
|
101
106
|
# +indep_dims+ must be equal to the rank (# of dims) of +data+.
|
@@ -215,7 +220,7 @@ module NumRu
|
|
215
220
|
# number of functions (also unsolvable).
|
216
221
|
#
|
217
222
|
def least_square_fit(data, grid_locs, functions, ensemble_dims=nil,
|
218
|
-
indep_dims=nil, with_offset=true)
|
223
|
+
indep_dims=nil, with_offset=true, wgt_func = nil)
|
219
224
|
|
220
225
|
#< argument check >
|
221
226
|
|
@@ -269,12 +274,20 @@ module NumRu
|
|
269
274
|
if otherdims.length != ensemble_dims.length + n_indep
|
270
275
|
raise ArgumentError, "Overlap in ensemble_dims and indep_dims"
|
271
276
|
end
|
277
|
+
sh = data.shape
|
278
|
+
indep_len = 1
|
279
|
+
indep_dims.each{|i| indep_len *= sh[i]}
|
280
|
+
meandims = (0...rank).collect{|d| d} - indep_dims
|
272
281
|
end
|
273
282
|
|
274
283
|
#< pre-process data >
|
275
284
|
|
276
|
-
|
277
|
-
|
285
|
+
if with_offset
|
286
|
+
d0 = data.mean
|
287
|
+
data = data - d0 # constant offset for numerical stability
|
288
|
+
else
|
289
|
+
d0 = 0.0
|
290
|
+
end
|
278
291
|
|
279
292
|
if data.is_a?(NArrayMiss)
|
280
293
|
mask = data.get_mask
|
@@ -291,33 +304,66 @@ module NumRu
|
|
291
304
|
otherdims.each{|d| f.newdim!(d)}
|
292
305
|
f
|
293
306
|
}
|
294
|
-
|
307
|
+
if wgt_func
|
308
|
+
wgt = wgt_func[*grid_locs]
|
309
|
+
otherdims.each{|d| wgt.newdim!(d)}
|
310
|
+
else
|
311
|
+
wgt = nil
|
312
|
+
end
|
313
|
+
|
295
314
|
ms = fv.length # matrix size
|
296
315
|
|
297
316
|
if ( (len=data.length) < ms )
|
298
317
|
raise "Insufficient data length (#{len}) for the # of funcs+1 (#{ms})"
|
299
318
|
end
|
300
319
|
|
301
|
-
|
320
|
+
if mask && indep_dims
|
321
|
+
mat = (0...indep_len).map{NMatrix.float(ms,ms)}
|
322
|
+
else
|
323
|
+
mat = NMatrix.float(ms,ms) # wil be symmetric
|
324
|
+
end
|
302
325
|
|
303
326
|
for i in 0...ms
|
304
327
|
for j in 0..i
|
305
328
|
if mask
|
306
|
-
|
307
|
-
|
329
|
+
unless wgt
|
330
|
+
fvij = NArrayMiss.to_nam( fv[i] * fv[j] * mask, mask )
|
331
|
+
else
|
332
|
+
fvij = NArrayMiss.to_nam( fv[i] * fv[j] * wgt * mask, mask )
|
333
|
+
end
|
334
|
+
if !indep_dims
|
335
|
+
mat[i,j] = (fvij).mean
|
336
|
+
else
|
337
|
+
x = (fvij).mean(*meandims)
|
338
|
+
(0...indep_len).each{|k| mat[k][i,j] = x[k]}
|
339
|
+
end
|
308
340
|
else
|
309
|
-
|
341
|
+
unless wgt
|
342
|
+
mat[i,j] = (fv[i] * fv[j]).mean
|
343
|
+
else
|
344
|
+
mat[i,j] = (fv[i] * fv[j] * wgt).mean
|
345
|
+
end
|
310
346
|
end
|
311
347
|
end
|
312
348
|
end
|
313
349
|
|
314
350
|
for i in 0...ms
|
315
351
|
for j in i+1...ms
|
316
|
-
|
352
|
+
if mask && indep_dims
|
353
|
+
for k in 0...indep_len
|
354
|
+
mat[k][i,j] = mat[k][j,i] # symmetric
|
355
|
+
end
|
356
|
+
else
|
357
|
+
mat[i,j] = mat[j,i] # symmetric
|
358
|
+
end
|
317
359
|
end
|
318
360
|
end
|
319
361
|
#p "*** mat ***",mat
|
320
|
-
|
362
|
+
if mask && indep_dims
|
363
|
+
lu = (0...indep_len).map{|k| mat[k].lu}
|
364
|
+
else
|
365
|
+
lu = mat.lu
|
366
|
+
end
|
321
367
|
|
322
368
|
#< derive the vector, solve, and best fit >
|
323
369
|
|
@@ -325,7 +371,11 @@ module NumRu
|
|
325
371
|
# derive the vector
|
326
372
|
b = NVector.float(ms)
|
327
373
|
for i in 0...ms
|
328
|
-
|
374
|
+
unless wgt
|
375
|
+
b[i] = (data * fv[i]).mean
|
376
|
+
else
|
377
|
+
b[i] = (data * fv[i] * wgt).mean
|
378
|
+
end
|
329
379
|
end
|
330
380
|
|
331
381
|
# solve
|
@@ -355,7 +405,6 @@ module NumRu
|
|
355
405
|
# derive vectors
|
356
406
|
idshp = indep_dims.collect{|d| data.shape[d]}
|
357
407
|
bs = NArray.float(ms,*idshp)
|
358
|
-
meandims = (0...rank).collect{|d| d} - indep_dims
|
359
408
|
for i in 0...ms
|
360
409
|
bsi = (data * fv[i]).mean(*meandims)
|
361
410
|
if bsi.is_a?(NArrayMiss)
|
@@ -366,16 +415,18 @@ module NumRu
|
|
366
415
|
end
|
367
416
|
bs[i,false] = bsi
|
368
417
|
end
|
369
|
-
idlen = 1
|
370
|
-
idshp.each{|l| idlen *= l}
|
371
418
|
|
372
419
|
# solve
|
373
|
-
bs = bs.reshape(ms,
|
374
|
-
c = NArray.float(ms,
|
420
|
+
bs = bs.reshape(ms, indep_len)
|
421
|
+
c = NArray.float(ms,indep_len)
|
375
422
|
b = NVector.float(ms)
|
376
|
-
for id in 0...
|
423
|
+
for id in 0...indep_len
|
377
424
|
b[true] = bs[true,id]
|
378
|
-
|
425
|
+
if mask
|
426
|
+
c[true,id] = lu[id].solve(b)
|
427
|
+
else
|
428
|
+
c[true,id] = lu.solve(b)
|
429
|
+
end
|
379
430
|
end
|
380
431
|
c[-1,true] += d0
|
381
432
|
c = c.reshape(ms, *idshp)
|
@@ -453,6 +504,11 @@ module NumRu
|
|
453
504
|
# this argument can be used to specify the dimensions that are
|
454
505
|
# not included in +grid_locs+ and are treated as independent, so
|
455
506
|
# the fitting is made for each of their component.
|
507
|
+
# * +with_offset+ (optional) if true (default), constant offset is derived.
|
508
|
+
# If false, offset is not treated.
|
509
|
+
# * +wgt_func+ (optional) [nil (defualt) or Proc] If Proc, weighting function
|
510
|
+
# to conduct the weighted least square fit. It should conform with the
|
511
|
+
# fitting functions.
|
456
512
|
#
|
457
513
|
# === RETURN VALUES
|
458
514
|
# [ c, bf, diff ]
|
@@ -473,7 +529,8 @@ module NumRu
|
|
473
529
|
|
474
530
|
|
475
531
|
|
476
|
-
def least_square_fit(functions, ensemble_dims=nil, indep_dims=nil
|
532
|
+
def least_square_fit(functions, ensemble_dims=nil, indep_dims=nil, with_offset=true,
|
533
|
+
wgt_func = nil)
|
477
534
|
|
478
535
|
#< preparation >
|
479
536
|
|
@@ -492,7 +549,8 @@ module NumRu
|
|
492
549
|
|
493
550
|
#< fitting >
|
494
551
|
c, bf, diff = GAnalysis::Fitting.least_square_fit(data, grid_locs,
|
495
|
-
|
552
|
+
functions, ensemble_dims, indep_dims,
|
553
|
+
with_offset, wgt_func)
|
496
554
|
|
497
555
|
#< make a GPhys of the best fit >
|
498
556
|
|
@@ -557,12 +615,17 @@ if $0 == __FILE__
|
|
557
615
|
data = x + x*x*0.1
|
558
616
|
c, bf, diff = GAnalysis::Fitting.least_square_fit(data, [x],
|
559
617
|
[GAnalysis::Fitting::X])
|
560
|
-
p "data:", data, "c:", c, "bf:", bf
|
561
|
-
exit
|
618
|
+
p "*1* data:", data, "c:", c, "bf:", bf, "diff:", diff
|
562
619
|
|
563
620
|
c, bf, diff = GAnalysis::Fitting.least_square_fit(data, [x],
|
564
621
|
[GAnalysis::Fitting::X,GAnalysis::Fitting::XX])
|
565
|
-
p c
|
622
|
+
p "*2* data:", data, "c:", c, "bf:", bf, "diff:", diff
|
623
|
+
|
624
|
+
wgt = Proc.new{|x| 1.0 + x**2}
|
625
|
+
c, bf, diff = GAnalysis::Fitting.least_square_fit(data, [x],
|
626
|
+
[GAnalysis::Fitting::X,GAnalysis::Fitting::XX],
|
627
|
+
nil, nil, true, wgt)
|
628
|
+
p "*3* data:", data, "x:", x, "c:", c, "bf:", bf, "diff:", diff
|
566
629
|
|
567
630
|
xx = x.newdim(-1)
|
568
631
|
data = xx + 2*yy + 100
|
@@ -43,9 +43,9 @@ module NumRu
|
|
43
43
|
val = gphys0.val
|
44
44
|
val = val.get_array![val.get_mask!] if NArrayMiss === val
|
45
45
|
val = NMath.log10(val) if log_bins
|
46
|
-
hist.increment(
|
46
|
+
val.each{|v| hist.increment(v)}
|
47
47
|
|
48
|
-
bounds =
|
48
|
+
bounds = NArray.to_na(hist.range.to_a)
|
49
49
|
bounds = 10 ** bounds if log_bins
|
50
50
|
center = (bounds[0..-2]+bounds[1..-1])/2
|
51
51
|
cell_width = (bounds[1..-1] - bounds[0..-2]) / 2
|
@@ -57,7 +57,7 @@ module NumRu
|
|
57
57
|
axis.set_cell(center, bounds, name)
|
58
58
|
axis.set_pos_to_center
|
59
59
|
|
60
|
-
bin = hist.bin.
|
60
|
+
bin = NArray.to_na(hist.bin.to_a)
|
61
61
|
bin /= cell_width if opts["log_bins"]
|
62
62
|
bin = VArray.new(bin,
|
63
63
|
{"long_name" => (log_bins ? "number per unit bin width" : "number in bins"), "units"=>"1"},
|
@@ -74,6 +74,26 @@ module NumRu
|
|
74
74
|
pi_dz_p_p_dz
|
75
75
|
end
|
76
76
|
|
77
|
+
def gph2n2(gph)
|
78
|
+
phi = gph*Met.g
|
79
|
+
phi.units = Units[ phi.units.to_s.sub(/gpm/i,'m') ]
|
80
|
+
phi_zz = pcdata_dz2(phi)
|
81
|
+
phi_z = pcdata_dz(phi)
|
82
|
+
n2 = phi_zz + phi_z * (Met::Kappa/h)
|
83
|
+
n2 = n2.convert_units(Units["s-2"])
|
84
|
+
n2.name = "N2"
|
85
|
+
n2.long_name = "squared log-p buoyancy freq"
|
86
|
+
n2
|
87
|
+
end
|
88
|
+
|
89
|
+
def gph2n(gph)
|
90
|
+
n2 = gph2n2(gph)
|
91
|
+
n = n2.sqrt
|
92
|
+
n.name = "N"
|
93
|
+
n.long_name = "log-p buoyancy freq"
|
94
|
+
n
|
95
|
+
end
|
96
|
+
|
77
97
|
end
|
78
98
|
end
|
79
99
|
|
@@ -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
|