""" Discrete Fourier Transform, Number Theoretic Transform, Walsh Hadamard Transform, Mobius Transform """ from sympy.core import S, Symbol, sympify from sympy.core.function import expand_mul from sympy.core.numbers import pi, I from sympy.functions.elementary.trigonometric import sin, cos from sympy.ntheory import isprime, primitive_root from sympy.utilities.iterables import ibin, iterable from sympy.utilities.misc import as_int #----------------------------------------------------------------------------# # # # Discrete Fourier Transform # # # #----------------------------------------------------------------------------# def _fourier_transform(seq, dps, inverse=False): """Utility function for the Discrete Fourier Transform""" if not iterable(seq): raise TypeError("Expected a sequence of numeric coefficients " "for Fourier Transform") a = [sympify(arg) for arg in seq] if any(x.has(Symbol) for x in a): raise ValueError("Expected non-symbolic coefficients") n = len(a) if n < 2: return a b = n.bit_length() - 1 if n&(n - 1): # not a power of 2 b += 1 n = 2**b a += [S.Zero]*(n - len(a)) for i in range(1, n): j = int(ibin(i, b, str=True)[::-1], 2) if i < j: a[i], a[j] = a[j], a[i] ang = -2*pi/n if inverse else 2*pi/n if dps is not None: ang = ang.evalf(dps + 2) w = [cos(ang*i) + I*sin(ang*i) for i in range(n // 2)] h = 2 while h <= n: hf, ut = h // 2, n // h for i in range(0, n, h): for j in range(hf): u, v = a[i + j], expand_mul(a[i + j + hf]*w[ut * j]) a[i + j], a[i + j + hf] = u + v, u - v h *= 2 if inverse: a = [(x/n).evalf(dps) for x in a] if dps is not None \ else [x/n for x in a] return a def fft(seq, dps=None): r""" Performs the Discrete Fourier Transform (**DFT**) in the complex domain. The sequence is automatically padded to the right with zeros, as the *radix-2 FFT* requires the number of sample points to be a power of 2. This method should be used with default arguments only for short sequences as the complexity of expressions increases with the size of the sequence. Parameters ========== seq : iterable The sequence on which **DFT** is to be applied. dps : Integer Specifies the number of decimal digits for precision. Examples ======== >>> from sympy import fft, ifft >>> fft([1, 2, 3, 4]) [10, -2 - 2*I, -2, -2 + 2*I] >>> ifft(_) [1, 2, 3, 4] >>> ifft([1, 2, 3, 4]) [5/2, -1/2 + I/2, -1/2, -1/2 - I/2] >>> fft(_) [1, 2, 3, 4] >>> ifft([1, 7, 3, 4], dps=15) [3.75, -0.5 - 0.75*I, -1.75, -0.5 + 0.75*I] >>> fft(_) [1.0, 7.0, 3.0, 4.0] References ========== .. [1] https://en.wikipedia.org/wiki/Cooley%E2%80%93Tukey_FFT_algorithm .. [2] https://mathworld.wolfram.com/FastFourierTransform.html """ return _fourier_transform(seq, dps=dps) def ifft(seq, dps=None): return _fourier_transform(seq, dps=dps, inverse=True) ifft.__doc__ = fft.__doc__ #----------------------------------------------------------------------------# # # # Number Theoretic Transform # # # #----------------------------------------------------------------------------# def _number_theoretic_transform(seq, prime, inverse=False): """Utility function for the Number Theoretic Transform""" if not iterable(seq): raise TypeError("Expected a sequence of integer coefficients " "for Number Theoretic Transform") p = as_int(prime) if not isprime(p): raise ValueError("Expected prime modulus for " "Number Theoretic Transform") a = [as_int(x) % p for x in seq] n = len(a) if n < 1: return a b = n.bit_length() - 1 if n&(n - 1): b += 1 n = 2**b if (p - 1) % n: raise ValueError("Expected prime modulus of the form (m*2**k + 1)") a += [0]*(n - len(a)) for i in range(1, n): j = int(ibin(i, b, str=True)[::-1], 2) if i < j: a[i], a[j] = a[j], a[i] pr = primitive_root(p) rt = pow(pr, (p - 1) // n, p) if inverse: rt = pow(rt, p - 2, p) w = [1]*(n // 2) for i in range(1, n // 2): w[i] = w[i - 1]*rt % p h = 2 while h <= n: hf, ut = h // 2, n // h for i in range(0, n, h): for j in range(hf): u, v = a[i + j], a[i + j + hf]*w[ut * j] a[i + j], a[i + j + hf] = (u + v) % p, (u - v) % p h *= 2 if inverse: rv = pow(n, p - 2, p) a = [x*rv % p for x in a] return a def ntt(seq, prime): r""" Performs the Number Theoretic Transform (**NTT**), which specializes the Discrete Fourier Transform (**DFT**) over quotient ring `Z/pZ` for prime `p` instead of complex numbers `C`. The sequence is automatically padded to the right with zeros, as the *radix-2 NTT* requires the number of sample points to be a power of 2. Parameters ========== seq : iterable The sequence on which **DFT** is to be applied. prime : Integer Prime modulus of the form `(m 2^k + 1)` to be used for performing **NTT** on the sequence. Examples ======== >>> from sympy import ntt, intt >>> ntt([1, 2, 3, 4], prime=3*2**8 + 1) [10, 643, 767, 122] >>> intt(_, 3*2**8 + 1) [1, 2, 3, 4] >>> intt([1, 2, 3, 4], prime=3*2**8 + 1) [387, 415, 384, 353] >>> ntt(_, prime=3*2**8 + 1) [1, 2, 3, 4] References ========== .. [1] http://www.apfloat.org/ntt.html .. [2] https://mathworld.wolfram.com/NumberTheoreticTransform.html .. [3] https://en.wikipedia.org/wiki/Discrete_Fourier_transform_(general%29 """ return _number_theoretic_transform(seq, prime=prime) def intt(seq, prime): return _number_theoretic_transform(seq, prime=prime, inverse=True) intt.__doc__ = ntt.__doc__ #----------------------------------------------------------------------------# # # # Walsh Hadamard Transform # # # #----------------------------------------------------------------------------# def _walsh_hadamard_transform(seq, inverse=False): """Utility function for the Walsh Hadamard Transform""" if not iterable(seq): raise TypeError("Expected a sequence of coefficients " "for Walsh Hadamard Transform") a = [sympify(arg) for arg in seq] n = len(a) if n < 2: return a if n&(n - 1): n = 2**n.bit_length() a += [S.Zero]*(n - len(a)) h = 2 while h <= n: hf = h // 2 for i in range(0, n, h): for j in range(hf): u, v = a[i + j], a[i + j + hf] a[i + j], a[i + j + hf] = u + v, u - v h *= 2 if inverse: a = [x/n for x in a] return a def fwht(seq): r""" Performs the Walsh Hadamard Transform (**WHT**), and uses Hadamard ordering for the sequence. The sequence is automatically padded to the right with zeros, as the *radix-2 FWHT* requires the number of sample points to be a power of 2. Parameters ========== seq : iterable The sequence on which WHT is to be applied. Examples ======== >>> from sympy import fwht, ifwht >>> fwht([4, 2, 2, 0, 0, 2, -2, 0]) [8, 0, 8, 0, 8, 8, 0, 0] >>> ifwht(_) [4, 2, 2, 0, 0, 2, -2, 0] >>> ifwht([19, -1, 11, -9, -7, 13, -15, 5]) [2, 0, 4, 0, 3, 10, 0, 0] >>> fwht(_) [19, -1, 11, -9, -7, 13, -15, 5] References ========== .. [1] https://en.wikipedia.org/wiki/Hadamard_transform .. [2] https://en.wikipedia.org/wiki/Fast_Walsh%E2%80%93Hadamard_transform """ return _walsh_hadamard_transform(seq) def ifwht(seq): return _walsh_hadamard_transform(seq, inverse=True) ifwht.__doc__ = fwht.__doc__ #----------------------------------------------------------------------------# # # # Mobius Transform for Subset Lattice # # # #----------------------------------------------------------------------------# def _mobius_transform(seq, sgn, subset): r"""Utility function for performing Mobius Transform using Yate's Dynamic Programming method""" if not iterable(seq): raise TypeError("Expected a sequence of coefficients") a = [sympify(arg) for arg in seq] n = len(a) if n < 2: return a if n&(n - 1): n = 2**n.bit_length() a += [S.Zero]*(n - len(a)) if subset: i = 1 while i < n: for j in range(n): if j & i: a[j] += sgn*a[j ^ i] i *= 2 else: i = 1 while i < n: for j in range(n): if j & i: continue a[j] += sgn*a[j ^ i] i *= 2 return a def mobius_transform(seq, subset=True): r""" Performs the Mobius Transform for subset lattice with indices of sequence as bitmasks. The indices of each argument, considered as bit strings, correspond to subsets of a finite set. The sequence is automatically padded to the right with zeros, as the definition of subset/superset based on bitmasks (indices) requires the size of sequence to be a power of 2. Parameters ========== seq : iterable The sequence on which Mobius Transform is to be applied. subset : bool Specifies if Mobius Transform is applied by enumerating subsets or supersets of the given set. Examples ======== >>> from sympy import symbols >>> from sympy import mobius_transform, inverse_mobius_transform >>> x, y, z = symbols('x y z') >>> mobius_transform([x, y, z]) [x, x + y, x + z, x + y + z] >>> inverse_mobius_transform(_) [x, y, z, 0] >>> mobius_transform([x, y, z], subset=False) [x + y + z, y, z, 0] >>> inverse_mobius_transform(_, subset=False) [x, y, z, 0] >>> mobius_transform([1, 2, 3, 4]) [1, 3, 4, 10] >>> inverse_mobius_transform(_) [1, 2, 3, 4] >>> mobius_transform([1, 2, 3, 4], subset=False) [10, 6, 7, 4] >>> inverse_mobius_transform(_, subset=False) [1, 2, 3, 4] References ========== .. [1] https://en.wikipedia.org/wiki/M%C3%B6bius_inversion_formula .. [2] https://people.csail.mit.edu/rrw/presentations/subset-conv.pdf .. [3] https://arxiv.org/pdf/1211.0189.pdf """ return _mobius_transform(seq, sgn=+1, subset=subset) def inverse_mobius_transform(seq, subset=True): return _mobius_transform(seq, sgn=-1, subset=subset) inverse_mobius_transform.__doc__ = mobius_transform.__doc__