はじめに
国土交通省 気象庁で震度算出方法が公開されている。
https://www.data.jma.go.jp/svd/eqev/data/kyoshin/kaisetsu/calc_sindo.htm
この計算をMATLABで再現するというのが今回の目的となる。
MATLAB、Scilab、Python関係の数値計算関連の話はこちら。
https://www.simulationroom999.com/blog/comparison-of-matlab-python-scilab/
発端
同僚からの相談。
- 現状は震度計算をMATLABでやっているが、他の言語(C/C++等)でやって欲しいと言われている。
- MATLABのコード(mスクリプト)は貰えていない。
- よって、「それっぽいmスクリプト書いてくれ!!」とは言われていないが、なんとなく察した。
mスクリプトがあれば、MATLAB coderで一発でC/C++のオートコード生成ができるが、無いとなると自作するしかない。
いろいろ調べると、算出方法の概要は分かったので、とりあえずmスクリプトを書いてMATLAB coderでオートコード生成を試みる。
結果
まず、オートコード生成はできると言えばできる。
しかし、FFT(高速フーリエ変換)関数に於いて、入力要素数固定じゃないとオートコード生成が利かないことが分かった。
さすがに、要素数固定では使い物にならない。
しかし、一応mスクリプトによるアルゴリズムの再現は出来たので、普通にC/C++でゴリゴリ書ける水準までは来たと言える。
それでOKとしよう。
場合によっては、オートコードをベースに手修正しても良い。
MATLAB coderによるFFTでは、静的配列変数として三角関数テーブルとビット反転テーブルを宣言する方式と取っている。
この部分をmalloc的な動的配列にして、各テーブルも演算前に要素数に合わせて作ってしまえばOK。
FFTでオートコード生成する場合は、要素数を2のべき乗にする必要あり。
以下、公式ページで紹介されているデータと比較しながらの再現方法解説。
MATLABによる震度算出方法
ブロック線図
小数第3位を四捨五入し、小数第2位を切り捨てしたものを震度とする。
mスクリプト
まずは書き出したmスクリプト。
至る所にglobal変数を配置しているが、途中計算確認用に使用しているのみ。
global定義を全部削除しても動作する。
function y = SeismicIntensityBody(ns,ew,ud,fs)
global fft_ns;
global fft_ew;
global fft_ud;
% FFTで周波数領域に
fft_ns = fft(ns);\n
fft_ew = fft(ew);\n
fft_ud = fft(ud);\n
global num;
num = size( ns, 2 ); % データ数(列数)
n = 0:( num - 1 ); % プロット軸元ネタ
%f = n / (num/fs);
% 0割回避&疑似0値のため微小値を加算
f = n / (num / fs) + 0.00000000000000000001; %関数窓用プロット軸
% 乗除は行列乗除("*" , "/")ではなく要素単位の乗除(".*" , "./")
% 周期効果関数窓
global winX;
winX = sqrt(1 ./ f);
% ハイカット関数窓
Y = f ./ 10;
global winY;
winY = 1 ./ sqrt(ones(1,num) + 0.694*Y.^2 + 0.241*Y.^4 + 0.0557*Y.^6 + 0.009664*Y.^8 + 0.00134*Y.^10 + 0.000155*Y.^12);
% ローカット関数窓
global winZ;
winZ = sqrt(ones(1,num) - exp(-(f./0.5).^3));
% 関数窓合成
win1 = winX .* winY .* winZ; % 前半分用
win2 = fliplr(winX) .* fliplr(winY) .* fliplr(winZ); % 後半用
nn = floor(num/2);
global win;
win = [win1(1:nn),win1(nn+1),win2(nn+2:num)];
% バンドパスフィルタ
global fft_ns_;
global fft_ew_;
global fft_ud_;
fft_ns_ = win.*fft_ns;
fft_ew_ = win.*fft_ew;
fft_ud_ = win.*fft_ud;
% 逆フーリエ変換
global res_ns;
global res_ew;
global res_ud;
res_ns = ifft(fft_ns_);
res_ew = ifft(fft_ew_);
res_ud = ifft(fft_ud_);
% 合成加速度
global anorm;
anorm = abs(sqrt(res_ns.^2 + res_ew.^2 + res_ud.^2));
% 降順ソート
sorted_anorm = sort(anorm,'descend');
% 0.3秒後のデータサンプル確保(開始位置を0秒とする。)
S = sorted_anorm(floor(0.3*fs)+1);
% 気象庁指定の演算で震度算出
I = 2 * log10(S) + 0.94;
y = floor(10*(I+0.005)) / 10; % 小数第3位を四捨五入し、小数第2位を切り捨て
※全コードはGitHubで公開
気象庁データの取得
気象庁で公開されているデータはcsv形式で、先頭7行がヘッダで、8行目以降からが実データとなる。
よって、dlmread関数を使用して行列として抜き出すことができる。
data = dlmread([ファイル名],',',7,0);
1列目がNS(NorthSouth:南北)加速度、2列目がEW(EastWest:東西)加速度、3列目がUD(UpDown:上下)加速度となる。
よって、データをスライシングする。
ns = data(:,1)'; % NSデータ スライシング
ew = data(:,2)'; % EWデータ スライシング
ud = data(:,3)'; % UDデータ スライシング
これを上記関数に渡す。
y = SeismicIntensityBody(ns,ew,ud,100);
次のページへ
次のページから実際の利用方法と何となくの正しさの証明。
コメント