Source code for ginger.autocorr
from math import cos, pi, sqrt
from typing import List
from mywheel.robin import Robin
from .rootfinding import Options, delta, horner, suppress
from .vector2 import Vector2
[docs]
def initial_autocorr(coeffs: List[float]) -> List[Vector2]:
"""
Calculates initial guesses for autocorrelation roots using coefficient analysis.
The method:
1. Computes a root radius estimate from the constant term
2. Adjusts radius to focus on roots outside unit circle
3. Generates initial guesses using cosine spaced angles with quadratic terms
:param coeffs: Polynomial coefficients from highest to lowest degree
:return: List of Vector2 representing quadratic factors (x² - rx - q)
"""
degree = len(coeffs) - 1
# Calculate initial radius estimate using absolute value of constant term
radius = pow(abs(coeffs[-1]), 1.0 / degree)
if radius < 1: # Focus on roots outside unit circle by taking reciprocal
radius = 1 / radius
degree //= 2 # Work with half-degree for conjugate pairs
k = pi / degree # Angular step size between roots
m = radius * radius # Quadratic term for Vector2
# Generate initial guesses using cosine distribution of roots
return [Vector2(2 * radius * cos(k * i), -m) for i in range(1, degree, 2)]
[docs]
def pbairstow_autocorr(
coeffs: List[float], vrs: List[Vector2], options: Options = Options()
):
"""
Implements Bairstow's method for polynomial root finding with autocorrelation.
Process outline:
1. Iterates until convergence or max iterations
2. Evaluates polynomial and first derivative using Horner's scheme
3. Suppresses interference from other roots
4. Updates estimates using Newton-Raphson step
:param coeffs: Polynomial coefficients (degree must be even)
:param vrs: Initial guesses for quadratic factors
:param options: Algorithm control parameters
:return: Tuple of (updated factors, iterations, convergence status)
"""
M = len(vrs) # Number of quadratic factors
degree = len(coeffs) - 1
converged = [False] * M # Convergence status tracker
robin = Robin(M) # Round-robin iterator for factor updates
for niter in range(options.max_iters):
tolerance = 0.0 # Track maximum error across all factors
for i, (vri, ci) in enumerate(zip(vrs, converged)):
if ci: # Skip already converged factors
continue
# Polynomial evaluation at current factor estimate
coeffs1 = coeffs.copy()
vA = horner(coeffs1, degree, vri)
# Check individual convergence
tol_i = max(abs(vA.x), abs(vA.y))
if tol_i < options.tol_ind:
converged[i] = True
continue
tolerance = max(tolerance, tol_i)
# Evaluate reduced polynomial (degree-2)
vA1 = horner(coeffs1, degree - 2, vri)
# Suppress influence of other factors
for j in robin.exclude(i):
vrj = vrs[j]
vA, vA1 = suppress(vA, vA1, vri, vrj)
# Handle reciprocal roots
vrn = Vector2(-vrj.x, 1.0) / vrj.y
vA, vA1 = suppress(vA, vA1, vri, vrn)
# Apply Newton-Raphson update
vrs[i] -= delta(vA, vri, vA1)
# Check global convergence
if tolerance < options.tolerance:
return vrs, niter, True
return vrs, options.max_iters, False