コンテンツにスキップ

FFT()

高速フーリエ変換

📌
実習を進める前に、以下のクエリを実行してテーブルとデータを準備してください。
CREATE TAG TABLE IF NOT EXISTS EXAMPLE (
    NAME VARCHAR(20) PRIMARY KEY,
    TIME DATETIME BASETIME,
    VALUE DOUBLE SUMMARIZED
);

サンプルデータの生成

Web UIで新しい tql エディターを開き、以下のコードをコピーして実行してください。

この例では、oscillator() が15Hz・振幅1.0と24Hz・振幅1.5を合成した波形を生成します。 また、CHART_SCATTER() には、X軸の下にスライダーを表示する dataZoom() オプション関数を指定しています。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
SCRIPT({
    const m = require('mathx');
    const data = m.oscillator({
        components: [
            { frequencyHz:15, amplitude:1.0 },
            { frequencyHz:24, amplitude:1.5 },
        ],
        timeRange: { from:"now", to:"now+10s"},
        sample:"1000Hz",
        noise: { amplitude: 0.3 },
    });
    const s = m.series(data, {xKey:"ts", yKey:"amp"})
    $.yield({
        xAxis:{ data: s.ts },
        yAxis: {},
        series: [ { type:"scatter", data: s.amp } ],
        dataZoom: { type: 'slider', start: 95, end: 100 },
    })
})
CHART( size("600px", "350px") )

生成したデータをデータベースに保存する

生成したデータを ‘signal’ というタグ名でデータベースに保存してください。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
SCRIPT({
    const m = require('mathx');
    const data = m.oscillator({
        components: [
            { frequencyHz:15, amplitude:1.0 },
            { frequencyHz:24, amplitude:1.5 },
        ],
        timeRange: { from:"now", to:"now+10s"},
        sample:"1000Hz",
        noise: { amplitude: 0.3 },
    });
    for( d of data ) {
        $.yield(d[0], d[1]);
    }
})
SQL('insert into example values(?,?,?)','signal',value(0),value(1))

実行結果ウィンドウに「10000 rows inserted.」と表示されます。

テストマシン(Apple Mac mini M1)では約270msかかりました。以下の例のように APPEND() を使用すると、約65ms(約4倍高速)に短縮できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
SCRIPT({
    const m = require('mathx');
    const data = m.oscillator({
        components: [
            { frequencyHz:15, amplitude:1.0 },
            { frequencyHz:24, amplitude:1.5 },
        ],
        timeRange: { from:"now", to:"now+10s"},
        sample:"1000Hz",
        noise: { amplitude: 0.3 },
    });
    for( d of data ) {
        $.yield('signal', d[0], d[1]);
    }
})
APPEND( table('example') )
APPEND は、入力レコードのフィールドがテーブルのカラムと順序・型まで正確に一致する場合にのみ動作します。

データベースからデータを読み取る

以下のコードは、’example’ テーブルに保存したデータを読み取ります。

SQL(`select time, value from example where name = 'signal' order by time`)
CHART(
    size("600px", "350px"), 
    chartOption({
        xAxis:{ data: column(0) },
        yAxis:{},
        series:[ {type:"line", data: column(1), showAllSymbol:true } ],
        dataZoom:{type:"slider", start:95, end: 100},
    })
)

高速フーリエ変換を実行する

SQL_SELECT() ソースと CHART_LINE() シンクの間に、データ変換関数を追加します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
SQL(`select time, value from example where name = 'signal' order by time`)
MAPKEY('sample')
GROUPBYKEY()
FFT()
CHART_LINE(
  size("600px", "350px"), 
  xAxis(0, 'Hz'),
  yAxis(1, 'Amplitude'),
  dataZoom('slider', 0, 10) 
)

動作の仕組み

SQL_SELECT()

SQL_SELECT(...) 関数は、クエリ結果を {key: rownum, value: (time, value)} 形式のレコードとして渡します。

MAPKEY(‘sample’)

MAPKEY('sample') 関数は、すべてのレコードに固定キー ‘sample’ を設定します。各レコードの key'sample' となり、value(time, value) は維持されます。{key: 'sample', value:(time, value)}

GROUPBYKEY()

GROUPBYKEY() は、同じキーを持つレコードを結合します。この例では、すべてのクエリ結果が1つのレコードにまとまり、{key: 'sample', value:[ (time1, value1), (time2, value2), ..., (timeN, valueN) ]} となります。

FFT()

FFT() は、レコードの値に高速フーリエ変換を適用し、(time, value) 配列を (frequency, amplitude) 配列に変換します。{key: 'sample', value:[ (Hz1, Ampl1), (Hz2, Ampl2), ... ]}

時間軸を追加する

次の例は、時間軸を追加して周波数変換の結果を時系列として可視化します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
SQL(`select time, value from example where name = 'signal' order by time`)

MAPKEY( roundTime(value(0), '500ms') )
GROUPBYKEY()
FFT(minHz(0), maxHz(100))
FLATTEN()
PUSHKEY('fft')
CHART_BAR3D(
      xAxis(0, 'time', 'time'),
      yAxis(1, 'Hz'),
      zAxis(2, 'Amp'),
      size('600px', '600px'), visualMap(0, 1.5), theme('westeros')
)

SQL_SELECT() で直近10秒の範囲を指定する場合は、次のように記述できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
SQL_SELECT( 'time', 'value', from('example', 'signal'), between('last-10s', 'last'))

MAPKEY( roundTime(value(0), '500ms') )
GROUPBYKEY()
FFT(minHz(0), maxHz(100))
FLATTEN()
PUSHKEY('fft')
CHART_BAR3D(
      xAxis(0, 'time', 'time'),
      yAxis(1, 'Hz'),
      zAxis(2, 'Amp'),
      size('600px', '600px'), visualMap(0, 1.5), theme('westeros')
)

時間軸を追加した場合の動作

SQL_SELECT()

SQL_SELECT(...) 関数は、クエリ結果を {key: rownum, value: (time, value)} 形式で渡します。

MAPKEY()

MAPKEY( roundTime(value(0), '500ms')) は、value(0) を500ミリ秒のバケット境界に切りそろえた結果を新しいキーに設定します。レコードは {key: (time/500ms)*500ms, value:(time, value)} 形式に変換されます。

GROUPBYKEY()

GROUPBYKEY() は、レコードを500ミリ秒単位でグループ化します。{key: time1In500ms, value:[(time1, value1), (time2, value2)...]}

FFT()

FFT() は、各レコードに高速フーリエ変換を適用します。省略可能な minHz(0)maxHz(100) は、可視化用に出力範囲を制限します。{key:time1In500ms, value:[(Hz1, Ampl1), ...]}{key:'time2In500ms', value:[(Hz1, Ampl1), ...]}、…

FLATTEN()

FLATTEN() は、値配列の次元を減らし、複数のレコードに分割します。各周波数と振幅のペアが個別のレコードとして出力されます。

PUSHKEY()

PUSHKEY('fft') は、すべてのレコードに固定キー ‘fft’ を設定し、以前のキーを値配列の先頭に移動します。{key:'fft', value:(time1In500ms, Hz1, Ampl1)}{key:'fft', value:(time1In500ms, Hz2, Ampl2)}

最終更新日