Skip to content

FFT()

고속 푸리에 변환

📌
원활한 실습을 위해 아래 쿼리를 실행하여 테이블과 데이터를 미리 준비해 주십시오.
CREATE TAG TABLE IF NOT EXISTS EXAMPLE (
    NAME VARCHAR(20) PRIMARY KEY,
    TIME DATETIME BASETIME,
    VALUE DOUBLE SUMMARIZED
);

샘플 데이터 생성

웹 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()는 동일한 키를 가진 레코드를 병합합니다. 이 예시에서는 모든 쿼리 결과가 하나의 레코드로 합쳐져 {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()

SQL_SELECT(...) 함수는 쿼리 결과를 {key: time, value: (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)}

최근 업데이트