Bỏ qua

Biến đổi Chirp Z (CZT)

Tương tự biến đổi Fourier rời rạc, biến đổi Chirp Z là thuật toán cho đa thức \(f(x) = \sum_{i = 0}^{m - 1} f_i x^i \in \mathbb{C}\lbrack x\rbrack\)\(q \in \mathbb{C} \setminus \{0\}\), tính \(f(1), f(q), \dots, f(q^{n - 1})\) mà không yêu cầu \(q\) là căn đơn vị. Cũng có thể dùng cho biến đổi số học. Phần sau sẽ giới thiệu Chirp Z và nghịch biến đổi.

Biến đổi Chirp Z

Theo định nghĩa, Chirp Z có thể viết:

\[ \operatorname{\mathsf{CZT}}_n : \left(f(x), q\right) \mapsto \begin{bmatrix} f(1) & f(q) & \cdots & f\left(q^{n - 1}\right) \end{bmatrix} \]

trong đó \(f(x) := \sum_{i = 0}^{m - 1} f_i x^i \in \mathbb{C}\lbrack x\rbrack\)\(q \in \mathbb{C} \setminus \{0\}\).

Thuật toán Bluestein

Xét

\[ ij = \binom{i}{2} + \binom{-j}{2} - \binom{i - j}{2} \]

với \(i, j \in \mathbb{Z}\), ta có thể xây dựng

\[ \begin{aligned} G(x) & := \sum_{i = -(m - 1)}^{n - 1} q^{-\binom{i}{2}}x^i, \\ F(x) & := \sum_{i = 0}^{m - 1} f_i q^{\binom{-i}{2}}x^i. \end{aligned} \]

Trong đó \(G(x) \in \mathbb{C}\left\lbrack x, x^{-1}\right\rbrack\), và với \(i = 0, \dots, n - 1\) ta có

\[ \begin{aligned} \left\lbrack x^i\right\rbrack\left(G(x)F(x)\right) &= \sum_{j = 0}^{m - 1}\left(\left(\left\lbrack x^{i - j}\right\rbrack G(x)\right)\left(\left\lbrack x^j\right\rbrack F(x)\right)\right) \\ &= \sum_{j = 0}^{m - 1} f_j q^{\binom{-j}{2} - \binom{i - j}{2}} \\ &= q^{-\binom{i}{2}} f\left(q^i\right) \end{aligned} \]

\(q^{\binom{i + 1}{2}} = q^{\binom{i}{2}}\cdot q^i\), \(\binom{-i}{2} = \binom{i + 1}{2}\). Như vậy chỉ cần một phép nhân đa thức, thuật toán này gọi là Bluestein.

Mẫu ( P6800【Mẫu】Chirp Z-Transform )
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
std::vector<uint> CZT(const std::vector<uint>& f, uint q, int n) {
  if (f.empty() || n == 0) return std::vector<uint>(n);
  const int m = f.size();
  if (q == 0) {
    std::vector<uint> res(n, f[0]);
    for (int i = 1; i < m; ++i)
      if ((res[0] += f[i]) >= MOD) res[0] -= MOD;
    return res;
  }
  // H[i] = q^(binom(i + 1, 2))
  std::vector<uint> H(std::max(m, n - 1));
  H[0] = 1;     // H[0] = q^0
  uint qi = 1;  // qi = q^i
  for (int i = 1; i < (int)H.size(); ++i) {
    qi = (ull)qi * q % MOD;
    // H[i] = q^(binom(i, 2)) * q^i
    H[i] = (ull)H[i - 1] * qi % MOD;
  }
  // F[i] = f[i] * q^(binom(i + 1, 2))
  std::vector<uint> F(m);
  for (int i = 0; i < m; ++i) F[i] = (ull)f[i] * H[i] % MOD;
  std::vector<uint> G_p(m + n - 1);
  // G[i] = q^(-binom(i, 2)), -(m - 1) ≤ i < n
  uint* const G = G_p.data() + (m - 1);
  const uint iq = InvMod(q);
  // G[-(m - 1)] = q^(-binom(-(m - 1), 2)),
  // binom(-(m - 1), 2) = (-(m - 1)) * (-(m - 1) - 1) / 2
  //                    = (m - 1) * m / 2
  G[-(m - 1)] = PowMod(iq, (ull)(m - 1) * m / 2);
  uint qmi = PowMod(q, m - 1);  // q^(-i), -(m - 1) ≤ i < n
  for (int i = -(m - 1) + 1; i < n; ++i) {
    // q^(-binom(i, 2)) = q^(-binom(i - 1, 2)) * q^(-(i - 1))
    G[i] = (ull)G[i - 1] * qmi % MOD;
    // q^(-i) = q^(-(i - 1)) * q^(-1)
    qmi = (ull)qmi * iq % MOD;
  }
  // res[i] = q^(-binom(i, 2)) * f(q^i), 0 ≤ i < n
  std::vector<uint> res = MiddleProduct(G_p, F);
  for (int i = 1; i < n; ++i) res[i] = (ull)res[i] * H[i - 1] % MOD;
  return res;
}

Nghịch biến đổi Chirp Z

Nghịch Chirp Z có thể viết:

\[ \operatorname{\mathsf{ICZT}}_n : \left( \begin{bmatrix} f(1) & f(q) & \cdots & f\left(q^{n - 1}\right) \end{bmatrix},q \right) \mapsto f(x) \]

trong đó \(f(x) \in \mathbb{C}\left\lbrack x\right\rbrack_{< n}\)\(q \in \mathbb{C} \setminus \{0\}\), đồng thời \(q^i \neq q^j\) với mọi \(i \neq j\); đây là điều kiện nội suy đa thức.

Thuật toán Bostan–Schost

Nhắc lại công thức nội suy Lagrange:

\[ f(x) = \sum_{i = 0}^{n - 1}\left(f\left(x_i\right)\prod_{0 \leq j < n \atop j \neq i} \frac{x - x_j}{x_i - x_j}\right) \]

\(x_i \neq x_j\) với mọi \(i \neq j\). Giống như trong nội suy đa thức nhanh, đặt \(M(x) := \prod_{i = 0}^{n - 1}\left(x - x_i\right)\), theo quy tắc L’Hôpital ta có

\[ M'(x_i) = \lim_{x \to x_i} \frac{M(x)}{x - x_i} = \prod_{0 \leq j < n \atop j \neq i}\left(x_i - x_j\right) \]

Công thức Lagrange sửa đổi

\[ f(x) = M(x)\left(\sum_{i = 0}^{n - 1}\frac{f\left(x_i\right)/M'(x_i)}{x - x_i}\right) \]

Do đó

\[ f(x) = M(x)\left(\sum_{i = 0}^{n - 1}\frac{f\left(q^i\right)/M'\left(q^i\right)}{x - q^i}\right) \]

trong đó \(M(x)=\prod_{j = 0}^{n - 1}\left(x - q^j\right)\). Nếu giả sử \(n\) chẵn, đặt \(n = 2k\)\(H(x) := \prod_{j = 0}^{k - 1}\left(x - q^j\right)\), thì

\[ M(x) = H(x) \cdot q^{k^2} \cdot H\left(\frac{x}{q^k}\right) \]

Nhờ đó ta có thể tính nhanh \(M(x)\). Sau đó dùng Bluestein để tính \(M'(1), \dots, M'(q^{n - 1})\). Đặt \(c_i := f\left(q^i\right)/M'\left(q^i\right)\), ta có

\[ f(x) = M(x)\left(\sum_{i = 0}^{n - 1}\frac{c_i}{x - q^i}\right) \]

\(\deg f(x) < n\), ta chỉ cần tính \(\sum_{i = 0}^{n - 1}\frac{c_i}{x - q^i}\bmod{x^n}\), với \(\frac{c_i}{x - q^i} \in \mathbb{C}\left\lbrack\left\lbrack x\right\rbrack\right\rbrack\), tức là

\[ \begin{aligned} \sum_{i = 0}^{n - 1}\frac{c_i}{x - q^i} \bmod x^n &= -\sum_{i = 0}^{n - 1}\left(\sum_{j = 0}^{n - 1} c_i q^{-i(j+1)}x^j\right) \\ &= -\sum_{j = 0}^{n - 1} C\left(q^{-j - 1}\right) x^j \end{aligned} \]

trong đó \(C(x) = \sum_{i = 0}^{n - 1} c_i x^i\). Ta có thể dùng Bluestein để tính \(C\left(q^{-1}\right), \dots, C\left(q^{-n}\right)\).

Tóm lại, ta thực hiện:

  1. Dùng chia để trị (decrease and conquer) tính \(M(x)\);
  2. Dùng Bluestein để tính \(M'(1), \dots, M'(q^{n - 1})\);
  3. Dùng Bluestein để tính \(C\left(q^{-1}\right), \dots, C\left(q^{-n}\right)\);
  4. Dùng một phép nhân đa thức để tính \(f(x)\).

Mỗi bước đều có độ phức tạp bằng thời gian nhân hai đa thức bậc không vượt quá \(n\).

Mẫu triển khai
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
std::vector<uint> InvCZT(const std::vector<uint>& f, uint q) {
  if (f.empty()) return {};
  const int n = f.size();
  if (q == 0) {
    // f[0] = f(1), f[1] = f(0)
    assert(n <= 2);
    if (n == 1) return {f[0]};                 // deg(f) < 1
    return {f[1], (f[0] + MOD - f[1]) % MOD};  // deg(f) < 2
  }
  // prod[0 ≤ i < n] (x - q^i)
  const auto DaC = [q](auto&& DaC, int n) -> std::vector<uint> {
    if (n == 1) return {MOD - 1, 1u};
    // H = prod[0 ≤ i < ⌊n/2⌋] (x - q^i)
    const std::vector<uint> H = DaC(DaC, n / 2);
    // HH = H(x / q^(⌊n/2⌋)) * q^(⌊n/2⌋^2)
    std::vector<uint> HH = H;
    const uint iqn = InvMod(PowMod(q, n / 2));
    uint qq = PowMod(q, (ull)(n / 2) * (n / 2));
    for (int i = 0; i <= n / 2; ++i)
      HH[i] = (ull)HH[i] * qq % MOD, qq = (ull)qq * iqn % MOD;
    std::vector<uint> res = Product(H, HH);
    if (n & 1) {
      res.resize(n + 1);
      const uint qnm1 = MOD - PowMod(q, n - 1);
      for (int i = n; i > 0; --i) {
        if ((res[i] += res[i - 1]) >= MOD) res[i] -= MOD;
        res[i - 1] = (ull)res[i - 1] * qnm1 % MOD;
      }
    }
    return res;
  };
  const std::vector<uint> M = DaC(DaC, n);
  // C[i] = (M'(q^i))^(-1)
  std::vector<uint> C = InvModBatch(CZT(Deriv(M), q, n));
  // C[i] = f(q^i) / M'(q^i)
  for (int i = 0; i < n; ++i) C[i] = (ull)f[i] * C[i] % MOD;
  // C(x) ← C(q^(-1)x)
  const uint iq = InvMod(q);
  uint qmi = 1;
  for (int i = 0; i < n; ++i)
    C[i] = (ull)C[i] * qmi % MOD, qmi = (ull)qmi * iq % MOD;
  C = CZT(C, iq, n);
  for (int i = 0; i < n; ++i)
    if (C[i] != 0) C[i] = MOD - C[i];
  std::vector<uint> res = Product(M, C);
  res.resize(n);
  return res;
}

Tài liệu tham khảo

  1. Bostan, A. (2010). Fast algorithms for polynomials and matrices. JNCF 2010. Algorithms Project, INRIA.