I am done

This commit is contained in:
2024-10-30 22:14:35 +01:00
parent 720dc28c09
commit 40e2a747cf
36901 changed files with 5011519 additions and 0 deletions

View File

@ -0,0 +1,20 @@
"""This module contains functions which operate on discrete sequences.
Transforms - ``fft``, ``ifft``, ``ntt``, ``intt``, ``fwht``, ``ifwht``,
``mobius_transform``, ``inverse_mobius_transform``
Convolutions - ``convolution``, ``convolution_fft``, ``convolution_ntt``,
``convolution_fwht``, ``convolution_subset``,
``covering_product``, ``intersecting_product``
"""
from .transforms import (fft, ifft, ntt, intt, fwht, ifwht,
mobius_transform, inverse_mobius_transform)
from .convolutions import convolution, covering_product, intersecting_product
__all__ = [
'fft', 'ifft', 'ntt', 'intt', 'fwht', 'ifwht', 'mobius_transform',
'inverse_mobius_transform',
'convolution', 'covering_product', 'intersecting_product',
]

View File

@ -0,0 +1,597 @@
"""
Convolution (using **FFT**, **NTT**, **FWHT**), Subset Convolution,
Covering Product, Intersecting Product
"""
from sympy.core import S, sympify, Rational
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.external.gmpy import MPZ, lcm
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:
def loop(a):
dens = []
for i in a:
if isinstance(i, Rational) and i.q - 1:
dens.append(i.q)
elif not isinstance(i, int):
return
if dens:
l = lcm(*dens)
return [i*l if type(i) is int else i.p*(l//i.q) for i in a], l
# no lcm of den to deal with
return a, 1
ls = None
da = loop(a)
if da is not None:
db = loop(b)
if db is not None:
(ia, ma), (ib, mb) = da, db
den = ma*mb
ls = convolution_int(ia, ib)
if den != 1:
ls = [Rational(i, den) for i in ls]
if ls is None:
ls = convolution_fft(a, b, 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
#----------------------------------------------------------------------------#
# #
# Integer Convolutions #
# #
#----------------------------------------------------------------------------#
def convolution_int(a, b):
"""Return the convolution of two sequences as a list.
The iterables must consist solely of integers.
Parameters
==========
a, b : Sequence
The sequences for which convolution is performed.
Explanation
===========
This function performs the convolution of ``a`` and ``b`` by packing
each into a single integer, multiplying them together, and then
unpacking the result from the product. The intuition behind this is
that if we evaluate some polynomial [1]:
.. math ::
1156x^6 + 3808x^5 + 8440x^4 + 14856x^3 + 16164x^2 + 14040x + 8100
at say $x = 10^5$ we obtain $1156038080844014856161641404008100$.
Note we can read of the coefficients for each term every five digits.
If the $x$ we chose to evaluate at is large enough, the same will hold
for the product.
The idea now is since big integer multiplication in libraries such
as GMP is highly optimised, this will be reasonably fast.
Examples
========
>>> from sympy.discrete.convolutions import convolution_int
>>> convolution_int([2, 3], [4, 5])
[8, 22, 15]
>>> convolution_int([1, 1, -1], [1, 1])
[1, 2, 0, -1]
References
==========
.. [1] Fateman, Richard J.
Can you save time in multiplying polynomials by encoding them as integers?
University of California, Berkeley, California (2004).
https://people.eecs.berkeley.edu/~fateman/papers/polysbyGMP.pdf
"""
# An upper bound on the largest coefficient in p(x)q(x) is given by (1 + min(dp, dq))N(p)N(q)
# where dp = deg(p), dq = deg(q), N(f) denotes the coefficient of largest modulus in f [1]
B = max(abs(c) for c in a)*max(abs(c) for c in b)*(1 + min(len(a) - 1, len(b) - 1))
x, power = MPZ(1), 0
while x <= (2*B): # multiply by two for negative coefficients, see [1]
x <<= 1
power += 1
def to_integer(poly):
n, mul = MPZ(0), 0
for c in reversed(poly):
if c and not mul: mul = -1 if c < 0 else 1
n <<= power
n += mul*int(c)
return mul, n
# Perform packing and multiplication
(a_mul, a_packed), (b_mul, b_packed) = to_integer(a), to_integer(b)
result = a_packed * b_packed
# Perform unpacking
mul = a_mul * b_mul
mask, half, borrow, poly = x - 1, x >> 1, 0, []
while result or borrow:
coeff = (result & mask) + borrow
result >>= power
borrow = coeff >= half
poly.append(mul * int(coeff if coeff < half else coeff - x))
return poly or [0]

View File

@ -0,0 +1,166 @@
"""
Recurrences
"""
from sympy.core import S, sympify
from sympy.utilities.iterables import iterable
from sympy.utilities.misc import as_int
def linrec(coeffs, init, n):
r"""
Evaluation of univariate linear recurrences of homogeneous type
having coefficients independent of the recurrence variable.
Parameters
==========
coeffs : iterable
Coefficients of the recurrence
init : iterable
Initial values of the recurrence
n : Integer
Point of evaluation for the recurrence
Notes
=====
Let `y(n)` be the recurrence of given type, ``c`` be the sequence
of coefficients, ``b`` be the sequence of initial/base values of the
recurrence and ``k`` (equal to ``len(c)``) be the order of recurrence.
Then,
.. math :: y(n) = \begin{cases} b_n & 0 \le n < k \\
c_0 y(n-1) + c_1 y(n-2) + \cdots + c_{k-1} y(n-k) & n \ge k
\end{cases}
Let `x_0, x_1, \ldots, x_n` be a sequence and consider the transformation
that maps each polynomial `f(x)` to `T(f(x))` where each power `x^i` is
replaced by the corresponding value `x_i`. The sequence is then a solution
of the recurrence if and only if `T(x^i p(x)) = 0` for each `i \ge 0` where
`p(x) = x^k - c_0 x^(k-1) - \cdots - c_{k-1}` is the characteristic
polynomial.
Then `T(f(x)p(x)) = 0` for each polynomial `f(x)` (as it is a linear
combination of powers `x^i`). Now, if `x^n` is congruent to
`g(x) = a_0 x^0 + a_1 x^1 + \cdots + a_{k-1} x^{k-1}` modulo `p(x)`, then
`T(x^n) = x_n` is equal to
`T(g(x)) = a_0 x_0 + a_1 x_1 + \cdots + a_{k-1} x_{k-1}`.
Computation of `x^n`,
given `x^k = c_0 x^{k-1} + c_1 x^{k-2} + \cdots + c_{k-1}`
is performed using exponentiation by squaring (refer to [1_]) with
an additional reduction step performed to retain only first `k` powers
of `x` in the representation of `x^n`.
Examples
========
>>> from sympy.discrete.recurrences import linrec
>>> from sympy.abc import x, y, z
>>> linrec(coeffs=[1, 1], init=[0, 1], n=10)
55
>>> linrec(coeffs=[1, 1], init=[x, y], n=10)
34*x + 55*y
>>> linrec(coeffs=[x, y], init=[0, 1], n=5)
x**2*y + x*(x**3 + 2*x*y) + y**2
>>> linrec(coeffs=[1, 2, 3, 0, 0, 4], init=[x, y, z], n=16)
13576*x + 5676*y + 2356*z
References
==========
.. [1] https://en.wikipedia.org/wiki/Exponentiation_by_squaring
.. [2] https://en.wikipedia.org/w/index.php?title=Modular_exponentiation&section=6#Matrices
See Also
========
sympy.polys.agca.extensions.ExtensionElement.__pow__
"""
if not coeffs:
return S.Zero
if not iterable(coeffs):
raise TypeError("Expected a sequence of coefficients for"
" the recurrence")
if not iterable(init):
raise TypeError("Expected a sequence of values for the initialization"
" of the recurrence")
n = as_int(n)
if n < 0:
raise ValueError("Point of evaluation of recurrence must be a "
"non-negative integer")
c = [sympify(arg) for arg in coeffs]
b = [sympify(arg) for arg in init]
k = len(c)
if len(b) > k:
raise TypeError("Count of initial values should not exceed the "
"order of the recurrence")
else:
b += [S.Zero]*(k - len(b)) # remaining initial values default to zero
if n < k:
return b[n]
terms = [u*v for u, v in zip(linrec_coeffs(c, n), b)]
return sum(terms[:-1], terms[-1])
def linrec_coeffs(c, n):
r"""
Compute the coefficients of n'th term in linear recursion
sequence defined by c.
`x^k = c_0 x^{k-1} + c_1 x^{k-2} + \cdots + c_{k-1}`.
It computes the coefficients by using binary exponentiation.
This function is used by `linrec` and `_eval_pow_by_cayley`.
Parameters
==========
c = coefficients of the divisor polynomial
n = exponent of x, so dividend is x^n
"""
k = len(c)
def _square_and_reduce(u, offset):
# squares `(u_0 + u_1 x + u_2 x^2 + \cdots + u_{k-1} x^k)` (and
# multiplies by `x` if offset is 1) and reduces the above result of
# length upto `2k` to `k` using the characteristic equation of the
# recurrence given by, `x^k = c_0 x^{k-1} + c_1 x^{k-2} + \cdots + c_{k-1}`
w = [S.Zero]*(2*len(u) - 1 + offset)
for i, p in enumerate(u):
for j, q in enumerate(u):
w[offset + i + j] += p*q
for j in range(len(w) - 1, k - 1, -1):
for i in range(k):
w[j - i - 1] += w[j]*c[i]
return w[:k]
def _final_coeffs(n):
# computes the final coefficient list - `cf` corresponding to the
# point at which recurrence is to be evalauted - `n`, such that,
# `y(n) = cf_0 y(k-1) + cf_1 y(k-2) + \cdots + cf_{k-1} y(0)`
if n < k:
return [S.Zero]*n + [S.One] + [S.Zero]*(k - n - 1)
else:
return _square_and_reduce(_final_coeffs(n // 2), n % 2)
return _final_coeffs(n)

View File

@ -0,0 +1,392 @@
from sympy.core.numbers import (E, Rational, pi)
from sympy.functions.elementary.exponential import exp
from sympy.functions.elementary.miscellaneous import sqrt
from sympy.core import S, symbols, I
from sympy.discrete.convolutions import (
convolution, convolution_fft, convolution_ntt, convolution_fwht,
convolution_subset, covering_product, intersecting_product,
convolution_int)
from sympy.testing.pytest import raises
from sympy.abc import x, y
def test_convolution():
# fft
a = [1, Rational(5, 3), sqrt(3), Rational(7, 5)]
b = [9, 5, 5, 4, 3, 2]
c = [3, 5, 3, 7, 8]
d = [1422, 6572, 3213, 5552]
e = [-1, Rational(5, 3), Rational(7, 5)]
assert convolution(a, b) == convolution_fft(a, b)
assert convolution(a, b, dps=9) == convolution_fft(a, b, dps=9)
assert convolution(a, d, dps=7) == convolution_fft(d, a, dps=7)
assert convolution(a, d[1:], dps=3) == convolution_fft(d[1:], a, dps=3)
# prime moduli of the form (m*2**k + 1), sequence length
# should be a divisor of 2**k
p = 7*17*2**23 + 1
q = 19*2**10 + 1
# ntt
assert convolution(d, b, prime=q) == convolution_ntt(b, d, prime=q)
assert convolution(c, b, prime=p) == convolution_ntt(b, c, prime=p)
assert convolution(d, c, prime=p) == convolution_ntt(c, d, prime=p)
raises(TypeError, lambda: convolution(b, d, dps=5, prime=q))
raises(TypeError, lambda: convolution(b, d, dps=6, prime=q))
# fwht
assert convolution(a, b, dyadic=True) == convolution_fwht(a, b)
assert convolution(a, b, dyadic=False) == convolution(a, b)
raises(TypeError, lambda: convolution(b, d, dps=2, dyadic=True))
raises(TypeError, lambda: convolution(b, d, prime=p, dyadic=True))
raises(TypeError, lambda: convolution(a, b, dps=2, dyadic=True))
raises(TypeError, lambda: convolution(b, c, prime=p, dyadic=True))
# subset
assert convolution(a, b, subset=True) == convolution_subset(a, b) == \
convolution(a, b, subset=True, dyadic=False) == \
convolution(a, b, subset=True)
assert convolution(a, b, subset=False) == convolution(a, b)
raises(TypeError, lambda: convolution(a, b, subset=True, dyadic=True))
raises(TypeError, lambda: convolution(c, d, subset=True, dps=6))
raises(TypeError, lambda: convolution(a, c, subset=True, prime=q))
# integer
assert convolution([0], [0]) == convolution_int([0], [0])
assert convolution(b, c) == convolution_int(b, c)
# rational
assert convolution([Rational(1,2)], [Rational(1,2)]) == [Rational(1, 4)]
assert convolution(b, e) == [-9, 10, Rational(239, 15), Rational(34, 3),
Rational(32, 3), Rational(43, 5), Rational(113, 15),
Rational(14, 5)]
def test_cyclic_convolution():
# fft
a = [1, Rational(5, 3), sqrt(3), Rational(7, 5)]
b = [9, 5, 5, 4, 3, 2]
assert convolution([1, 2, 3], [4, 5, 6], cycle=0) == \
convolution([1, 2, 3], [4, 5, 6], cycle=5) == \
convolution([1, 2, 3], [4, 5, 6])
assert convolution([1, 2, 3], [4, 5, 6], cycle=3) == [31, 31, 28]
a = [Rational(1, 3), Rational(7, 3), Rational(5, 9), Rational(2, 7), Rational(5, 8)]
b = [Rational(3, 5), Rational(4, 7), Rational(7, 8), Rational(8, 9)]
assert convolution(a, b, cycle=0) == \
convolution(a, b, cycle=len(a) + len(b) - 1)
assert convolution(a, b, cycle=4) == [Rational(87277, 26460), Rational(30521, 11340),
Rational(11125, 4032), Rational(3653, 1080)]
assert convolution(a, b, cycle=6) == [Rational(20177, 20160), Rational(676, 315), Rational(47, 24),
Rational(3053, 1080), Rational(16397, 5292), Rational(2497, 2268)]
assert convolution(a, b, cycle=9) == \
convolution(a, b, cycle=0) + [S.Zero]
# ntt
a = [2313, 5323532, S(3232), 42142, 42242421]
b = [S(33456), 56757, 45754, 432423]
assert convolution(a, b, prime=19*2**10 + 1, cycle=0) == \
convolution(a, b, prime=19*2**10 + 1, cycle=8) == \
convolution(a, b, prime=19*2**10 + 1)
assert convolution(a, b, prime=19*2**10 + 1, cycle=5) == [96, 17146, 2664,
15534, 3517]
assert convolution(a, b, prime=19*2**10 + 1, cycle=7) == [4643, 3458, 1260,
15534, 3517, 16314, 13688]
assert convolution(a, b, prime=19*2**10 + 1, cycle=9) == \
convolution(a, b, prime=19*2**10 + 1) + [0]
# fwht
u, v, w, x, y = symbols('u v w x y')
p, q, r, s, t = symbols('p q r s t')
c = [u, v, w, x, y]
d = [p, q, r, s, t]
assert convolution(a, b, dyadic=True, cycle=3) == \
[2499522285783, 19861417974796, 4702176579021]
assert convolution(a, b, dyadic=True, cycle=5) == [2718149225143,
2114320852171, 20571217906407, 246166418903, 1413262436976]
assert convolution(c, d, dyadic=True, cycle=4) == \
[p*u + p*y + q*v + r*w + s*x + t*u + t*y,
p*v + q*u + q*y + r*x + s*w + t*v,
p*w + q*x + r*u + r*y + s*v + t*w,
p*x + q*w + r*v + s*u + s*y + t*x]
assert convolution(c, d, dyadic=True, cycle=6) == \
[p*u + q*v + r*w + r*y + s*x + t*w + t*y,
p*v + q*u + r*x + s*w + s*y + t*x,
p*w + q*x + r*u + s*v,
p*x + q*w + r*v + s*u,
p*y + t*u,
q*y + t*v]
# subset
assert convolution(a, b, subset=True, cycle=7) == [18266671799811,
178235365533, 213958794, 246166418903, 1413262436976,
2397553088697, 1932759730434]
assert convolution(a[1:], b, subset=True, cycle=4) == \
[178104086592, 302255835516, 244982785880, 3717819845434]
assert convolution(a, b[:-1], subset=True, cycle=6) == [1932837114162,
178235365533, 213958794, 245166224504, 1413262436976, 2397553088697]
assert convolution(c, d, subset=True, cycle=3) == \
[p*u + p*x + q*w + r*v + r*y + s*u + t*w,
p*v + p*y + q*u + s*y + t*u + t*x,
p*w + q*y + r*u + t*v]
assert convolution(c, d, subset=True, cycle=5) == \
[p*u + q*y + t*v,
p*v + q*u + r*y + t*w,
p*w + r*u + s*y + t*x,
p*x + q*w + r*v + s*u,
p*y + t*u]
raises(ValueError, lambda: convolution([1, 2, 3], [4, 5, 6], cycle=-1))
def test_convolution_fft():
assert all(convolution_fft([], x, dps=y) == [] for x in ([], [1]) for y in (None, 3))
assert convolution_fft([1, 2, 3], [4, 5, 6]) == [4, 13, 28, 27, 18]
assert convolution_fft([1], [5, 6, 7]) == [5, 6, 7]
assert convolution_fft([1, 3], [5, 6, 7]) == [5, 21, 25, 21]
assert convolution_fft([1 + 2*I], [2 + 3*I]) == [-4 + 7*I]
assert convolution_fft([1 + 2*I, 3 + 4*I, 5 + 3*I/5], [Rational(2, 5) + 4*I/7]) == \
[Rational(-26, 35) + I*48/35, Rational(-38, 35) + I*116/35, Rational(58, 35) + I*542/175]
assert convolution_fft([Rational(3, 4), Rational(5, 6)], [Rational(7, 8), Rational(1, 3), Rational(2, 5)]) == \
[Rational(21, 32), Rational(47, 48), Rational(26, 45), Rational(1, 3)]
assert convolution_fft([Rational(1, 9), Rational(2, 3), Rational(3, 5)], [Rational(2, 5), Rational(3, 7), Rational(4, 9)]) == \
[Rational(2, 45), Rational(11, 35), Rational(8152, 14175), Rational(523, 945), Rational(4, 15)]
assert convolution_fft([pi, E, sqrt(2)], [sqrt(3), 1/pi, 1/E]) == \
[sqrt(3)*pi, 1 + sqrt(3)*E, E/pi + pi*exp(-1) + sqrt(6),
sqrt(2)/pi + 1, sqrt(2)*exp(-1)]
assert convolution_fft([2321, 33123], [5321, 6321, 71323]) == \
[12350041, 190918524, 374911166, 2362431729]
assert convolution_fft([312313, 31278232], [32139631, 319631]) == \
[10037624576503, 1005370659728895, 9997492572392]
raises(TypeError, lambda: convolution_fft(x, y))
raises(ValueError, lambda: convolution_fft([x, y], [y, x]))
def test_convolution_ntt():
# prime moduli of the form (m*2**k + 1), sequence length
# should be a divisor of 2**k
p = 7*17*2**23 + 1
q = 19*2**10 + 1
r = 2*500000003 + 1 # only for sequences of length 1 or 2
# s = 2*3*5*7 # composite modulus
assert all(convolution_ntt([], x, prime=y) == [] for x in ([], [1]) for y in (p, q, r))
assert convolution_ntt([2], [3], r) == [6]
assert convolution_ntt([2, 3], [4], r) == [8, 12]
assert convolution_ntt([32121, 42144, 4214, 4241], [32132, 3232, 87242], p) == [33867619,
459741727, 79180879, 831885249, 381344700, 369993322]
assert convolution_ntt([121913, 3171831, 31888131, 12], [17882, 21292, 29921, 312], q) == \
[8158, 3065, 3682, 7090, 1239, 2232, 3744]
assert convolution_ntt([12, 19, 21, 98, 67], [2, 6, 7, 8, 9], p) == \
convolution_ntt([12, 19, 21, 98, 67], [2, 6, 7, 8, 9], q)
assert convolution_ntt([12, 19, 21, 98, 67], [21, 76, 17, 78, 69], p) == \
convolution_ntt([12, 19, 21, 98, 67], [21, 76, 17, 78, 69], q)
raises(ValueError, lambda: convolution_ntt([2, 3], [4, 5], r))
raises(ValueError, lambda: convolution_ntt([x, y], [y, x], q))
raises(TypeError, lambda: convolution_ntt(x, y, p))
def test_convolution_fwht():
assert convolution_fwht([], []) == []
assert convolution_fwht([], [1]) == []
assert convolution_fwht([1, 2, 3], [4, 5, 6]) == [32, 13, 18, 27]
assert convolution_fwht([Rational(5, 7), Rational(6, 8), Rational(7, 3)], [2, 4, Rational(6, 7)]) == \
[Rational(45, 7), Rational(61, 14), Rational(776, 147), Rational(419, 42)]
a = [1, Rational(5, 3), sqrt(3), Rational(7, 5), 4 + 5*I]
b = [94, 51, 53, 45, 31, 27, 13]
c = [3 + 4*I, 5 + 7*I, 3, Rational(7, 6), 8]
assert convolution_fwht(a, b) == [53*sqrt(3) + 366 + 155*I,
45*sqrt(3) + Rational(5848, 15) + 135*I,
94*sqrt(3) + Rational(1257, 5) + 65*I,
51*sqrt(3) + Rational(3974, 15),
13*sqrt(3) + 452 + 470*I,
Rational(4513, 15) + 255*I,
31*sqrt(3) + Rational(1314, 5) + 265*I,
27*sqrt(3) + Rational(3676, 15) + 225*I]
assert convolution_fwht(b, c) == [Rational(1993, 2) + 733*I, Rational(6215, 6) + 862*I,
Rational(1659, 2) + 527*I, Rational(1988, 3) + 551*I, 1019 + 313*I, Rational(3955, 6) + 325*I,
Rational(1175, 2) + 52*I, Rational(3253, 6) + 91*I]
assert convolution_fwht(a[3:], c) == [Rational(-54, 5) + I*293/5, -1 + I*204/5,
Rational(133, 15) + I*35/6, Rational(409, 30) + 15*I, Rational(56, 5), 32 + 40*I, 0, 0]
u, v, w, x, y, z = symbols('u v w x y z')
assert convolution_fwht([u, v], [x, y]) == [u*x + v*y, u*y + v*x]
assert convolution_fwht([u, v, w], [x, y]) == \
[u*x + v*y, u*y + v*x, w*x, w*y]
assert convolution_fwht([u, v, w], [x, y, z]) == \
[u*x + v*y + w*z, u*y + v*x, u*z + w*x, v*z + w*y]
raises(TypeError, lambda: convolution_fwht(x, y))
raises(TypeError, lambda: convolution_fwht(x*y, u + v))
def test_convolution_subset():
assert convolution_subset([], []) == []
assert convolution_subset([], [Rational(1, 3)]) == []
assert convolution_subset([6 + I*3/7], [Rational(2, 3)]) == [4 + I*2/7]
a = [1, Rational(5, 3), sqrt(3), 4 + 5*I]
b = [64, 71, 55, 47, 33, 29, 15]
c = [3 + I*2/3, 5 + 7*I, 7, Rational(7, 5), 9]
assert convolution_subset(a, b) == [64, Rational(533, 3), 55 + 64*sqrt(3),
71*sqrt(3) + Rational(1184, 3) + 320*I, 33, 84,
15 + 33*sqrt(3), 29*sqrt(3) + 157 + 165*I]
assert convolution_subset(b, c) == [192 + I*128/3, 533 + I*1486/3,
613 + I*110/3, Rational(5013, 5) + I*1249/3,
675 + 22*I, 891 + I*751/3,
771 + 10*I, Rational(3736, 5) + 105*I]
assert convolution_subset(a, c) == convolution_subset(c, a)
assert convolution_subset(a[:2], b) == \
[64, Rational(533, 3), 55, Rational(416, 3), 33, 84, 15, 25]
assert convolution_subset(a[:2], c) == \
[3 + I*2/3, 10 + I*73/9, 7, Rational(196, 15), 9, 15, 0, 0]
u, v, w, x, y, z = symbols('u v w x y z')
assert convolution_subset([u, v, w], [x, y]) == [u*x, u*y + v*x, w*x, w*y]
assert convolution_subset([u, v, w, x], [y, z]) == \
[u*y, u*z + v*y, w*y, w*z + x*y]
assert convolution_subset([u, v], [x, y, z]) == \
convolution_subset([x, y, z], [u, v])
raises(TypeError, lambda: convolution_subset(x, z))
raises(TypeError, lambda: convolution_subset(Rational(7, 3), u))
def test_covering_product():
assert covering_product([], []) == []
assert covering_product([], [Rational(1, 3)]) == []
assert covering_product([6 + I*3/7], [Rational(2, 3)]) == [4 + I*2/7]
a = [1, Rational(5, 8), sqrt(7), 4 + 9*I]
b = [66, 81, 95, 49, 37, 89, 17]
c = [3 + I*2/3, 51 + 72*I, 7, Rational(7, 15), 91]
assert covering_product(a, b) == [66, Rational(1383, 8), 95 + 161*sqrt(7),
130*sqrt(7) + 1303 + 2619*I, 37,
Rational(671, 4), 17 + 54*sqrt(7),
89*sqrt(7) + Rational(4661, 8) + 1287*I]
assert covering_product(b, c) == [198 + 44*I, 7740 + 10638*I,
1412 + I*190/3, Rational(42684, 5) + I*31202/3,
9484 + I*74/3, 22163 + I*27394/3,
10621 + I*34/3, Rational(90236, 15) + 1224*I]
assert covering_product(a, c) == covering_product(c, a)
assert covering_product(b, c[:-1]) == [198 + 44*I, 7740 + 10638*I,
1412 + I*190/3, Rational(42684, 5) + I*31202/3,
111 + I*74/3, 6693 + I*27394/3,
429 + I*34/3, Rational(23351, 15) + 1224*I]
assert covering_product(a, c[:-1]) == [3 + I*2/3,
Rational(339, 4) + I*1409/12, 7 + 10*sqrt(7) + 2*sqrt(7)*I/3,
-403 + 772*sqrt(7)/15 + 72*sqrt(7)*I + I*12658/15]
u, v, w, x, y, z = symbols('u v w x y z')
assert covering_product([u, v, w], [x, y]) == \
[u*x, u*y + v*x + v*y, w*x, w*y]
assert covering_product([u, v, w, x], [y, z]) == \
[u*y, u*z + v*y + v*z, w*y, w*z + x*y + x*z]
assert covering_product([u, v], [x, y, z]) == \
covering_product([x, y, z], [u, v])
raises(TypeError, lambda: covering_product(x, z))
raises(TypeError, lambda: covering_product(Rational(7, 3), u))
def test_intersecting_product():
assert intersecting_product([], []) == []
assert intersecting_product([], [Rational(1, 3)]) == []
assert intersecting_product([6 + I*3/7], [Rational(2, 3)]) == [4 + I*2/7]
a = [1, sqrt(5), Rational(3, 8) + 5*I, 4 + 7*I]
b = [67, 51, 65, 48, 36, 79, 27]
c = [3 + I*2/5, 5 + 9*I, 7, Rational(7, 19), 13]
assert intersecting_product(a, b) == [195*sqrt(5) + Rational(6979, 8) + 1886*I,
178*sqrt(5) + 520 + 910*I, Rational(841, 2) + 1344*I,
192 + 336*I, 0, 0, 0, 0]
assert intersecting_product(b, c) == [Rational(128553, 19) + I*9521/5,
Rational(17820, 19) + 1602*I, Rational(19264, 19), Rational(336, 19), 1846, 0, 0, 0]
assert intersecting_product(a, c) == intersecting_product(c, a)
assert intersecting_product(b[1:], c[:-1]) == [Rational(64788, 19) + I*8622/5,
Rational(12804, 19) + 1152*I, Rational(11508, 19), Rational(252, 19), 0, 0, 0, 0]
assert intersecting_product(a, c[:-2]) == \
[Rational(-99, 5) + 10*sqrt(5) + 2*sqrt(5)*I/5 + I*3021/40,
-43 + 5*sqrt(5) + 9*sqrt(5)*I + 71*I, Rational(245, 8) + 84*I, 0]
u, v, w, x, y, z = symbols('u v w x y z')
assert intersecting_product([u, v, w], [x, y]) == \
[u*x + u*y + v*x + w*x + w*y, v*y, 0, 0]
assert intersecting_product([u, v, w, x], [y, z]) == \
[u*y + u*z + v*y + w*y + w*z + x*y, v*z + x*z, 0, 0]
assert intersecting_product([u, v], [x, y, z]) == \
intersecting_product([x, y, z], [u, v])
raises(TypeError, lambda: intersecting_product(x, z))
raises(TypeError, lambda: intersecting_product(u, Rational(8, 3)))
def test_convolution_int():
assert convolution_int([1], [1]) == [1]
assert convolution_int([1, 1], [0]) == [0]
assert convolution_int([1, 2, 3], [4, 5, 6]) == [4, 13, 28, 27, 18]
assert convolution_int([1], [5, 6, 7]) == [5, 6, 7]
assert convolution_int([1, 3], [5, 6, 7]) == [5, 21, 25, 21]
assert convolution_int([10, -5, 1, 3], [-5, 6, 7]) == [-50, 85, 35, -44, 25, 21]
assert convolution_int([0, 1, 0, -1], [1, 0, -1, 0]) == [0, 1, 0, -2, 0, 1]
assert convolution_int(
[-341, -5, 1, 3, -71, -99, 43, 87],
[5, 6, 7, 12, 345, 21, -78, -7, -89]
) == [-1705, -2071, -2412, -4106, -118035, -9774, 25998, 2981, 5509,
-34317, 19228, 38870, 5485, 1724, -4436, -7743]

View File

@ -0,0 +1,59 @@
from sympy.core.numbers import Rational
from sympy.functions.combinatorial.numbers import fibonacci
from sympy.core import S, symbols
from sympy.testing.pytest import raises
from sympy.discrete.recurrences import linrec
def test_linrec():
assert linrec(coeffs=[1, 1], init=[1, 1], n=20) == 10946
assert linrec(coeffs=[1, 2, 3, 4, 5], init=[1, 1, 0, 2], n=10) == 1040
assert linrec(coeffs=[0, 0, 11, 13], init=[23, 27], n=25) == 59628567384
assert linrec(coeffs=[0, 0, 1, 1, 2], init=[1, 5, 3], n=15) == 165
assert linrec(coeffs=[11, 13, 15, 17], init=[1, 2, 3, 4], n=70) == \
56889923441670659718376223533331214868804815612050381493741233489928913241
assert linrec(coeffs=[0]*55 + [1, 1, 2, 3], init=[0]*50 + [1, 2, 3], n=4000) == \
702633573874937994980598979769135096432444135301118916539
assert linrec(coeffs=[11, 13, 15, 17], init=[1, 2, 3, 4], n=10**4)
assert linrec(coeffs=[11, 13, 15, 17], init=[1, 2, 3, 4], n=10**5)
assert all(linrec(coeffs=[1, 1], init=[0, 1], n=n) == fibonacci(n)
for n in range(95, 115))
assert all(linrec(coeffs=[1, 1], init=[1, 1], n=n) == fibonacci(n + 1)
for n in range(595, 615))
a = [S.Half, Rational(3, 4), Rational(5, 6), 7, Rational(8, 9), Rational(3, 5)]
b = [1, 2, 8, Rational(5, 7), Rational(3, 7), Rational(2, 9), 6]
x, y, z = symbols('x y z')
assert linrec(coeffs=a[:5], init=b[:4], n=80) == \
Rational(1726244235456268979436592226626304376013002142588105090705187189,
1960143456748895967474334873705475211264)
assert linrec(coeffs=a[:4], init=b[:4], n=50) == \
Rational(368949940033050147080268092104304441, 504857282956046106624)
assert linrec(coeffs=a[3:], init=b[:3], n=35) == \
Rational(97409272177295731943657945116791049305244422833125109,
814315512679031689453125)
assert linrec(coeffs=[0]*60 + [Rational(2, 3), Rational(4, 5)], init=b, n=3000) == \
Rational(26777668739896791448594650497024, 48084516708184142230517578125)
raises(TypeError, lambda: linrec(coeffs=[11, 13, 15, 17], init=[1, 2, 3, 4, 5], n=1))
raises(TypeError, lambda: linrec(coeffs=a[:4], init=b[:5], n=10000))
raises(ValueError, lambda: linrec(coeffs=a[:4], init=b[:4], n=-10000))
raises(TypeError, lambda: linrec(x, b, n=10000))
raises(TypeError, lambda: linrec(a, y, n=10000))
assert linrec(coeffs=[x, y, z], init=[1, 1, 1], n=4) == \
x**2 + x*y + x*z + y + z
assert linrec(coeffs=[1, 2, 1], init=[x, y, z], n=20) == \
269542*x + 664575*y + 578949*z
assert linrec(coeffs=[0, 3, 1, 2], init=[x, y], n=30) == \
58516436*x + 56372788*y
assert linrec(coeffs=[0]*50 + [1, 2, 3], init=[x, y, z], n=1000) == \
11477135884896*x + 25999077948732*y + 41975630244216*z
assert linrec(coeffs=[], init=[1, 1], n=20) == 0
assert linrec(coeffs=[x, y, z], init=[1, 2, 3], n=2) == 3

View File

@ -0,0 +1,154 @@
from sympy.functions.elementary.miscellaneous import sqrt
from sympy.core import S, Symbol, symbols, I, Rational
from sympy.discrete import (fft, ifft, ntt, intt, fwht, ifwht,
mobius_transform, inverse_mobius_transform)
from sympy.testing.pytest import raises
def test_fft_ifft():
assert all(tf(ls) == ls for tf in (fft, ifft)
for ls in ([], [Rational(5, 3)]))
ls = list(range(6))
fls = [15, -7*sqrt(2)/2 - 4 - sqrt(2)*I/2 + 2*I, 2 + 3*I,
-4 + 7*sqrt(2)/2 - 2*I - sqrt(2)*I/2, -3,
-4 + 7*sqrt(2)/2 + sqrt(2)*I/2 + 2*I,
2 - 3*I, -7*sqrt(2)/2 - 4 - 2*I + sqrt(2)*I/2]
assert fft(ls) == fls
assert ifft(fls) == ls + [S.Zero]*2
ls = [1 + 2*I, 3 + 4*I, 5 + 6*I]
ifls = [Rational(9, 4) + 3*I, I*Rational(-7, 4), Rational(3, 4) + I, -2 - I/4]
assert ifft(ls) == ifls
assert fft(ifls) == ls + [S.Zero]
x = Symbol('x', real=True)
raises(TypeError, lambda: fft(x))
raises(ValueError, lambda: ifft([x, 2*x, 3*x**2, 4*x**3]))
def test_ntt_intt():
# prime moduli of the form (m*2**k + 1), sequence length
# should be a divisor of 2**k
p = 7*17*2**23 + 1
q = 2*500000003 + 1 # only for sequences of length 1 or 2
r = 2*3*5*7 # composite modulus
assert all(tf(ls, p) == ls for tf in (ntt, intt)
for ls in ([], [5]))
ls = list(range(6))
nls = [15, 801133602, 738493201, 334102277, 998244350, 849020224,
259751156, 12232587]
assert ntt(ls, p) == nls
assert intt(nls, p) == ls + [0]*2
ls = [1 + 2*I, 3 + 4*I, 5 + 6*I]
x = Symbol('x', integer=True)
raises(TypeError, lambda: ntt(x, p))
raises(ValueError, lambda: intt([x, 2*x, 3*x**2, 4*x**3], p))
raises(ValueError, lambda: intt(ls, p))
raises(ValueError, lambda: ntt([1.2, 2.1, 3.5], p))
raises(ValueError, lambda: ntt([3, 5, 6], q))
raises(ValueError, lambda: ntt([4, 5, 7], r))
raises(ValueError, lambda: ntt([1.0, 2.0, 3.0], p))
def test_fwht_ifwht():
assert all(tf(ls) == ls for tf in (fwht, ifwht) \
for ls in ([], [Rational(7, 4)]))
ls = [213, 321, 43235, 5325, 312, 53]
fls = [49459, 38061, -47661, -37759, 48729, 37543, -48391, -38277]
assert fwht(ls) == fls
assert ifwht(fls) == ls + [S.Zero]*2
ls = [S.Half + 2*I, Rational(3, 7) + 4*I, Rational(5, 6) + 6*I, Rational(7, 3), Rational(9, 4)]
ifls = [Rational(533, 672) + I*3/2, Rational(23, 224) + I/2, Rational(1, 672), Rational(107, 224) - I,
Rational(155, 672) + I*3/2, Rational(-103, 224) + I/2, Rational(-377, 672), Rational(-19, 224) - I]
assert ifwht(ls) == ifls
assert fwht(ifls) == ls + [S.Zero]*3
x, y = symbols('x y')
raises(TypeError, lambda: fwht(x))
ls = [x, 2*x, 3*x**2, 4*x**3]
ifls = [x**3 + 3*x**2/4 + x*Rational(3, 4),
-x**3 + 3*x**2/4 - x/4,
-x**3 - 3*x**2/4 + x*Rational(3, 4),
x**3 - 3*x**2/4 - x/4]
assert ifwht(ls) == ifls
assert fwht(ifls) == ls
ls = [x, y, x**2, y**2, x*y]
fls = [x**2 + x*y + x + y**2 + y,
x**2 + x*y + x - y**2 - y,
-x**2 + x*y + x - y**2 + y,
-x**2 + x*y + x + y**2 - y,
x**2 - x*y + x + y**2 + y,
x**2 - x*y + x - y**2 - y,
-x**2 - x*y + x - y**2 + y,
-x**2 - x*y + x + y**2 - y]
assert fwht(ls) == fls
assert ifwht(fls) == ls + [S.Zero]*3
ls = list(range(6))
assert fwht(ls) == [x*8 for x in ifwht(ls)]
def test_mobius_transform():
assert all(tf(ls, subset=subset) == ls
for ls in ([], [Rational(7, 4)]) for subset in (True, False)
for tf in (mobius_transform, inverse_mobius_transform))
w, x, y, z = symbols('w x y z')
assert mobius_transform([x, y]) == [x, x + y]
assert inverse_mobius_transform([x, x + y]) == [x, y]
assert mobius_transform([x, y], subset=False) == [x + y, y]
assert inverse_mobius_transform([x + y, y], subset=False) == [x, y]
assert mobius_transform([w, x, y, z]) == [w, w + x, w + y, w + x + y + z]
assert inverse_mobius_transform([w, w + x, w + y, w + x + y + z]) == \
[w, x, y, z]
assert mobius_transform([w, x, y, z], subset=False) == \
[w + x + y + z, x + z, y + z, z]
assert inverse_mobius_transform([w + x + y + z, x + z, y + z, z], subset=False) == \
[w, x, y, z]
ls = [Rational(2, 3), Rational(6, 7), Rational(5, 8), 9, Rational(5, 3) + 7*I]
mls = [Rational(2, 3), Rational(32, 21), Rational(31, 24), Rational(1873, 168),
Rational(7, 3) + 7*I, Rational(67, 21) + 7*I, Rational(71, 24) + 7*I,
Rational(2153, 168) + 7*I]
assert mobius_transform(ls) == mls
assert inverse_mobius_transform(mls) == ls + [S.Zero]*3
mls = [Rational(2153, 168) + 7*I, Rational(69, 7), Rational(77, 8), 9, Rational(5, 3) + 7*I, 0, 0, 0]
assert mobius_transform(ls, subset=False) == mls
assert inverse_mobius_transform(mls, subset=False) == ls + [S.Zero]*3
ls = ls[:-1]
mls = [Rational(2, 3), Rational(32, 21), Rational(31, 24), Rational(1873, 168)]
assert mobius_transform(ls) == mls
assert inverse_mobius_transform(mls) == ls
mls = [Rational(1873, 168), Rational(69, 7), Rational(77, 8), 9]
assert mobius_transform(ls, subset=False) == mls
assert inverse_mobius_transform(mls, subset=False) == ls
raises(TypeError, lambda: mobius_transform(x, subset=True))
raises(TypeError, lambda: inverse_mobius_transform(y, subset=False))

View File

@ -0,0 +1,425 @@
"""
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__