Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Number Theory and Combinatorics

Integer functions live in symplex::ntheory, symplex::diophantine and symplex::combinatorics; they work on anything Into<BigInt> (i64, u64, BigInt, …) and return BigInt/Ratio<BigInt>/Option. Symbolic counterparts (n.fibonacci(), n.factorial(), n.binomial(&k), n.bell(), …) live on Ex and evaluate when the argument is a concrete integer.

Primality and factorization

0.2 replaces trial division with Pollard–Brent rho (Montgomery u128 arithmetic) plus ECM for BigInt, and deterministic Miller–Rabin with the BPSW test (no Carmichael false positives).

use num_bigint::BigInt;
use symplex::ntheory::*;

fn main() {
    println!("{}", isprime(561));                                   // false (Carmichael number)
    let m127 = BigInt::parse_bytes(b"170141183460469231731687303715884105727", 10).unwrap();
    println!("{}", isprime(m127));                                  // true, well under a millisecond
    println!("{:?}", factorint(1_099_532_599_387u64));              // [(1048583, 1), (1048589, 1)]
    println!("{:?}", factorint(BigInt::from(2u128.pow(64) + 1)));   // [(274177, 1), (67280421310721, 1)]
    println!("{:?}", primepi(1_000_000));                           // Some(78498)
    println!("{} {:?}", nextprime(100), prevprime(100));            // 101 Some(97)
    println!("{:?}", divisors(28));                                 // [1, 2, 4, 7, 14, 28]
    println!("{} {} {}", totient(36), mobius(30), carmichael_lambda(8));   // 12 -1 2
    println!("{:?}", perfect_power(1024));                          // Some((2, 10))
}

Modular arithmetic

use symplex::ntheory::*;

fn main() {
    println!("{:?}", mod_inverse(17, 43));               // Some(38)
    println!("{}", mod_pow(3, 200, 1_000_003));
    println!("{:?}", crt_i64(&[2, 3, 2], &[3, 5, 7]));   // Some(23)
    println!("{:?} {:?}", sqrt_mod(2, 7), sqrt_mod_all(2, 7));   // Some(3) [3, 4]
    println!("{:?}", sqrt_mod(3, 7));                    // None — not a quadratic residue
    println!("{:?}", sqrt_mod_all(1, 15));               // [1, 4, 11, 14]
    println!("{:?}", discrete_log(3, 13, 17));           // Some(4): 3⁴ ≡ 13 (mod 17)
    println!("{:?}", primitive_root(17));                // Some(3)
    println!("{:?}", multiplicative_order(2, 7));        // Some(3)
    println!("{} {:?} {}", legendre_symbol(2, 7), jacobi_symbol(1001, 9907), kronecker_symbol(3, 8));   // 1 Ok(-1) -1
}

Continued fractions and Egyptian fractions

use num_bigint::BigInt;
use num_rational::Ratio;
use symplex::ntheory::*;

fn main() {
    let r = Ratio::new(BigInt::from(415), BigInt::from(93));
    println!("{:?}", continued_fraction(&r));                  // [4, 2, 6, 7]
    let cf = continued_fraction_periodic(23).unwrap();         // √23 = [4; (1, 3, 1, 8)]
    println!("{:?} {:?}", cf.pre_period, cf.period);           // [4] [1, 3, 1, 8]
    let terms: Vec<BigInt> = [3, 7, 15, 1].iter().map(|&k| BigInt::from(k)).collect();
    println!("{:?}", continued_fraction_convergents(&terms));  // 3, 22/7, 333/106, 355/113
    println!("{:?}", egyptian_fraction(&Ratio::new(BigInt::from(4), BigInt::from(13))));   // Some([4, 18, 468])
}

Diophantine equations

use symplex::diophantine::*;

fn main() {
    let sol = linear_diophantine(3, 5, 1).unwrap();      // x = 2 + 5k, y = −1 − 3k
    println!("{} {} {} {}", sol.x, sol.y, sol.x_step, sol.y_step);   // 2 -1 5 -3
    println!("{:?}", pell(61));                          // Some((1766319049, 226153980))
    println!("{:?}", pell_solutions(2, 4));              // [(3, 2), (17, 12), (99, 70), (577, 408)]
    println!("{:?}", pell_negative(5));                  // x² − 5y² = −1
    println!("{:?}", sum_of_two_squares(65));            // Some((4, 7))
    println!("{:?}", sum_of_two_squares(2021));          // None (43·47, both ≡ 3 mod 4)
    println!("{:?}", sum_of_four_squares(7));
    println!("{:?}", pythagorean_triples(30));           // primitive triples with c ≤ 30
    println!("{:?}", frobenius_number(&[6, 9, 20]));     // Some(43)  (Chicken McNugget)
}

Sequences and combinatorics

use symplex::combinatorics::*;
use symplex::ntheory;
use symplex::prelude::*;

fn main() {
    println!("{}", ntheory::fibonacci(100));                  // 354224848179261915075
    println!("{:?}", ntheory::bernoulli(12));                 // Some(-691/2730)
    println!("{:?}", ntheory::euler_number(10));              // Some(-50521)
    println!("{:?}", ntheory::harmonic(10));                  // Some(7381/2520)
    println!("{:?}", stirling2(10, 4));                       // Some(34105)
    println!("{:?}", stirling1(5, 2));                        // signed Stirling numbers of the first kind
    println!("{:?}", bell(10));                               // Some(115975)
    println!("{:?}", catalan(10));                            // Some(16796)
    println!("{:?}", derangements(10));                       // Some(1334961)
    println!("{:?}", partition_count(100));                   // Some(190569292)
    println!("{:?}", partitions(5).collect::<Vec<_>>());      // all partitions of 5
    println!("{:?}", multinomial(6, &[2, 2, 2]));             // Some(90)

    // Symbolic: stays a node until the argument is concrete
    let ctx = Context::new();
    symplex::syms!(ctx; n);
    println!("{} {}", n.fibonacci(), ctx.int(30).fibonacci().eval());     // fibonacci(n) 832040
    println!("{}", ctx.int(10).bell().eval());                             // 115975
}

Polynomial factoring and algebra

Factoring over ℤ (Berlekamp–Zassenhaus), multivariate factoring, resultants, discriminants, square-free decomposition, root isolation and the rest of the polynomial toolbox are covered in Algebra.

See cargo run --example factoring_and_ntheory, number_theory and crypto_rsa.

More number theory and discrete transforms (0.9.1)

0.9.1 fills in the rest of SymPy’s ntheory residue toolbox and adds an exact symplex::discrete module.

Higher power residues and polynomial congruences

nthroot_mod(a, n, m, all_roots) solves xⁿ ≡ a (mod m) for any modulus: m is factored, each prime is handled with Johnston’s generalised q-th root algorithm (a primitive root plus discrete logarithms only inside the Sylow subgroups for the primes dividing gcd(n, p−1), so p may be huge as long as those primes are moderate), roots are Hensel-lifted to prime powers and combined by CRT. n = 2 is sqrt_mod_all. The result is None when there is no root, otherwise the sorted roots (or just the smallest one).

use num_bigint::BigInt;
use symplex::ntheory::*;

fn main() {
    println!("{:?}", nthroot_mod(11, 4, 19, true));          // Some([8, 11])          x⁴ ≡ 11 (mod 19)
    println!("{:?}", nthroot_mod(68, 3, 109, false));        // Some([23])
    println!("{:?}", nthroot_mod(2, 3, 7, true));            // None — 2 is not a cube mod 7
    println!("{:?}", nthroot_mod(16, 4, 35, true));          // Some([2, 9, 12, 16, 19, 23, 26, 33])
    let m127 = (BigInt::from(1) << 127) - 1;
    println!("{:?}", nthroot_mod(8, 3, m127, false));        // Some([2])   cube roots modulo 2¹²⁷ − 1

    println!("{:?}", quadratic_residues(7));                 // [0, 1, 2, 4]
    println!("{} {}", is_nthpow_residue(2, 4, 7), is_nthpow_residue(2, 3, 7));   // true false

    // Roots of x⁶ − 2x⁵ − 35 modulo 6125 = 5³·7² (coefficients highest degree first)
    let f: Vec<BigInt> = [1, -2, 0, 0, 0, 0, -35].iter().map(|&c| BigInt::from(c)).collect();
    println!("{:?}", polynomial_congruence(&f, 6125));       // [3257]
    let g: Vec<BigInt> = [1, 0, 0, -3, 5].iter().map(|&c| BigInt::from(c)).collect();
    println!("{:?}", polynomial_congruence(&g, 1_000_003));  // [357940, 847957]  (Cantor–Zassenhaus mod a large prime)
}

polynomial_congruence solves linear and quadratic congruences and monic binomials xⁿ − a for any factorable modulus; for other polynomials it finds the roots modulo each prime p | m (brute force for p ≤ 2¹⁶, gcd(f, xᵖ − x) plus Cantor–Zassenhaus splitting for 2¹⁶ < p < 2⁶³), Hensel-lifts them and combines them by CRT. A prime factor p ≥ 2⁶³ in that general case is not supported and gives an empty result.

Arithmetic functions

use symplex::ntheory::*;

fn main() {
    println!("{} {} {}", multiplicity(2, 40), primenu(72), primeomega(72));   // 3 2 5
    println!("{} {}", primorial(5), primorial_up_to(10));                     // 2310 210
    println!("{} {}", is_carmichael(561), is_carmichael(563));                // true false
    println!("{}", is_amicable(220, 284));                                    // true
    println!("{:?}", binomial_coefficients_list(4));                          // [1, 4, 6, 4, 1]
    println!("{:?}", binomial_coefficients(3));                               // [((0, 3), 1), ((1, 2), 3), ((2, 1), 3), ((3, 0), 1)]
}

Continued fraction reduction

continued_fraction_reduce is the inverse of continued_fraction: a finite [a₀; a₁, …] back to a rational. The PeriodicContinuedFraction { pre_period, period } returned by continued_fraction_periodic is reduced by continued_fraction_reduce_periodic to a QuadraticSurd { p, q, d } meaning (p + √d)/q (q may be negative — that is how a negative radical coefficient is encoded); continued_fraction_reduce_periodic_ex builds the same value as an Ex, which canonicalises it.

use num_bigint::BigInt;
use symplex::ntheory::*;
use symplex::prelude::*;

fn main() {
    let cf: Vec<BigInt> = [4, 2, 6, 7].iter().map(|&t| BigInt::from(t)).collect();
    println!("{:?}", continued_fraction_reduce(&cf));                        // Some(415/93)

    let cf = continued_fraction_periodic(23).unwrap();                       // [4; (1, 3, 1, 8)]
    let surd = continued_fraction_reduce_periodic(&cf.pre_period, &cf.period).unwrap();
    println!("({} + √{})/{}", surd.p, surd.d, surd.q);                       // (0 + √23)/1  = √23
    let one: Vec<BigInt> = vec![BigInt::from(1)];
    let phi = continued_fraction_reduce_periodic(&[], &one).unwrap();
    println!("({} + √{})/{}", phi.p, phi.d, phi.q);                          // (1 + √5)/2

    let ctx = Context::new();
    let pre: Vec<BigInt> = [1, 2, 3].iter().map(|&t| BigInt::from(t)).collect();
    let per: Vec<BigInt> = [4, 5].iter().map(|&t| BigInt::from(t)).collect();
    println!("{}", continued_fraction_reduce_periodic_ex(&ctx, &pre, &per).unwrap());   // -1/52*sqrt(30) + 20/13  = (80 − √30)/52
}

Discrete transforms (symplex::discrete)

Everything in symplex::discrete is exact: sequences are Ratio<BigInt> (or BigInt residues for the NTT). There is deliberately no floating-point FFT and no symbolic DFT over Ex roots of unity — convolution is exact polynomial multiplication, and convolution_ex does the same on symbolic Ex coefficients. Power-of-two transforms zero-pad their input like SymPy.

use num_bigint::BigInt;
use num_rational::Ratio;
use symplex::discrete::*;

fn main() {
    let q = |v: &[i64]| -> Vec<Ratio<BigInt>> { v.iter().map(|&t| Ratio::from_integer(BigInt::from(t))).collect() };
    let b = |v: &[i64]| -> Vec<BigInt> { v.iter().map(|&t| BigInt::from(t)).collect() };

    println!("{:?}", convolution(&q(&[1, 2, 3]), &q(&[4, 5, 6])));            // [4, 13, 28, 27, 18]
    println!("{:?}", convolution_cyclic(&q(&[1, 2, 3]), &q(&[4, 5, 6]), 3));  // [31, 31, 28]
    println!("{:?}", convolution_subset(&q(&[1, 2, 3, 4]), &q(&[5, 6, 7, 8])));   // [5, 16, 22, 60]

    // Number-theoretic transform modulo 998244353 = 119·2²³ + 1 (root 3, as in SymPy)
    let t = ntt(&b(&[1, 2, 3, 4]), 998_244_353).unwrap();
    println!("{:?}", t);                                                     // [10, 173167434, 998244351, 825076915]
    println!("{:?}", intt(&t, 998_244_353).unwrap());                        // [1, 2, 3, 4]
    println!("{:?}", convolution_ntt(&b(&[1, 2, 3]), &b(&[4, 5, 6]), 998_244_353).unwrap());   // [4, 13, 28, 27, 18]
    println!("{}", ntt(&b(&[1, 2, 3, 4]), 7).is_err());                     // true — 4 ∤ 7 − 1

    println!("{:?}", fwht(&q(&[1, 2, 3, 4])));                               // [10, -2, -4, 0]
    println!("{:?}", ifwht(&q(&[10, -2, -4, 0])));                           // [1, 2, 3, 4]
    println!("{:?}", mobius_transform(&q(&[1, 2, 3, 4])));                   // [1, 3, 4, 10]   subset sums
    println!("{:?}", inverse_mobius_transform(&q(&[1, 3, 4, 10])));          // [1, 2, 3, 4]
    println!("{:?}", mobius_transform_superset(&q(&[1, 2, 3, 4])));          // [10, 6, 7, 4]   superset sums
}