123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488 |
- """
- Convolution (using **FFT**, **NTT**, **FWHT**), Subset Convolution,
- Covering Product, Intersecting Product
- """
- from sympy.core import S, sympify
- from sympy.core.function import expand_mul
- from sympy.discrete.transforms import (
- fft, ifft, ntt, intt, fwht, ifwht,
- mobius_transform, inverse_mobius_transform)
- from sympy.utilities.iterables import iterable
- from sympy.utilities.misc import as_int
- def convolution(a, b, cycle=0, dps=None, prime=None, dyadic=None, subset=None):
- """
- Performs convolution by determining the type of desired
- convolution using hints.
- Exactly one of ``dps``, ``prime``, ``dyadic``, ``subset`` arguments
- should be specified explicitly for identifying the type of convolution,
- and the argument ``cycle`` can be specified optionally.
- For the default arguments, linear convolution is performed using **FFT**.
- Parameters
- ==========
- a, b : iterables
- The sequences for which convolution is performed.
- cycle : Integer
- Specifies the length for doing cyclic convolution.
- dps : Integer
- Specifies the number of decimal digits for precision for
- performing **FFT** on the sequence.
- prime : Integer
- Prime modulus of the form `(m 2^k + 1)` to be used for
- performing **NTT** on the sequence.
- dyadic : bool
- Identifies the convolution type as dyadic (*bitwise-XOR*)
- convolution, which is performed using **FWHT**.
- subset : bool
- Identifies the convolution type as subset convolution.
- Examples
- ========
- >>> from sympy import convolution, symbols, S, I
- >>> u, v, w, x, y, z = symbols('u v w x y z')
- >>> convolution([1 + 2*I, 4 + 3*I], [S(5)/4, 6], dps=3)
- [1.25 + 2.5*I, 11.0 + 15.8*I, 24.0 + 18.0*I]
- >>> convolution([1, 2, 3], [4, 5, 6], cycle=3)
- [31, 31, 28]
- >>> convolution([111, 777], [888, 444], prime=19*2**10 + 1)
- [1283, 19351, 14219]
- >>> convolution([111, 777], [888, 444], prime=19*2**10 + 1, cycle=2)
- [15502, 19351]
- >>> convolution([u, v], [x, y, z], dyadic=True)
- [u*x + v*y, u*y + v*x, u*z, v*z]
- >>> convolution([u, v], [x, y, z], dyadic=True, cycle=2)
- [u*x + u*z + v*y, u*y + v*x + v*z]
- >>> convolution([u, v, w], [x, y, z], subset=True)
- [u*x, u*y + v*x, u*z + w*x, v*z + w*y]
- >>> convolution([u, v, w], [x, y, z], subset=True, cycle=3)
- [u*x + v*z + w*y, u*y + v*x, u*z + w*x]
- """
- c = as_int(cycle)
- if c < 0:
- raise ValueError("The length for cyclic convolution "
- "must be non-negative")
- dyadic = True if dyadic else None
- subset = True if subset else None
- if sum(x is not None for x in (prime, dps, dyadic, subset)) > 1:
- raise TypeError("Ambiguity in determining the type of convolution")
- if prime is not None:
- ls = convolution_ntt(a, b, prime=prime)
- return ls if not c else [sum(ls[i::c]) % prime for i in range(c)]
- if dyadic:
- ls = convolution_fwht(a, b)
- elif subset:
- ls = convolution_subset(a, b)
- else:
- ls = convolution_fft(a, b, dps=dps)
- return ls if not c else [sum(ls[i::c]) for i in range(c)]
- #----------------------------------------------------------------------------#
- # #
- # Convolution for Complex domain #
- # #
- #----------------------------------------------------------------------------#
- def convolution_fft(a, b, dps=None):
- """
- Performs linear convolution using Fast Fourier Transform.
- Parameters
- ==========
- a, b : iterables
- The sequences for which convolution is performed.
- dps : Integer
- Specifies the number of decimal digits for precision.
- Examples
- ========
- >>> from sympy import S, I
- >>> from sympy.discrete.convolutions import convolution_fft
- >>> convolution_fft([2, 3], [4, 5])
- [8, 22, 15]
- >>> convolution_fft([2, 5], [6, 7, 3])
- [12, 44, 41, 15]
- >>> convolution_fft([1 + 2*I, 4 + 3*I], [S(5)/4, 6])
- [5/4 + 5*I/2, 11 + 63*I/4, 24 + 18*I]
- References
- ==========
- .. [1] https://en.wikipedia.org/wiki/Convolution_theorem
- .. [2] https://en.wikipedia.org/wiki/Discrete_Fourier_transform_(general%29
- """
- a, b = a[:], b[:]
- n = m = len(a) + len(b) - 1 # convolution size
- if n > 0 and n&(n - 1): # not a power of 2
- n = 2**n.bit_length()
- # padding with zeros
- a += [S.Zero]*(n - len(a))
- b += [S.Zero]*(n - len(b))
- a, b = fft(a, dps), fft(b, dps)
- a = [expand_mul(x*y) for x, y in zip(a, b)]
- a = ifft(a, dps)[:m]
- return a
- #----------------------------------------------------------------------------#
- # #
- # Convolution for GF(p) #
- # #
- #----------------------------------------------------------------------------#
- def convolution_ntt(a, b, prime):
- """
- Performs linear convolution using Number Theoretic Transform.
- Parameters
- ==========
- a, b : iterables
- The sequences for which convolution is performed.
- prime : Integer
- Prime modulus of the form `(m 2^k + 1)` to be used for performing
- **NTT** on the sequence.
- Examples
- ========
- >>> from sympy.discrete.convolutions import convolution_ntt
- >>> convolution_ntt([2, 3], [4, 5], prime=19*2**10 + 1)
- [8, 22, 15]
- >>> convolution_ntt([2, 5], [6, 7, 3], prime=19*2**10 + 1)
- [12, 44, 41, 15]
- >>> convolution_ntt([333, 555], [222, 666], prime=19*2**10 + 1)
- [15555, 14219, 19404]
- References
- ==========
- .. [1] https://en.wikipedia.org/wiki/Convolution_theorem
- .. [2] https://en.wikipedia.org/wiki/Discrete_Fourier_transform_(general%29
- """
- a, b, p = a[:], b[:], as_int(prime)
- n = m = len(a) + len(b) - 1 # convolution size
- if n > 0 and n&(n - 1): # not a power of 2
- n = 2**n.bit_length()
- # padding with zeros
- a += [0]*(n - len(a))
- b += [0]*(n - len(b))
- a, b = ntt(a, p), ntt(b, p)
- a = [x*y % p for x, y in zip(a, b)]
- a = intt(a, p)[:m]
- return a
- #----------------------------------------------------------------------------#
- # #
- # Convolution for 2**n-group #
- # #
- #----------------------------------------------------------------------------#
- def convolution_fwht(a, b):
- """
- Performs dyadic (*bitwise-XOR*) convolution using Fast Walsh Hadamard
- Transform.
- The convolution 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
- ==========
- a, b : iterables
- The sequences for which convolution is performed.
- Examples
- ========
- >>> from sympy import symbols, S, I
- >>> from sympy.discrete.convolutions import convolution_fwht
- >>> u, v, x, y = symbols('u v x y')
- >>> convolution_fwht([u, v], [x, y])
- [u*x + v*y, u*y + v*x]
- >>> convolution_fwht([2, 3], [4, 5])
- [23, 22]
- >>> convolution_fwht([2, 5 + 4*I, 7], [6*I, 7, 3 + 4*I])
- [56 + 68*I, -10 + 30*I, 6 + 50*I, 48 + 32*I]
- >>> convolution_fwht([S(33)/7, S(55)/6, S(7)/4], [S(2)/3, 5])
- [2057/42, 1870/63, 7/6, 35/4]
- References
- ==========
- .. [1] https://www.radioeng.cz/fulltexts/2002/02_03_40_42.pdf
- .. [2] https://en.wikipedia.org/wiki/Hadamard_transform
- """
- if not a or not b:
- return []
- a, b = a[:], b[:]
- n = max(len(a), len(b))
- if n&(n - 1): # not a power of 2
- n = 2**n.bit_length()
- # padding with zeros
- a += [S.Zero]*(n - len(a))
- b += [S.Zero]*(n - len(b))
- a, b = fwht(a), fwht(b)
- a = [expand_mul(x*y) for x, y in zip(a, b)]
- a = ifwht(a)
- return a
- #----------------------------------------------------------------------------#
- # #
- # Subset Convolution #
- # #
- #----------------------------------------------------------------------------#
- def convolution_subset(a, b):
- """
- Performs Subset Convolution of given sequences.
- 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 based on bitmasks (indices) requires the size of
- sequence to be a power of 2.
- Parameters
- ==========
- a, b : iterables
- The sequences for which convolution is performed.
- Examples
- ========
- >>> from sympy import symbols, S
- >>> from sympy.discrete.convolutions import convolution_subset
- >>> u, v, x, y, z = symbols('u v x y z')
- >>> convolution_subset([u, v], [x, y])
- [u*x, u*y + v*x]
- >>> convolution_subset([u, v, x], [y, z])
- [u*y, u*z + v*y, x*y, x*z]
- >>> convolution_subset([1, S(2)/3], [3, 4])
- [3, 6]
- >>> convolution_subset([1, 3, S(5)/7], [7])
- [7, 21, 5, 0]
- References
- ==========
- .. [1] https://people.csail.mit.edu/rrw/presentations/subset-conv.pdf
- """
- if not a or not b:
- return []
- if not iterable(a) or not iterable(b):
- raise TypeError("Expected a sequence of coefficients for convolution")
- a = [sympify(arg) for arg in a]
- b = [sympify(arg) for arg in b]
- n = max(len(a), len(b))
- if n&(n - 1): # not a power of 2
- n = 2**n.bit_length()
- # padding with zeros
- a += [S.Zero]*(n - len(a))
- b += [S.Zero]*(n - len(b))
- c = [S.Zero]*n
- for mask in range(n):
- smask = mask
- while smask > 0:
- c[mask] += expand_mul(a[smask] * b[mask^smask])
- smask = (smask - 1)&mask
- c[mask] += expand_mul(a[smask] * b[mask^smask])
- return c
- #----------------------------------------------------------------------------#
- # #
- # Covering Product #
- # #
- #----------------------------------------------------------------------------#
- def covering_product(a, b):
- """
- Returns the covering product of given sequences.
- The indices of each argument, considered as bit strings, correspond to
- subsets of a finite set.
- The covering product of given sequences is a sequence which contains
- the sum of products of the elements of the given sequences grouped by
- the *bitwise-OR* of the corresponding indices.
- The sequence is automatically padded to the right with zeros, as the
- definition of subset based on bitmasks (indices) requires the size of
- sequence to be a power of 2.
- Parameters
- ==========
- a, b : iterables
- The sequences for which covering product is to be obtained.
- Examples
- ========
- >>> from sympy import symbols, S, I, covering_product
- >>> u, v, x, y, z = symbols('u v x y z')
- >>> covering_product([u, v], [x, y])
- [u*x, u*y + v*x + v*y]
- >>> covering_product([u, v, x], [y, z])
- [u*y, u*z + v*y + v*z, x*y, x*z]
- >>> covering_product([1, S(2)/3], [3, 4 + 5*I])
- [3, 26/3 + 25*I/3]
- >>> covering_product([1, 3, S(5)/7], [7, 8])
- [7, 53, 5, 40/7]
- References
- ==========
- .. [1] https://people.csail.mit.edu/rrw/presentations/subset-conv.pdf
- """
- if not a or not b:
- return []
- a, b = a[:], b[:]
- n = max(len(a), len(b))
- if n&(n - 1): # not a power of 2
- n = 2**n.bit_length()
- # padding with zeros
- a += [S.Zero]*(n - len(a))
- b += [S.Zero]*(n - len(b))
- a, b = mobius_transform(a), mobius_transform(b)
- a = [expand_mul(x*y) for x, y in zip(a, b)]
- a = inverse_mobius_transform(a)
- return a
- #----------------------------------------------------------------------------#
- # #
- # Intersecting Product #
- # #
- #----------------------------------------------------------------------------#
- def intersecting_product(a, b):
- """
- Returns the intersecting product of given sequences.
- The indices of each argument, considered as bit strings, correspond to
- subsets of a finite set.
- The intersecting product of given sequences is the sequence which
- contains the sum of products of the elements of the given sequences
- grouped by the *bitwise-AND* of the corresponding indices.
- The sequence is automatically padded to the right with zeros, as the
- definition of subset based on bitmasks (indices) requires the size of
- sequence to be a power of 2.
- Parameters
- ==========
- a, b : iterables
- The sequences for which intersecting product is to be obtained.
- Examples
- ========
- >>> from sympy import symbols, S, I, intersecting_product
- >>> u, v, x, y, z = symbols('u v x y z')
- >>> intersecting_product([u, v], [x, y])
- [u*x + u*y + v*x, v*y]
- >>> intersecting_product([u, v, x], [y, z])
- [u*y + u*z + v*y + x*y + x*z, v*z, 0, 0]
- >>> intersecting_product([1, S(2)/3], [3, 4 + 5*I])
- [9 + 5*I, 8/3 + 10*I/3]
- >>> intersecting_product([1, 3, S(5)/7], [7, 8])
- [327/7, 24, 0, 0]
- References
- ==========
- .. [1] https://people.csail.mit.edu/rrw/presentations/subset-conv.pdf
- """
- if not a or not b:
- return []
- a, b = a[:], b[:]
- n = max(len(a), len(b))
- if n&(n - 1): # not a power of 2
- n = 2**n.bit_length()
- # padding with zeros
- a += [S.Zero]*(n - len(a))
- b += [S.Zero]*(n - len(b))
- a, b = mobius_transform(a, subset=False), mobius_transform(b, subset=False)
- a = [expand_mul(x*y) for x, y in zip(a, b)]
- a = inverse_mobius_transform(a, subset=False)
- return a
|