Skip to content

Reversed polynomials #973

@anvaperi

Description

@anvaperi

Reversed Polynomials

Hello! I'd like to propose to add a **reverse: boolean = False** argument to the polynomial functions _polynomial_eval_, _polynomial_from_roots_ and _polynomial_derivative_, so they can also handle polynomials with coefficients ordered from lowest to highest degree (Little-endian notation) when such argument is set to True. Defaulting it to False would ensure backwards compatibility.

The current implementation of polynomials follows the Big-endian notation where the coefficient of highest degree comes first. For some cases, the reverse order could make some calculations easier as there would be a direct correspondence between the iterable index and the power of x, that is, instead of representing the polynomial:
x³ ‐ 4 x² ‐ 17 x + 60 as [1, -4, -17, 60], it could be reordered (60 - 17 x - 4 x² + x³) as [60, -17, -4, 1].

Code implementation

I have succesfully tested the code with minimal modifications:


def polynomial_from_roots(roots, reverse=False):
    """Compute a polynomial's coefficients from its roots.

    >>> roots = [5, -4, 3]            # (x - 5) * (x + 4) * (x - 3)
    >>> polynomial_from_roots(roots)  # x³ - 4 x² - 17 x + 60
    [1, -4, -17, 60]
    >>> roots = [5, -4, 3]            # (x - 5) * (x + 4) * (x - 3)
    >>> polynomial_from_roots(roots, reverse=True)  # + 60 - 17 x - 4 x² + x³
    [60, -17, -4, 1]

    Supports all numeric types: int, float, complex, Decimal, Fraction.
    """
    # This recipe differs from the one in itertools docs in that it
    # applies list() after each call to convolve().  This avoids
    # hitting stack limits with nested generators.
    poly = [1]
    if reverse:
        for root in roots:
            poly = list(convolve(poly, (-root, 1)))
        return poly
    for root in roots:
        poly = list(convolve(poly, (1, -root)))
    return poly


def polynomial_eval(coefficients, x, reverse=False):
    """Evaluate a polynomial at a specific value.

    Computes with better numeric stability than Horner's method.

    Evaluate ``x³ - 4 x² - 17 x + 60`` at ``x = 2.5``:

    >>> coefficients = [1, -4, -17, 60]  
    >>> polynomial_eval(coefficients, x=2.5)
    8.125
    >>> coefficients = [60, -17, -4, 1]
    >>> polynomial_eval(coefficients, x=2.5, reverse=True)
    8.125

    Supports all numeric types: int, float, complex, Decimal, Fraction.
    """
    n = len(coefficients)
    if n == 0:
        return type(x)(0)
    powers = map(pow, repeat(x), range(n) if reverse else reversed(range(n)))
    return _sumprod(coefficients, powers)



def polynomial_derivative(coefficients, reverse=False):
    """Compute the first derivative of a polynomial.

    Evaluate the derivative of ``x³ - 4 x² - 17 x + 60``:

    >>> coefficients = [1, -4, -17, 60]
    >>> polynomial_derivative(coefficients)
    [3, -8, -17]
    >>> coefficients = [60, -17, -4, 1]
    >>> polynomial_derivative(coefficients, reverse=True)
    [-17, -8, 3]

    Supports all numeric types: int, float, complex, Decimal, Fraction.
    """
    n = len(coefficients)
    if reverse:
        powers = range(n)
        return list(map(mul, coefficients, powers))[1:]
    powers = reversed(range(1, n))
    return list(map(mul, coefficients, powers))

Practical example

  • One example leveraging this rearrangement could be this alternative way to calculate a polynomial derivative:

d( 60 - 17 x - 4 x² + x³ )/dx = - 17 - 8 x + 3x²


print(list(reversed(more_itertools.polynomial_derivative(
    list(reversed([60, -17, -4, 1]))
)))) # [-17, -8, 3] 

index = itertools.count(1)
print(list(more_itertools.difference(
    [60, -17, -4, 1], 
    func=lambda x, _: x*next(index), 
    initial=0
))) # [-17, -8, 3] 

Thank you

Thank you very much for considering this idea. If I can help in any way to clarify it any further, just let me know.
Best Regards.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions