blend.rs

15.4 kB · rust · 477 lines

1const PRIMES: [u64; 3] = [2_147_483_647, 2_147_483_629, 2_147_483_587];23/// Adds two sequences term by term over their shared length.4pub fn add(a: &[i128], b: &[i128]) -> Vec<i128> {5    a.iter().zip(b).map(|(&x, &y)| x + y).collect()6}78/// Subtracts the second sequence from the first over their shared length.9pub fn sub(a: &[i128], b: &[i128]) -> Vec<i128> {10    a.iter().zip(b).map(|(&x, &y)| x - y).collect()11}1213/// Multiplies two sequences term by term over their shared length.14pub fn hadamard(a: &[i128], b: &[i128]) -> Vec<i128> {15    a.iter().zip(b).map(|(&x, &y)| x * y).collect()16}1718/// Convolves two sequences, keeping the exact prefix their shared length affords.19///20/// Panics when a convolution sum passes a signed hundred and twenty-eight bits.21pub fn cauchy(a: &[i128], b: &[i128]) -> Vec<i128> {22    let length = a.len().min(b.len());23    (0..length)24        .map(|n| {25            (0..=n).fold(0i128, |sum, i| {26                a[i].checked_mul(b[n - i])27                    .and_then(|v| sum.checked_add(v))28                    .expect("the convolution passes a hundred and twenty-eight bits")29            })30        })31        .collect()32}3334/// Drops the first terms of a sequence.35pub fn shift(a: &[i128], count: usize) -> Vec<i128> {36    a.iter().skip(count).copied().collect()37}3839/// Keeps every step-th term from the offset onward.40///41/// Panics at a step of zero.42pub fn decimate(a: &[i128], step: usize, offset: usize) -> Vec<i128> {43    assert!(step > 0, "decimate needs a step above zero");44    a.iter().skip(offset).step_by(step).copied().collect()45}4647/// Returns the first differences of a sequence, one term shorter.48pub fn delta(a: &[i128]) -> Vec<i128> {49    a.windows(2).map(|w| w[1] - w[0]).collect()50}5152/// Returns the partial sums of a sequence.53///54/// Panics when a partial sum passes a signed hundred and twenty-eight bits.55pub fn sigma(a: &[i128]) -> Vec<i128> {56    let mut total = 0i128;57    a.iter()58        .map(|&x| {59            total = total60                .checked_add(x)61                .expect("the partial sums pass a hundred and twenty-eight bits");62            total63        })64        .collect()65}6667/// Multiplies every term of a sequence by the factor.68pub fn scale(a: &[i128], factor: i128) -> Vec<i128> {69    a.iter().map(|&x| x * factor).collect()70}7172fn residues(terms: &[i128], prime: u64) -> Vec<u64> {73    let p = prime as i128;74    terms.iter().map(|&t| (t.rem_euclid(p)) as u64).collect()75}7677fn inverse(value: u64, prime: u64) -> u64 {78    let mut power = prime - 2;79    let mut out = 1u64;80    let mut factor = value % prime;81    while power > 0 {82        if power & 1 == 1 {83            out = out * factor % prime;84        }85        factor = factor * factor % prime;86        power >>= 1;87    }88    out89}9091fn massey(seq: &[u64], prime: u64) -> usize {92    let mut connection = vec![1u64];93    let mut previous = vec![1u64];94    let mut order = 0usize;95    let mut gap = 1usize;96    let mut last_delta = 1u64;97    for n in 0..seq.len() {98        let mut delta = 0u64;99        for (i, &c) in connection.iter().enumerate() {100            if i <= n {101                delta = (delta + c * seq[n - i]) % prime;102            }103        }104        if delta == 0 {105            gap += 1;106            continue;107        }108        if 2 * order > n {109            let scale = delta * inverse(last_delta, prime) % prime;110            for (i, &b) in previous.iter().enumerate() {111                let slot = i + gap;112                if slot >= connection.len() {113                    connection.resize(slot + 1, 0);114                }115                connection[slot] = (connection[slot] + prime * prime - scale * b) % prime;116            }117            gap += 1;118            continue;119        }120        let held = connection.clone();121        let scale = delta * inverse(last_delta, prime) % prime;122        for (i, &b) in previous.iter().enumerate() {123            let slot = i + gap;124            if slot >= connection.len() {125                connection.resize(slot + 1, 0);126            }127            connection[slot] = (connection[slot] + prime * prime - scale * b) % prime;128        }129        previous = held;130        last_delta = delta;131        gap = 1;132        order = n + 1 - order;133    }134    order135}136137fn fit(seq: &[u64], order: usize, prime: u64) -> Option<Vec<u64>> {138    let rows = seq.len() - order;139    let mut matrix: Vec<Vec<u64>> = (0..rows)140        .map(|r| {141            let mut row: Vec<u64> = (0..order).map(|c| seq[r + order - 1 - c]).collect();142            row.push(seq[r + order]);143            row144        })145        .collect();146    let mut pivots = Vec::new();147    let mut lead = 0usize;148    for col in 0..order {149        let Some(pivot) = (lead..rows).find(|&r| matrix[r][col] != 0) else {150            continue;151        };152        matrix.swap(lead, pivot);153        let inv = inverse(matrix[lead][col], prime);154        for value in &mut matrix[lead][col..=order] {155            *value = *value * inv % prime;156        }157        let cleared = matrix[lead].clone();158        for (row, entries) in matrix.iter_mut().enumerate() {159            if row != lead && entries[col] != 0 {160                let factor = entries[col];161                for (value, &top) in entries[col..=order].iter_mut().zip(&cleared[col..=order]) {162                    *value = (*value + prime - factor * top % prime) % prime;163                }164            }165        }166        pivots.push((lead, col));167        lead += 1;168    }169    if matrix[lead..].iter().any(|row| row[order] != 0) {170        return None;171    }172    let mut out = vec![0u64; order];173    for &(row, col) in &pivots {174        out[col] = matrix[row][order];175    }176    Some(out)177}178179fn combine(parts: [u64; 3]) -> u128 {180    let (p0, p1, p2) = (PRIMES[0] as u128, PRIMES[1] as u128, PRIMES[2] as u128);181    let m01 = p0 * p1;182    let step = (parts[1] as u128 + p1 - parts[0] as u128 % p1) % p1;183    let lift = inverse((p0 % p1) as u64, PRIMES[1]) as u128;184    let x01 = parts[0] as u128 + p0 * (step * lift % p1);185    let step2 = (parts[2] as u128 + p2 - x01 % p2) % p2;186    let lift2 = inverse((m01 % p2) as u64, PRIMES[2]) as u128;187    x01 + m01 * (step2 * lift2 % p2)188}189190fn reconstruct(residue: u128) -> Option<(i128, i128)> {191    let modulus = (PRIMES[0] as u128) * (PRIMES[1] as u128) * (PRIMES[2] as u128);192    let bound: u128 = 1 << 45;193    let (mut r0, mut r1) = (modulus as i128, residue as i128);194    let (mut t0, mut t1) = (0i128, 1i128);195    while r1.unsigned_abs() > bound {196        let q = r0 / r1;197        (r0, r1) = (r1, r0 - q * r1);198        (t0, t1) = (t1, t0 - q * t1);199    }200    if t1 == 0 || t1.unsigned_abs() > bound {201        return None;202    }203    let (num, den) = if t1 < 0 { (-r1, -t1) } else { (r1, t1) };204    let (mut a, mut b) = (num.unsigned_abs(), den.unsigned_abs());205    while b != 0 {206        (a, b) = (b, a % b);207    }208    if a > 1 {209        return None;210    }211    Some((num, den))212}213214fn verify(terms: &[i128], coefficients: &[(i128, i128)]) -> bool {215    let order = coefficients.len();216    let mut clear = 1i128;217    for &(_, den) in coefficients {218        clear = clear / gcd(clear, den) * den;219    }220    let weights: Vec<i128> = coefficients221        .iter()222        .map(|&(num, den)| num * (clear / den))223        .collect();224    let mut exact = true;225    for n in order..terms.len() {226        let mut sum = Some(0i128);227        for (i, &w) in weights.iter().enumerate() {228            sum = sum.and_then(|s| {229                w.checked_mul(terms[n - 1 - i])230                    .and_then(|v| s.checked_add(v))231            });232        }233        match (sum, terms[n].checked_mul(clear)) {234            (Some(left), Some(right)) => {235                if left != right {236                    return false;237                }238            }239            _ => {240                exact = false;241                break;242            }243        }244    }245    if exact {246        return true;247    }248    PRIMES.iter().all(|&prime| {249        let seq = residues(terms, prime);250        let p = prime as u128;251        let c = (clear.rem_euclid(prime as i128)) as u128;252        let ws: Vec<u128> = weights253            .iter()254            .map(|&w| (w.rem_euclid(prime as i128)) as u128)255            .collect();256        (order..terms.len()).all(|n| {257            let sum = ws258                .iter()259                .enumerate()260                .fold(0u128, |s, (i, &w)| (s + w * seq[n - 1 - i] as u128) % p);261            sum == c * seq[n] as u128 % p262        })263    })264}265266fn gcd(a: i128, b: i128) -> i128 {267    let (mut a, mut b) = (a.abs(), b.abs());268    while b != 0 {269        (a, b) = (b, a % b);270    }271    a.max(1)272}273274/// Finds the smallest linear constant-coefficient recurrence that fits every supplied term.275///276/// The coefficients come back as reduced fractions with the newest term first, so a277/// result of one and one twice is the Fibonacci rule. The hunt runs modulo three278/// primes, rebuilds the rationals by remainder reconstruction, and verifies the rule279/// on every term before answering; too few terms to trust an order returns nothing,280/// and the zero sequence returns the empty rule.281///282/// ```283/// let fib = vec![0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89];284/// assert_eq!(mrlynum::blend::recurrence(&fib), Some(vec![(1, 1), (1, 1)]));285/// ```286pub fn recurrence(terms: &[i128]) -> Option<Vec<(i128, i128)>> {287    if terms.len() < 4 {288        return None;289    }290    if terms.iter().all(|&t| t == 0) {291        return Some(Vec::new());292    }293    let order = PRIMES294        .iter()295        .map(|&p| massey(&residues(terms, p), p))296        .max()?;297    if order == 0 || 2 * order + 2 > terms.len() {298        return None;299    }300    let mut parts = Vec::new();301    for &prime in &PRIMES {302        parts.push(fit(&residues(terms, prime), order, prime)?);303    }304    let coefficients: Option<Vec<(i128, i128)>> = (0..order)305        .map(|i| reconstruct(combine([parts[0][i], parts[1][i], parts[2][i]])))306        .collect();307    let coefficients = coefficients?;308    verify(terms, &coefficients).then_some(coefficients)309}310311/// Returns the monic characteristic polynomial of a recurrence, highest power first.312pub fn characteristic(coefficients: &[(i128, i128)]) -> Vec<(i128, i128)> {313    let mut out = vec![(1, 1)];314    out.extend(coefficients.iter().map(|&(num, den)| (-num, den)));315    out316}317318/// Returns the largest positive real root of a recurrence's characteristic polynomial, the growth rate, or a not-a-number where no real root lands.319///320/// A simple root lands at machine precision; a repeated root lands to a few decimals only.321///322/// ```323/// let rate = mrlynum::blend::growth(&[(1, 1), (1, 1)]);324/// assert!((rate - 1.618033988749895).abs() < 1e-12);325/// ```326pub fn growth(coefficients: &[(i128, i128)]) -> f64 {327    let poly: Vec<f64> = characteristic(coefficients)328        .iter()329        .map(|&(num, den)| num as f64 / den as f64)330        .collect();331    let value = |x: f64| poly.iter().fold(0.0, |acc, &c| acc * x + c);332    let bound = 1.0 + poly.iter().skip(1).map(|c| c.abs()).fold(0.0f64, f64::max);333    let steps = 8192;334    let mut best = f64::NAN;335    let mut gap = f64::INFINITY;336    for i in (0..steps).rev() {337        let (lo, hi) = (338            bound * i as f64 / steps as f64,339            bound * (i + 1) as f64 / steps as f64,340        );341        if value(lo) <= 0.0 && value(hi) >= 0.0 {342            let (mut lo, mut hi) = (lo, hi);343            for _ in 0..200 {344                let mid = (lo + hi) / 2.0;345                if value(mid) <= 0.0 {346                    lo = mid;347                } else {348                    hi = mid;349                }350            }351            return (lo + hi) / 2.0;352        }353        let mid = (lo + hi) / 2.0;354        if value(mid).abs() < gap {355            gap = value(mid).abs();356            best = mid;357        }358    }359    let mut x = best;360    for _ in 0..100 {361        let h = 1e-7 * x.abs().max(1.0);362        let slope = (value(x + h) - value(x - h)) / (2.0 * h);363        if slope == 0.0 {364            break;365        }366        x -= value(x) / slope;367    }368    if value(x).abs() < 1e-6 * (1.0 + x.abs().powi(poly.len() as i32 - 1)) {369        x370    } else {371        f64::NAN372    }373}374375#[cfg(test)]376mod tests {377    use super::*;378    use crate::classics::{catalan, primes};379380    fn fibonacci(count: usize) -> Vec<i128> {381        let mut out = vec![0i128, 1];382        while out.len() < count {383            out.push(out[out.len() - 1] + out[out.len() - 2]);384        }385        out.truncate(count);386        out387    }388389    #[test]390    fn the_fibonacci_rule_and_its_golden_growth() {391        let fib = fibonacci(30);392        let rule = recurrence(&fib).unwrap();393        assert_eq!(rule, vec![(1, 1), (1, 1)]);394        assert!((growth(&rule) - 1.618_033_988_749_895).abs() < 1e-12);395    }396397    #[test]398    fn a_cubic_polynomial_sequence_needs_order_four() {399        let terms: Vec<i128> = (0..20).map(|n| n * n * (4 * n + 3)).collect();400        let rule = recurrence(&terms).unwrap();401        assert_eq!(rule, vec![(4, 1), (-6, 1), (4, 1), (-1, 1)]);402        assert!((growth(&rule) - 1.0).abs() < 1e-3);403    }404405    #[test]406    fn the_hexagram_rule_grows_by_its_perron_root() {407        let mut terms = vec![1i128, 6];408        while terms.len() < 14 {409            let n = terms.len();410            terms.push(9 * terms[n - 1] - 12 * terms[n - 2]);411        }412        let rule = recurrence(&terms).unwrap();413        assert_eq!(rule, vec![(9, 1), (-12, 1)]);414        assert!((growth(&rule) - (9.0 + 33f64.sqrt()) / 2.0).abs() < 1e-12);415    }416417    #[test]418    fn a_halving_sequence_carries_a_rational_rule() {419        let terms = vec![1024i128, 512, 256, 128, 64, 32, 16, 8, 4, 2, 1];420        assert_eq!(recurrence(&terms), Some(vec![(1, 2)]));421    }422423    #[test]424    fn the_primes_and_the_catalan_numbers_refuse_every_rule() {425        let ps: Vec<i128> = primes(200).iter().map(|&p| p as i128).collect();426        assert_eq!(recurrence(&ps), None);427        let cs: Vec<i128> = catalan(40_000_000).iter().map(|&c| c as i128).collect();428        assert_eq!(recurrence(&cs), None);429    }430431    #[test]432    fn blends_of_c_finite_sequences_stay_c_finite() {433        let fib = fibonacci(40);434        let squared = hadamard(&fib, &fib);435        assert_eq!(recurrence(&squared).unwrap().len(), 3);436        let summed = sigma(&fib);437        assert_eq!(recurrence(&summed).unwrap().len(), 3);438        let paired = decimate(&fib, 2, 0);439        let rule = recurrence(&paired).unwrap();440        assert_eq!(rule, vec![(3, 1), (-1, 1)]);441        let convolved = cauchy(&fib, &fib);442        assert_eq!(recurrence(&convolved).unwrap().len(), 4);443    }444445    #[test]446    fn the_term_ops_keep_their_hand_checked_values() {447        let a = vec![1i128, 2, 3, 4, 5];448        let b = vec![10i128, 20, 30];449        assert_eq!(add(&a, &b), vec![11, 22, 33]);450        assert_eq!(sub(&b, &a), vec![9, 18, 27]);451        assert_eq!(hadamard(&a, &b), vec![10, 40, 90]);452        assert_eq!(cauchy(&a, &b), vec![10, 40, 100]);453        assert_eq!(shift(&a, 2), vec![3, 4, 5]);454        assert_eq!(decimate(&a, 2, 1), vec![2, 4]);455        assert_eq!(delta(&a), vec![1, 1, 1, 1]);456        assert_eq!(sigma(&a), vec![1, 3, 6, 10, 15]);457        assert_eq!(scale(&a, -2), vec![-2, -4, -6, -8, -10]);458    }459460    #[test]461    fn the_zero_sequence_returns_the_empty_rule() {462        assert_eq!(recurrence(&[0, 0, 0, 0, 0, 0]), Some(vec![]));463        assert_eq!(recurrence(&[1, 2]), None);464    }465466    #[test]467    fn a_short_prefix_is_not_enough_evidence() {468        assert_eq!(recurrence(&[1, 2, 4]), None);469        assert_eq!(recurrence(&[1, 2, 4, 8]), Some(vec![(2, 1)]));470    }471472    #[test]473    #[should_panic(expected = "decimate needs a step above zero")]474    fn decimate_refuses_a_zero_step() {475        let _ = decimate(&[1, 2, 3], 0, 0);476    }477}