Source code for prodigy.distribution.multisection
"""
Multisection of univariate rational functions without roots of unity.
For f in x and a modulus q, the r-th section f_r keeps the terms x^k of the power series of f with
k = r (mod q); with a primitive q-th root of unity w it is (1/q) * sum_m w^(-m r) f(w^m x). For a rational
function f = P/Q this sum can be computed exactly over the coefficient field of f:
N(x) := prod_m Q(w^m x) = (-1)^(q deg Q) * Res_y(Q(y), y^q - x^q)
is a polynomial in x^q (so N(w x) = N(x)), and Q divides it. Writing f = P (N / Q) / N, the substitution
x -> w^m x only changes the numerator M = P N / Q, so f_r is the r-th section of the polynomial M divided
by N.
"""
from __future__ import annotations
from typing import List
import sympy
[docs]
def rational_multisection(function: sympy.Expr, variable: sympy.Symbol, modulus: int) -> List[sympy.Expr]:
"""
Returns the sections f_0, ..., f_{q-1} of the rational function `function` in `variable` for q = `modulus`.
Other symbols are treated as constants.
:raises ValueError: if `function` is not a rational function in `variable` or `modulus` is not positive.
"""
if modulus < 1:
raise ValueError(f"The modulus has to be positive, got {modulus}.")
if not function.is_rational_function(variable):
raise ValueError(f"{function} is not a rational function in {variable}.")
numerator, denominator = sympy.fraction(sympy.cancel(sympy.together(function)))
numerator_poly = sympy.Poly(numerator, variable)
denominator_poly = sympy.Poly(denominator, variable)
y = sympy.Dummy("y")
norm = sympy.resultant(denominator_poly.as_expr().subs(variable, y), y ** modulus - variable ** modulus, y)
norm_poly = sympy.Poly(sympy.expand(norm * (-1) ** (modulus * denominator_poly.degree())), variable)
cofactor, remainder = sympy.div(norm_poly, denominator_poly)
if not remainder.is_zero:
raise AssertionError(f"The norm of {denominator} is not divisible by it.") # pragma: no cover
product = numerator_poly * cofactor
norm_expr = norm_poly.as_expr()
sections: List[sympy.Expr] = []
for residue in range(modulus):
section = sum((coefficient * variable ** exponent
for (exponent,), coefficient in product.terms() if exponent % modulus == residue),
sympy.Integer(0))
sections.append(sympy.cancel(section / norm_expr))
return sections