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.
@@ -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
- dims = dims.map{|dim| gphys.dim_index(dim) }
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
- new_grid = gphys.instance_variable_get("@grid").delete_axes(dims, "covariance matrix").copy
79
- new_index = NArray.sint(*new_grid.shape).indgen
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 == new_grid.shape
95
+ unless w.shape == new_grid_wo_modes.shape
91
96
  raise "shape of weight is invalid"
92
97
  end
93
- w /= w.mean
94
- w.reshape!(n)
95
- elsif new_grid.rank >= 2
96
- nc0 = new_grid.coord(0)
97
- nc1 = new_grid.coord(1)
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
- w = NArray.new(lat.typecode,nlon).fill!(1) * NMath::cos(lat).reshape(1,lat.length)
105
- w /= w.mean
106
- w.reshape!(n)
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
- ary = NArrayMiss.new(gphys.typecode, n_lost, n)
115
- ind_rank = dims1.length
116
- ind = Array.new(ind_rank,0)
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 = new_grid.instance_variable_get('@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 << axis_order
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
- return [eof, rate]
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
- d0 = data.mean
277
- data = data - d0 # constant offset for numerical stability
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
- mat = NMatrix.float(ms,ms) # wil be symmetric
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
- fvij = NArrayMiss.to_nam( fv[i] * fv[j] * mask, mask )
307
- mat[i,j] = (fvij).mean
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
- mat[i,j] = (fv[i] * fv[j]).mean
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
- mat[i,j] = mat[j,i] # symmetric
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
- lu = mat.lu
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
- b[i] = (data * fv[i]).mean
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, idlen)
374
- c = NArray.float(ms,idlen)
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...idlen
423
+ for id in 0...indep_len
377
424
  b[true] = bs[true,id]
378
- c[true,id] = lu.solve(b)
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
- functions, ensemble_dims, indep_dims)
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(val)
46
+ val.each{|v| hist.increment(v)}
47
47
 
48
- bounds = hist.range.to_na
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.to_na
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