Skip to content

chuchichaestli.utils.arithmetic.laurent

Laurent polynomials, the ring a two-channel filter bank factors over.

A Laurent polynomial carries negative powers too, which is what lets a delay and an advance sit in the same ring.

Classes:

Name Description
Laurent

A Laurent polynomial sum_i c[i] z**(low + i).

Functions:

Name Description
divide

Divide a by b, leaving a remainder of smaller span.

reverse

Substitute 1/z for z, which swaps the two ends.

Laurent dataclass

Laurent(c: tuple[float, ...], low: int = 0)

A Laurent polynomial sum_i c[i] z**(low + i).

Parameters:

Name Type Description Default
c tuple[float, ...]

Coefficients, lowest exponent first.

required
low int

Exponent the first coefficient carries.

0

Methods:

Name Description
of

Build a trimmed polynomial.

scaled

Multiply by a scalar.

Attributes:

Name Type Description
high int

Exponent of the last coefficient.

span int

Number of terms less one, the degree once normalized.

high property

high: int

Exponent of the last coefficient.

span property

span: int

Number of terms less one, the degree once normalized.

of staticmethod

of(
    values: Sequence[float],
    low: int = 0,
    scale: float = 0.0,
) -> Laurent

Build a trimmed polynomial.

Parameters:

Name Type Description Default
values Sequence[float]

Coefficients, lowest exponent first.

required
low int

Exponent the first coefficient carries.

0
scale float

Magnitude the coefficients came from, where that is larger than the coefficients left. A remainder can be residue all the way down, which it cannot tell from its own terms alone.

0.0
Source code in src/chuchichaestli/utils/arithmetic/laurent.py
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
@staticmethod
def of(values: Sequence[float], low: int = 0, scale: float = 0.0) -> "Laurent":
    """Build a trimmed polynomial.

    Args:
        values: Coefficients, lowest exponent first.
        low: Exponent the first coefficient carries.
        scale: Magnitude the coefficients came from, where that is larger
            than the coefficients left. A remainder can be residue all the
            way down, which it cannot tell from its own terms alone.
    """
    c = list(values)
    tol = 1e-11 * max(scale, max((abs(v) for v in c), default=0.0))
    while c and abs(c[0]) <= tol:
        c.pop(0)
        low += 1
    while c and abs(c[-1]) <= tol:
        c.pop()
    return Laurent(tuple(c), low if c else 0)

scaled

scaled(k: float) -> Laurent

Multiply by a scalar.

Parameters:

Name Type Description Default
k float

Factor to apply.

required
Source code in src/chuchichaestli/utils/arithmetic/laurent.py
91
92
93
94
95
96
97
def scaled(self, k: float) -> "Laurent":
    """Multiply by a scalar.

    Args:
        k: Factor to apply.
    """
    return Laurent.of([v * k for v in self.c], self.low)

divide

divide(a: Laurent, b: Laurent) -> tuple[Laurent, Laurent]

Divide a by b, leaving a remainder of smaller span.

Either end of b can serve as the pivot. Both are tried and the one that inflates the coefficients least is kept, since dividing through a small coefficient is what carries rounding error into every later step.

Parameters:

Name Type Description Default
a Laurent

Dividend.

required
b Laurent

Divisor, which may not be zero.

required

Returns:

Type Description
tuple[Laurent, Laurent]

The quotient and the remainder.

Raises:

Type Description
ZeroDivisionError

If b is zero.

Source code in src/chuchichaestli/utils/arithmetic/laurent.py
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
def divide(a: Laurent, b: Laurent) -> tuple[Laurent, Laurent]:
    """Divide `a` by `b`, leaving a remainder of smaller span.

    Either end of `b` can serve as the pivot. Both are tried and the one that
    inflates the coefficients least is kept, since dividing through a small
    coefficient is what carries rounding error into every later step.

    Args:
        a: Dividend.
        b: Divisor, which may not be zero.

    Returns:
        The quotient and the remainder.

    Raises:
        ZeroDivisionError: If `b` is zero.
    """
    if not b:
        raise ZeroDivisionError("a Laurent polynomial cannot divide by zero")
    top = _divide_high(a, b)
    q, r = _divide_high(reverse(a), reverse(b))
    bottom = (reverse(q), reverse(r))
    return min(top, bottom, key=lambda pair: _growth(*pair))

reverse

reverse(p: Laurent) -> Laurent

Substitute 1/z for z, which swaps the two ends.

Parameters:

Name Type Description Default
p Laurent

Polynomial to reverse.

required
Source code in src/chuchichaestli/utils/arithmetic/laurent.py
100
101
102
103
104
105
106
107
108
def reverse(p: Laurent) -> Laurent:
    """Substitute `1/z` for `z`, which swaps the two ends.

    Args:
        p: Polynomial to reverse.
    """
    if not p.c:
        return Laurent((), 0)
    return Laurent(tuple(reversed(p.c)), -p.high)