UNPKG

ts-scikit

Version:

A scientific toolkit written in Typescript

392 lines 15.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.FftReal = void 0; const utils_1 = require("../utils"); const fft_pfa_1 = require("./fft-pfa"); /** * A fast Fourier transform of real-valued arrays. * <p> * The FFT length nfft equals the number of <em>real</em> numbers * transformed. The transform of nfft real numbers yields nfft/2+1 complex * numbers. (The imaginary parts of the first and last complex numbers * are always zero.) For real-to-complex and complex-to-real transforms, nfft * is always an even number. * <p> * Complex numbers are packed into arrays of numbers as [real_0, imag_0, * real_1, imag_1, ... ]. Here, real_k and imag_k correspond to the real and * imaginary parts of the complex number with index k, respectively. * <p> * When input and output arrays are the same array, transforms are performed * in-place. For example, an input array rx[nfft] of nfft real numbers may be * the same as an output array cy[nfft+2] of nfft/2+1 complex numbers. By * "the same array", we mean that rx === cy. In this case, both rx.length and * cy.length equal nfft+2. When we write rx[nfft] (here and below), we imply * that only the first nfft numbers in the input array rx are accessed. * <p> * Transforms may be performed for any dimension of a multi-dimensional * array. For example, we may transform the 1st dimension of an input array * rx[n2][nfft] of n2*nfft real numbers to an output array cy[n2][nfft+2] of * n2*(nfft/2+1) complex numbers. Or, we may transform the 2nd dimension of * an input array rx[nfft][n1] of nfft*n1 real numbers to an output array * cy[nfft/2+1][2*n1] of (nfft/2+1)*n1 complex numbers. In either case, the * input array rx and the output array cy may be the same array, such that * the transform may be performed in-place. * <p> * In-place transforms are typically used to reduce memory consumption. * Note, however, that memory consumption is reduced for only dimension-1 * in-place transformed. Dimension-2 (and higher) in-place transforms save * no memory, because of the contiguous packing of real and imaginary parts * of complex number in multi-dimensional array of numbers. (See above.) * Therefore, dimension-1 transforms are best when performing real-to-complex * of complex-to-real transforms of multi-dimensional arrays. * */ class FftReal { /** * Constructs a new FFT with the specified length. * <p> * Valid FFT lengths an be obtained by calling the methods * {@link SmallNFFT} and {@link FastNFFT}. * @param nfft the FFT length, which must be valid. */ constructor(nfft) { utils_1.Check.argument(nfft % 2 === 0 && fft_pfa_1.FftPfa.IsValidNFFT(Math.floor(nfft / 2)), 'nfft = ' + nfft + ' is valid FFT length'); this._nfft = nfft; } /** * Returns an FFT length optimized for memory. * <p> * The FFT length will be the smalled valid length that is not less than * the specified length n. * @param n the lower bound on FFT length. * @returns the FFT length. */ static SmallNFFT(n) { utils_1.Check.argument(n <= 1441440, 'n does not exceed 1441440'); return 2 * fft_pfa_1.FftPfa.SmallNFFT(Math.floor((n + 1) / 2)); } /** * Returns an FFT length optimized for speed. * <p> * The FFT length will be the fastest valid length that is not less than * the specified length n. * @param n the lower bound on FFT length. * @returns the FFT length. */ static FastNFFT(n) { utils_1.Check.argument(n <= 1441440, 'n does not exceed 1441440'); return 2 * Math.floor(fft_pfa_1.FftPfa.FastNFFT(Math.floor(n + 1) / 2)); } static _checkSign(sign) { utils_1.Check.argument(sign === 1 || sign === -1, 'sign equals 1 or -1'); } static _checkArray(a, name, n1, n2, n3) { let ok; switch (utils_1.arrayDimensions(a)) { case 1: a = a; utils_1.Check.argument(a.length >= n1, `dimensions of ${name} are valid`); break; case 2: a = a; ok = a.length >= n2; for (let i2 = 0; i2 < n2 && ok; ++i2) { ok = a[i2].length >= n1; } utils_1.Check.argument(ok, `dimensions of ${name} are valid`); break; default: a = a; ok = a.length >= n3; for (let i3 = 0; i3 < n3 && ok; ++i3) { ok = a[i3].length >= n2; for (let i2 = 0; i2 < n2 && ok; ++i2) { ok = a[i3][i2].length >= n1; } } utils_1.Check.argument(ok, `dimensions of ${name} are valid`); } } /** * The FFT length. */ get nfft() { return this._nfft; } /** * Computes a real-to-complex fast Fourier transform. * <p> * Transforms a 1-D input array rx[nfft] of nfft real numbers to * a 1-D output array cy[nfft+2] of nfft/2 + 1 complex numbers. * @param sign the sign (1 or -1) of the exponent used in the FFT. * @param rx the input array. * @param cy the output array. */ realToComplex(sign, rx, cy) { FftReal._checkSign(sign); FftReal._checkArray(rx, 'rx', this._nfft); FftReal._checkArray(cy, 'cy', this._nfft + 2); const nfft = this._nfft; let n = nfft; while (--n >= 0) { cy[n] = 0.5 * rx[n]; } fft_pfa_1.FftPfa.Transform(sign, Math.floor(nfft / 2), cy); cy[nfft] = 2.0 * (cy[0] - cy[1]); cy[0] = 2.0 * (cy[0] + cy[1]); cy[nfft + 1] = 0.0; cy[1] = 0.0; const theta = sign * 2.0 * Math.PI / nfft; let wt = Math.sin(0.5 * theta); const wpr = -2.0 * wt * wt; const wpi = Math.sin(theta); let wr = 1.0 + wpr; let wi = wpi; let sumr, sumi, difr, difi, tmpr, tmpi; for (let j = 2, k = nfft - 2; j <= k; j += 2, k -= 2) { sumr = cy[j] + cy[k]; sumi = cy[j + 1] + cy[k + 1]; difr = cy[j] - cy[k]; difi = cy[j + 1] - cy[k + 1]; tmpr = wi * difr + wr * sumi; tmpi = wi * sumi - wr * difr; cy[j] = sumr + tmpr; cy[j + 1] = tmpi + difi; cy[k] = sumr - tmpr; cy[k + 1] = tmpi - difi; wt = wr; wr += wr * wpr - wi * wpi; wi += wi * wpr + wt * wpi; } } /** * Computes a complex-to-real fast Fourier transform. * <p> * Transforms a 1-D input array cx[nfft+2] of nfft/2+1 complex numbers * to a 1-D output array cy[nfft] of nfft real numbers. * @param sign the sign (1 or -1) of the exponent used in the FFT. * @param cx the input array. * @param ry the output array. */ complexToReal(sign, cx, ry) { FftReal._checkSign(sign); FftReal._checkArray(cx, 'cx', this._nfft + 2); FftReal._checkArray(ry, 'ry', this._nfft); const nfft = this._nfft; if (cx !== ry) { let n = nfft; while (--n >= 2) { ry[n] = cx[n]; } } ry[1] = cx[0] - cx[nfft]; ry[0] = cx[0] + cx[nfft]; const theta = -sign * 2.0 * Math.PI / nfft; let wt = Math.sin(0.5 * theta); const wpr = -2.0 * wt * wt; // = cos(theta) - 1, with less rounding error const wpi = Math.sin(theta); // = sin(theta) let wr = 1.0 + wpr; let wi = wpi; let sumr, sumi, difr, difi, tmpr, tmpi; for (let j = 2, k = nfft - 2; j <= k; j += 2, k -= 2) { sumr = ry[j] + ry[k]; sumi = ry[j + 1] + ry[k + 1]; difr = ry[j] - ry[k]; difi = ry[j + 1] - ry[k + 1]; tmpr = wi * difr - wr * sumi; tmpi = wi * sumi + wr * difr; ry[j] = sumr + tmpr; ry[j + 1] = tmpi + difi; ry[k] = sumr - tmpr; ry[k + 1] = tmpi - difi; wt = wr; wr += wr * wpr - wi * wpi; wi += wi * wpr + wt * wpi; } fft_pfa_1.FftPfa.Transform(sign, Math.floor(nfft / 2), ry); } realToComplex1(sign, rx, cy, n2, n3) { FftReal._checkSign(sign); if (rx[0] instanceof Array) { if (rx[0][0] instanceof Array) { rx = rx; cy = cy; FftReal._checkArray(rx, 'rx', this._nfft, n2, n3); FftReal._checkArray(cy, 'cy', this._nfft + 2, n2, n3); for (let i3 = 0; i3 < n3; ++i3) { this.realToComplex1(sign, rx[i3], cy[i3], n2); } } else { rx = rx; cy = cy; FftReal._checkArray(rx, 'rx', this._nfft, n2); FftReal._checkArray(cy, 'cy', this._nfft + 2, n2); for (let i2 = 0; i2 < n2; ++i2) { this.realToComplex(sign, rx[i2], cy[i2]); } } } } complexToReal1(sign, cx, ry, n2, n3) { FftReal._checkSign(sign); if (cx[0][0] instanceof Array) { cx = cx; ry = ry; FftReal._checkArray(cx, 'cx', this._nfft + 2, n2, n3); FftReal._checkArray(ry, 'ry', this._nfft, n2, n3); for (let i3 = 0; i3 < n3; ++i3) { this.complexToReal1(sign, cx[i3], ry[i3], n2); } } else if (cx[0] instanceof Array) { cx = cx; ry = ry; FftReal._checkArray(cx, 'cx', this._nfft + 2, n2); FftReal._checkArray(ry, 'ry', this._nfft, n2); for (let i2 = 0; i2 < n2; ++i2) { this.complexToReal(sign, cx[i2], ry[i2]); } } } /** * Computes a real-to-complex dimension-2 fast Fourier transform. * <p> * Transform a 2-D input array rx[nfft][n1] of nfft*n1 real numbers to a * 2-D output array cy[nfft/2+1][2*n1] of (nfft/2+1)*n1 complex number. * @param sign the sign (1 or -1) of the exponent used in the FFT. * @param n1 the 1st dimension of arrays. * @param rx the input array. * @param cy the output array. */ realToComplex2(sign, n1, rx, cy) { FftReal._checkSign(sign); FftReal._checkArray(rx, 'rx', this._nfft, n1); FftReal._checkArray(cy, 'cy', 2 * n1, this._nfft / 2 + 1); // Pack real input rx into complex output cy. This is complicated // so that it works when input and output arrays are the same. for (let i1 = n1 - 1, j1 = i1 * 2; i1 >= 0; --i1, j1 -= 2) { for (let i2 = this._nfft - 2, j2 = i2 / 2; i2 >= 0; i2 -= 2, --j2) { cy[j2][j1] = 0.5 * rx[i2][i1]; cy[j2][j1 + 1] = 0.5 * rx[i2 + 1][i1]; } } // Dimension-2 complex-to-complex transform. fft_pfa_1.FftPfa.Transform2a(sign, n1, this._nfft / 2, cy); // Finish transform. const cy0 = cy[0]; const cyn = cy[this._nfft / 2]; for (let i1 = 2 * n1 - 2; i1 >= 0; i1 -= 2) { cyn[i1] = 2.0 * (cy0[i1] - cy0[i1 + 1]); cy0[i1] = 2.0 * (cy0[i1] + cy0[i1 + 1]); cyn[i1 + 1] = 0.0; cy0[i1 + 1] = 0.0; } const theta = sign * 2.0 * Math.PI / this._nfft; let wt = Math.sin(0.5 * theta); const wpr = -2.0 * wt * wt; // = cos(theta) - 1, with less rounding error const wpi = Math.sin(theta); // = sin(theta) let wr = 1.0 + wpr; let wi = wpi; let sumr, sumi, difr, difi, tmpr, tmpi; for (let j2 = 1, k2 = this._nfft / 2 - 1; j2 <= k2; ++j2, --k2) { const cyj2 = cy[j2]; const cyk2 = cy[k2]; for (let i1 = 0, j1 = 0; i1 < n1; ++i1, j1 += 2) { sumr = cyj2[j1] + cyk2[j1]; sumi = cyj2[j1 + 1] + cyk2[j1 + 1]; difr = cyj2[j1] - cyk2[j1]; difi = cyj2[j1 + 1] - cyk2[j1 + 1]; tmpr = wi * difr + wr * sumi; tmpi = wi * sumi - wr * difr; cyj2[j1] = sumr + tmpr; cyj2[j1 + 1] = tmpi + difi; cyk2[j1] = sumr - tmpr; cyk2[j1 + 1] = tmpi - difi; } wt = wr; wr += wr * wpr - wi * wpi; wi += wi * wpr + wt * wpi; } } /** * Computes a complex-to-real dimension-2 fast Fourier transform. * <p> * Transforms a 2-D input array cx[nfft/2+1][2*n1] of (nfft/2+1)*n1 complex * numbers to a 2-D output array ry[nfft][n1] of nfft*n1 real numbers. * @param sign the sign (1 or -1) of the exponent used in the FFT. * @param n1 the 1st dimension of arrays. * @param cx the input array. * @param ry the output array. */ complexToReal2(sign, n1, cx, ry) { FftReal._checkSign(sign); FftReal._checkArray(cx, 'cx', 2 * n1, this._nfft / 2 + 1); FftReal._checkArray(ry, 'ry', n1, this._nfft); // Unpack complex input cx into real output ry. This is complicated // so that it works when input and output arrays are the same. for (let i1 = 0, j1 = 0; j1 < n1; i1 += 2, ++j1) { const cx0 = cx[0][i1]; const cxn = cx[this._nfft / 2][i1]; for (let i2 = this._nfft / 2 - 1, j2 = 2 * i2; i2 > 0; --i2, j2 -= 2) { ry[j2][j1] = cx[i2][i1]; ry[j2 + 1][j1] = cx[i2][i1 + 1]; } ry[1][j1] = cx0 - cxn; ry[0][j1] = cx0 + cxn; } // Begin transform. const theta = -sign * 2.0 * Math.PI / this._nfft; let wt = Math.sin(0.5 * theta); const wpr = -2.0 * wt * wt; // = cos(theta)-1, with less rounding error const wpi = Math.sin(theta); // = sin(theta) let wr = 1.0 + wpr; let wi = wpi; for (let j2 = 2, k2 = this._nfft - 2; j2 <= k2; j2 += 2, k2 -= 2) { const ryj2r = ry[j2]; const ryj2i = ry[j2 + 1]; const ryk2r = ry[k2]; const ryk2i = ry[k2 + 1]; for (let i1 = 0; i1 < n1; ++i1) { const sumr = ryj2r[i1] + ryk2r[i1]; const sumi = ryj2i[i1] + ryk2i[i1]; const difr = ryj2r[i1] - ryk2r[i1]; const difi = ryj2i[i1] - ryk2i[i1]; const tmpr = wi * difr - wr * sumi; const tmpi = wi * sumi + wr * difr; ryj2r[i1] = sumr + tmpr; ryj2i[i1] = tmpi + difi; ryk2r[i1] = sumr - tmpr; ryk2i[i1] = tmpi - difi; } wt = wr; wr += wr * wpr - wi * wpi; wi += wi * wpr + wt * wpi; } // Dimension-2 complex-to-complex transform. fft_pfa_1.FftPfa.Transform2b(sign, n1, this._nfft / 2, ry); } scale(rx, n1, n2, n3) { const dim = utils_1.arrayDimensions(rx); switch (dim) { case 1: rx = rx; const s = 1.0 / this._nfft; while (--n1 >= 0) { rx[n1] *= s; } break; case 2: rx = rx; for (let i2 = 0; i2 < n2; ++i2) { this.scale(rx[i2], n1); } break; default: rx = rx; for (let i3 = 0; i3 < n3; ++i3) { this.scale(rx[i3], n1, n2); } } } } exports.FftReal = FftReal; //# sourceMappingURL=fft-real.js.map