(Tips) GAnalysis::EOF の使い方
EOF解析
- EOF解析(経験直交関数解析)を行う
- 固有値計算部分は
Ruby-SSL2,
Ruby-Lapack,
Ruby/GSL のいずれかを利用する
- ssl2, lapack, gsl の順に探し、見つかったものを使用する
- 小さな問題にはgslでもよいが、ベクトルの次元数が大きい場合は他を使うことをおすすめする
- 現在の仕様では、パフォーマンスを優先するために、共分散行列の計算時に、すべてのデータをメモリ上に読み込むため、メモリサイズを越えるデータのEOFをとると、実行に失敗するか、スワップを使用し著しく遅くなるため、注意が必要である
- 重み関数を指定することができる ("weight" option)
- 名前が "lon", "lat" で始まる軸変数を両方持っている場合は、自動的に cos(lat) の重みが適用される
- 出力されるEOFベクトルは、ベクトル長が固有値の平方根(PC の標準偏差)となっている
使用方法
eofs, rates = NumRu::GAnalysis.eof(gphys, dim0[, dim1, ..., dimN][, opts])
or
eofs, rates = gphys.eof(dim0[, dim1, ..., dimN][, opts])
引数
- gphys: GPhys object to be calculated its EOF
- dim0, ..., dimN: dimension name (String) or number (Ingeter) to calculate variance or covariance, and those dimensions are not contained in the result EOF vectors.
- opts:
a Hash object whose key is String or Symbol. The following options are available:
- nmodes: Integer, number of EOF modes to be calculate (default all EOF modes)
- weight: GPhys or NArray, weight vector gphys is multiplied by the weight vector before calculation of variance covariance matrix and the result eigen vectors are divided by the vector. If weight vector is not set, it is cosine of latitude when the first two axes of the +gphys+ are "lon" and "lat" and +disable_weight+ option is not +true+, else 1.
- disable_weight: See weight option.
戻り値
- eofs: GPhys object for array of EOF vectors.
- rates: GPhys object for array of contribution rate correspoinding to the EOF vectors.
使用例
乱数による2次元ベクトルのEOF (ソースに含まれている例)

- サンプルソース
require "numru/gphys"
require "numru/ggraph"
require "numru/ganalysis/eof"
include NumRu
N = 10000
x = NArray.float(N).randomn!*2
y = NArray.float(N).randomn!*1
ary = NArray.float(2,N)
theta = Math::PI/6
ary[0,true] = x*Math::cos(theta)-y*Math::sin(theta)
ary[1,true] = x*Math::sin(theta)+y*Math::cos(theta)
vary = VArray.new(ary, { }, "test")
axis0 = Axis.new
axis0.pos = VArray.new(NArray[0,1], {}, "dimensions")
axis1 = Axis.new
axis1.pos = VArray.new(NArray.sint(N).indgen, {}, "t")
gphys = GPhys.new(Grid.new(axis0,axis1), vary)
eof,rate = gphys.eof("t")
max = 5
DCL::gropn(4)
DCL::grfrm
DCL::grsvpt(0.1,0.9,0.1,0.9)
DCL::grswnd(-max,max,-max,max)
DCL::grstrn(1)
DCL::grstrf
DCL::sgpmzu(ary[0,true],ary[1,true],1,1,0.01)
eof1 = eof[true,0].val
eof2 = eof[true,1].val
DCL::sgplzu([eof1[0],-eof1[0]],[eof1[1],-eof1[1]], 1, 23)
DCL::sgplzu([eof2[0],-eof2[0]],[eof2[1],-eof2[1]], 1, 43)
DCL::usdaxs
DCL::uxsttl("T","test of GAnalysis::eof",0)
# TEST OF GAnalysis::eof2
vary1 = VArray.new(ary[0,true], { }, "test")
gphys1 = GPhys.new(Grid.new(axis1), vary1)
vary2 = VArray.new(ary[1,true], { }, "test")
gphys2 = GPhys.new(Grid.new(axis1), vary2)
eof,rate = GAnalysis.eof2(gphys1,gphys2)
max = 5
DCL::grfrm
DCL::grsvpt(0.1,0.9,0.1,0.9)
DCL::grswnd(-max,max,-max,max)
DCL::grstrn(1)
DCL::grstrf
DCL::sgpmzu(ary[0,true],ary[1,true],1,1,0.01)
eof1 = eof[true,0].val
eof2 = eof[true,1].val
DCL::sgplzu([eof1[0],-eof1[0]],[eof1[1],-eof1[1]], 1, 23)
DCL::sgplzu([eof2[0],-eof2[0]],[eof2[1],-eof2[1]], 1, 43)
DCL::usdaxs
DCL::uxsttl("T","test of GAnalysis::eof2",0)
DCL::grcls
冬季月平均海面更正気圧EOF第1モード

- サンプルデータ: NCEP/NCAR 月平均海面構成気圧場 (約30MB)
<URL:ftp://ftp.cdc.noaa.gov/Datasets/ncep.reanalysis.derived/surface/slp.mon.mean.nc>
- サンプルソース
require "numru/gphys"
require "numru/ganalysis"
require "numru/ggraph"
include NumRu
slp = GPhys::IO.open("slp.mon.mean.nc","slp") # ftp://ftp.cdc.noaa.gov/Datasets/ncep.reanalysis.derived/surface/slp.mon.mean.nc
slp = slp.cut("lat"=>30..90) # 北緯30度以北のデータを使用する
slp = slp[{0..-1,4},{0..-1,2},true] # 経度, 緯度について4, 2点おきに読む
# 計算時間を速めるため(∴不要)
nt = slp.shape[2]
mon = NArray.sint(12,nt/12).indgen #=> [ [0,1,..,11], [12,13,..,23], .. ]
mon = mon[[0,1,11],true] # [0,1,11]: Jan,Feb,Dec (use [0] to include only Jan)
mon = mon.reshape!(mon.total) # to 1D (list of months to use)
slp = slp[true,true,mon] # 12,1,2月のデータを切取り
vec,val = slp.eof("time","norder"=>2) # EOF第2モードまで計算する
DCL::gropn(4)
GGraph.set_fig("itr"=>32)
eof = vec[true,true,0]
if eof.cut("lat"=>90).mean > 0
eof = -eof # 北極における値がが負になるようにする
end
GGraph.tone(eof, true, "min"=>-7.0, "max"=>7.0)
GGraph.contour(eof,false, "min"=>-7.0, "max"=>7.0)
GGraph.map("coast_world"=>true)
DCL::grcls
ソース
require "numru/gphys"
module NumRu
module GAnalysis
begin
require "numru/ssl2"
@@EOF_engin = "ssl2"
rescue LoadError
begin
require "numru/lapack"
@@EOF_engin = "lapack"
rescue LoadError
begin
require "gsl"
@@EOF_engin = "gsl"
rescue LoadError
end
end
end
print "EOF engin is #{@@EOF_engin}\n" if $DEBUG
module_function
# = Calculate EOF vectors and contribution rate
# call-seq:
# NumRu::GAnalysis.eof(gphys, dim0[, dim1, ..., dimN[, opts]]) => [eof, rate]
#
# == Arguments
# +gphys+:: GPhys object to be calculated its EOF
# +dim0+, ..., +dimN+:: dimension name (String) or number (Ingeter) to calculate variance or covariance, and those dimensions are not contained in the result EOF vectors.
# +opts+:: a Hash object whose key is String or Symbol. The following options are available:
# * nmodes: Integer, number of EOF modes to be calculate (default all EOF modes)
# * weight: GPhys or NArray, weight vector
# +gphys+ is multiplied by the weight vector before calculation of variance covariance matrix
# and the result eigen vectors are divided by the vector.
# If weight vector is not set,
# it is cosine of latitude when the first two axes of the +gphys+ are "lon" and "lat" and +disable_weight+ option is not +true+,
# else 1.
# * disable_weight: See weight option.
#
# == Return values
# +eof+:: GPhys object for array of EOF vectors.
# +rate+:: GPhys object for array of contribution rate correspoinding to the EOF vectors.
def eof(gphys, *args)
unless defined?(@@EOF_engin)
raise "eigher ssl2 or gsl is required"
end
if Hash === args[-1]
dims = args[0..-2]
opts = args[-1]
else
dims = args
opts = Hash.new
end
dims = dims.map{|dim| gphys.dim_index(dim) }
n = 1
n_lost = 1
dims1 = Array.new
shape1 = Array.new
gphys.shape.each_with_index{|s,i|
if dims.include?(i)
n_lost *= s
else
n *= s
dims1.push i
shape1.push s
end
}
new_grid = gphys.instance_variable_get("@grid").delete_axes(dims, "covariance matrix").copy
new_index = NArray.sint(*new_grid.shape).indgen
index = NArray.object(gphys.rank)
index[dims] = true
if w = (opts[:weight] || opts["weight"])
if GPhys === w
w = w.val
end
unless NArray === w
raise "weight must be NArray of GPhys"
end
unless w.shape == new_grid.shape
raise "shape of weight is invalid"
end
w /= w.mean
w.reshape!(n)
else
if !(opts[:disable_weight]||opts["disable_weight"]) && /^lon/ =~ new_grid.coord(0).name && /^lat/ =~ new_grid.coord(1).name
rad = NumRu::Units.new("radian")
nlon = new_grid.coord(0).length
lat = new_grid.coord(1).convert_units(rad).val
w = NArray.new(lat.typecode,nlon).fill!(1) * NMath::cos(lat).reshape(1,lat.length)
w /= w.mean
w.reshape!(n)
else
w = nil
end
end
ary = NArrayMiss.new(gphys.typecode, n_lost, n)
ind_rank = dims1.length
ind = Array.new(ind_rank,0)
n.times{|n1|
index[dims1] = ind
val = gphys[*index].val
val.reshape!(n_lost)
val -= val.mean
ary[true,n1] = val
break if n1==n-1
ind[0] += 1
ind_rank.times{|i|
if ind[i] == shape1[i]
ind[i] = 0
ind[i+1] += 1
else
break
end
}
}
ary.mul!(w.reshape(1,n)) if w
nmodes = opts[:nmodes] || opts["nmodes"] || n
case @@EOF_engin
when "ssl2"
print "start calc covariance matrix\n" if $DEBUG
nary = NArray.new(gphys.typecode,n*(n+1)/2)
nn = 0
total_var = 0
n.times{|n0|
for n1 in n0...n
nary[nn] = ary[n0].mul_add(ary[n1],0)/(n_lost-1)
if n1==n0
total_var += nary[nn]
end
nn += 1
end
}
ary = nil # for GC
print "start calc eigen vector\n" if $DEBUG
val, vec = SSL2.seig2(nary,nmodes)
when "lapack"
print "start calc covariance matrix\n" if $DEBUG
nary = NArray.new(gphys.typecode,n,n)
total_var = 0.0
n.times{|n0|
nary[n0...n,n0] = (ary[true,n0...n].mul_add(ary[true,n0],0)/(n_lost-1)).get_array!
total_var += nary[n0,n0]
}
ary = nil # for GC
print "start calc eigen vector\n" if $DEBUG
case nary.typecode
when NArray::DFLOAT
m, val, vec, isuppz, work, iwork, info, = NumRu::Lapack.dsyevr("V", "I", "L", nary, 0, 0, n-nmodes+1, n, 0.0, -1, -1)
m, val, vec, = NumRu::Lapack.dsyevr("V", "I", "L", nary, 0, 0, n-nmodes+1, n, 0.0, work[0], iwork[0])
when NArray::SFLOAT
m, val, vec, isuppz, work, iwork, info, = NumRu::Lapack.ssyevr("V", "I", "L", nary, 0, 0, n-nmodes+1, n, 0.0, -1, -1)
m, val, vec, = NumRu::Lapack.ssyevr("V", "I", "L", nary, 0, 0, n-nmodes+1, n, 0.0, work[0], iwork[0])
end
val = val[-1..0]
vec = vec[true,-1..0]
when "gsl"
print "start calc covariance matrix\n" if $DEBUG
nary = NArray.new(gphys.typecode,n,n)
n.times{|n0|
nary[n0...n,n0] = (ary[true,n0...n].mul_add(ary[true,n0],0)/(n_lost-1)).get_array!
nary[n0,n0...n] = nary[n0...n,n0]
}
ary = nil # for GC
print "start calc eigen vector\n" if $DEBUG
val, vec = GSL::Eigen::symmv(nary.to_gm)
GSL::Eigen.symmv_sort(val, vec, GSL::Eigen::SORT_VAL_DESC)
vec = vec.to_na[0...nmodes,true].transpose(1,0)
val = val.to_na
total_var = val.sum
val = val[0...nmodes]
end
axes = new_grid.instance_variable_get('@axes')
axis_order = Axis.new
axis_order.pos = VArray.new(NArray.sint(nmodes).indgen(1),
{}, "mode")
axes << axis_order
new_grid = Grid.new(*axes)
vec /= w if w
vec.reshape!(*new_grid.shape)
vec *= NMath::sqrt( val.reshape( *([1]*(axes.length-1)+[nmodes]) ) )
va_eof = VArray.new(vec,
{"long_name"=>"EOF vector","units"=>gphys.units.to_s },
"EOF")
eof = GPhys.new(new_grid, va_eof)
va_rate = VArray.new(val.div!(total_var),
{"long_name"=>"EOF contribution rate", "units"=>"1" },
"rate")
rate = GPhys.new(Grid.new(axis_order), va_rate)
return [eof, rate]
end
def eof2(gphys1, gphys2, *args)
if Hash === args[-1]
opts = args[-1]
else
opts = Hash.new
end
raise ArgumentError, "The 1st arg must be a GPhys of rank 1: arg1 = #{gphys1.inspect}" unless gphys1.rank==1
raise ArgumentError, "The 2nd arg must be a GPhys of rank 1: arg2 = #{gphys2.inspect}" unless gphys2.rank==1
raise ArgumentError, "The 1st and 2nd args must have the same length: #{gphys1.length}!=#{gphys2.length}" unless gphys2.rank==1
nam = NArrayMiss.new(gphys1.typecode, 2, gphys1.length)
nam[0,true] = gphys1.val
nam[1,true] = gphys2.val
gphys = GPhys.new(Grid.new(Axis.new.set_pos(VArray.new(NArray[0,1],{},"var")),
gphys1.axis(0)),
VArray.new(nam,gphys1.data.attr_copy,gphys1.name))
eof(gphys, 1, opts)
end
end
class GPhys
def eof(*args)
GAnalysis.eof(self, *args)
end
end
end
#:enddoc
if $0 == __FILE__
require "numru/ggraph"
include NumRu
N = 10000
x = NArray.float(N).randomn!*2
y = NArray.float(N).randomn!*1
ary = NArray.float(2,N)
theta = Math::PI/6
ary[0,true] = x*Math::cos(theta)-y*Math::sin(theta)
ary[1,true] = x*Math::sin(theta)+y*Math::cos(theta)
vary = VArray.new(ary, { }, "test")
axis0 = Axis.new
axis0.pos = VArray.new(NArray[0,1], {}, "dimensions")
axis1 = Axis.new
axis1.pos = VArray.new(NArray.sint(N).indgen, {}, "t")
gphys = GPhys.new(Grid.new(axis0,axis1), vary)
eof,rate = gphys.eof("t")
max = 5
DCL::gropn(4)
DCL::grfrm
DCL::grsvpt(0.1,0.9,0.1,0.9)
DCL::grswnd(-max,max,-max,max)
DCL::grstrn(1)
DCL::grstrf
DCL::sgpmzu(ary[0,true],ary[1,true],1,1,0.01)
eof1 = eof[true,0].val
eof2 = eof[true,1].val
DCL::sgplzu([eof1[0],-eof1[0]],[eof1[1],-eof1[1]], 1, 23)
DCL::sgplzu([eof2[0],-eof2[0]],[eof2[1],-eof2[1]], 1, 43)
DCL::usdaxs
DCL::uxsttl("T","test of GAnalysis::eof",0)
# TEST OF GAnalysis::eof2
vary1 = VArray.new(ary[0,true], { }, "test")
gphys1 = GPhys.new(Grid.new(axis1), vary1)
vary2 = VArray.new(ary[1,true], { }, "test")
gphys2 = GPhys.new(Grid.new(axis1), vary2)
eof,rate = GAnalysis.eof2(gphys1,gphys2)
max = 5
DCL::grfrm
DCL::grsvpt(0.1,0.9,0.1,0.9)
DCL::grswnd(-max,max,-max,max)
DCL::grstrn(1)
DCL::grstrf
DCL::sgpmzu(ary[0,true],ary[1,true],1,1,0.01)
eof1 = eof[true,0].val
eof2 = eof[true,1].val
DCL::sgplzu([eof1[0],-eof1[0]],[eof1[1],-eof1[1]], 1, 23)
DCL::sgplzu([eof2[0],-eof2[0]],[eof2[1],-eof2[1]], 1, 43)
DCL::usdaxs
DCL::uxsttl("T","test of GAnalysis::eof2",0)
DCL::grcls
end
キーワード:
参照: