UNPKG

herta

Version:

Advanced mathematics framework for scientific, engineering, and financial applications

620 lines (523 loc) 15.1 kB
/** * numberTheory.js * Advanced number theory operations for Herta.js */ /** * Generate prime numbers up to a given limit using the Sieve of Eratosthenes * @param {Number} limit - Upper limit for prime generation * @returns {Array} - Array of prime numbers */ function generatePrimes(limit) { const sieve = Array(limit + 1).fill(true); sieve[0] = sieve[1] = false; for (let i = 2; i * i <= limit; i++) { if (sieve[i]) { for (let j = i * i; j <= limit; j += i) { sieve[j] = false; } } } const primes = []; for (let i = 2; i <= limit; i++) { if (sieve[i]) primes.push(i); } return primes; } /** * Factorize a number into its prime factors * @param {Number} n - Number to factorize * @returns {Array} - Array of prime factors */ function factorize(n) { const factors = []; let divisor = 2; while (n > 1) { while (n % divisor === 0) { factors.push(divisor); n /= divisor; } divisor++; // Optimization for large prime factors if (divisor * divisor > n && n > 1) { factors.push(n); break; } } return factors; } /** * Calculate the greatest common divisor (GCD) of two numbers * @param {Number} a - First number * @param {Number} b - Second number * @returns {Number} - Greatest common divisor */ function gcd(a, b) { if (b === 0) return Math.abs(a); return gcd(b, a % b); } /** * Calculate the least common multiple (LCM) of two numbers * @param {Number} a - First number * @param {Number} b - Second number * @returns {Number} - Least common multiple */ function lcm(a, b) { return Math.abs(a * b) / gcd(a, b); } /** * Check if a number is prime * @param {Number} n - Number to check * @returns {Boolean} - True if prime, false otherwise */ function isPrime(n) { if (n <= 1) return false; if (n <= 3) return true; if (n % 2 === 0 || n % 3 === 0) return false; for (let i = 5; i * i <= n; i += 6) { if (n % i === 0 || n % (i + 2) === 0) return false; } return true; } /** * Calculate modular exponentiation (a^b mod n) efficiently * @param {Number} base - Base value * @param {Number} exponent - Exponent value * @param {Number} modulus - Modulus value * @returns {Number} - Result of modular exponentiation */ function modPow(base, exponent, modulus) { if (modulus === 1) return 0; let result = 1; base %= modulus; while (exponent > 0) { if (exponent % 2 === 1) { result = (result * base) % modulus; } exponent = Math.floor(exponent / 2); base = (base * base) % modulus; } return result; } /** * Calculate Euler's totient function φ(n) - counts numbers up to n that are coprime to n * @param {Number} n - Input number * @returns {Number} - Value of Euler's totient function */ function eulerTotient(n) { let result = n; // Initialize result as n // Consider all prime factors of n and subtract their // multiples from result for (let p = 2; p * p <= n; p++) { // Check if p is a prime factor if (n % p === 0) { // If yes, then update n and result while (n % p === 0) { n = Math.floor(n / p); } result -= Math.floor(result / p); } } // If n has a prime factor greater than sqrt(n) // (There can be at-most one such prime factor) if (n > 1) { result -= Math.floor(result / n); } return result; } /** * Extended Euclidean Algorithm to find Bézout coefficients and GCD * @param {Number} a - First number * @param {Number} b - Second number * @returns {Array} - Array [gcd, x, y] where ax + by = gcd(a,b) */ function extendedGcd(a, b) { if (b === 0) { return [a, 1, 0]; } const [d, x, y] = extendedGcd(b, a % b); return [d, y, x - Math.floor(a / b) * y]; } /** * Solve linear congruence ax ≡ b (mod m) * @param {Number} a - Coefficient * @param {Number} b - Right-hand side * @param {Number} m - Modulus * @returns {Array} - Array of solutions modulo m, or empty array if no solution */ function solveLinearCongruence(a, b, m) { // Ensure positive values a = ((a % m) + m) % m; b = ((b % m) + m) % m; const [g, x, _] = extendedGcd(a, m); if (b % g !== 0) { return []; // No solution } let x0 = (x * (b / g)) % m; x0 = ((x0 % m) + m) % m; const solutions = []; for (let i = 0; i < g; i++) { solutions.push((x0 + i * (m / g)) % m); } return solutions; } /** * Implement Chinese Remainder Theorem to solve system of congruences * @param {Array} remainders - Array of remainders * @param {Array} moduli - Array of moduli * @returns {Number} - Solution x such that x ≡ remainders[i] (mod moduli[i]) for all i */ function chineseRemainderTheorem(remainders, moduli) { if (remainders.length !== moduli.length) { throw new Error('Number of remainders must equal number of moduli'); } let result = 0; const product = moduli.reduce((a, b) => a * b, 1); for (let i = 0; i < moduli.length; i++) { const m = moduli[i]; const pp = Math.floor(product / m); const [_, inv, __] = extendedGcd(pp, m); result += remainders[i] * pp * inv; } return ((result % product) + product) % product; } /** * Calculate Legendre symbol (a/p) - determines whether a is a quadratic residue modulo p * @param {Number} a - Number to check * @param {Number} p - Prime modulus * @returns {Number} - 1 if a is a quadratic residue modulo p, -1 if not, 0 if a is divisible by p */ function legendreSymbol(a, p) { if (p <= 1 || p % 2 === 0) { throw new Error('p must be an odd prime'); } a = ((a % p) + p) % p; if (a === 0) return 0; if (a === 1) return 1; let result; if (a % 2 === 0) { result = legendreSymbol(a / 2, p); if (p % 8 === 3 || p % 8 === 5) result = -result; } else { result = legendreSymbol(p % a, a); if (a % 4 === 3 && p % 4 === 3) result = -result; } return result; } /** * Find all quadratic residues modulo p * @param {Number} p - Modulus * @returns {Array} - Array of quadratic residues */ function quadraticResidues(p) { const residues = new Set(); for (let i = 1; i < p; i++) { residues.add((i * i) % p); } return Array.from(residues).sort((a, b) => a - b); } /** * Convert a number to continued fraction representation * @param {Number} num - Numerator * @param {Number} den - Denominator * @returns {Array} - Array of coefficients in the continued fraction */ function toContinuedFraction(num, den) { const result = []; while (den > 0) { result.push(Math.floor(num / den)); const temp = num; num = den; den = temp % den; } return result; } /** * Convert a continued fraction back to a rational number * @param {Array} cf - Continued fraction coefficients * @returns {Array} - [numerator, denominator] */ function fromContinuedFraction(cf) { let num = 1; let den = 0; for (let i = cf.length - 1; i >= 0; i--) { const temp = num; num = cf[i] * num + den; den = temp; } return [num, den]; } /** * Calculate the convergents of a continued fraction * @param {Array} cf - Continued fraction coefficients * @returns {Array} - Array of convergents as [num, den] pairs */ function continuedFractionConvergents(cf) { const convergents = []; let p = [1, 0]; let q = [0, 1]; for (let i = 0; i < cf.length; i++) { const nextP = cf[i] * p[0] + p[1]; const nextQ = cf[i] * q[0] + q[1]; p = [nextP, p[0]]; q = [nextQ, q[0]]; convergents.push([p[0], q[0]]); } return convergents; } /** * Primality test using Miller-Rabin probabilistic algorithm * @param {Number} n - Number to test * @param {Number} k - Number of iterations for accuracy (higher is more accurate) * @returns {Boolean} - True if probably prime, false if definitely composite */ function isProbablePrime(n, k = 5) { if (n <= 1) return false; if (n <= 3) return true; if (n % 2 === 0) return false; // Write n as 2^r * d + 1 let r = 0; let d = n - 1; while (d % 2 === 0) { d /= 2; r++; } // Witness loop const witnesses = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37]; // Use a limited number of witnesses for practicality const witnesses_to_use = witnesses.filter((w) => w < n).slice(0, k); // If we've exhausted our witnesses list, fall back to random witnesses if (witnesses_to_use.length < k) { while (witnesses_to_use.length < k) { const a = 2 + Math.floor(Math.random() * (n - 3)); if (!witnesses_to_use.includes(a)) { witnesses_to_use.push(a); } } } // For each witness for (const a of witnesses_to_use) { // Skip if a is not in range [2, n-2] if (a < 2 || a > n - 2) continue; let x = modPow(a, d, n); if (x === 1 || x === n - 1) continue; let continueMainLoop = false; for (let i = 0; i < r - 1; i++) { x = modPow(x, 2, n); if (x === n - 1) { continueMainLoop = true; break; } } if (continueMainLoop) continue; return false; // Definitely composite } return true; // Probably prime } /** * Pollard's rho algorithm for integer factorization * @param {Number} n - Number to factorize * @returns {Number} - A non-trivial factor of n, or n if none found */ function pollardRho(n) { if (n === 1) return 1; if (n % 2 === 0) return 2; if (isPrime(n)) return n; // g(x) = (x^2 + c) % n, with c = 1 const g = (x) => (x * x + 1) % n; let x = 2; let y = 2; let d = 1; while (d === 1) { x = g(x); y = g(g(y)); d = gcd(Math.abs(x - y), n); } if (d === n) return n; // Failure, but n might be prime return d; } /** * Fast factorization using trial division and Pollard's rho * @param {Number} n - Number to factorize * @returns {Array} - Array of prime factors with repetition */ function fastFactorize(n) { if (n <= 1) return [n]; const factors = []; // Trial division for small primes const smallPrimes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]; for (const p of smallPrimes) { while (n % p === 0) { factors.push(p); n /= p; } } if (n === 1) return factors; // If n is prime, we're done if (isProbablePrime(n)) { factors.push(n); return factors; } // Otherwise, use Pollard's rho to find a factor const stack = [n]; while (stack.length > 0) { const num = stack.pop(); if (isProbablePrime(num)) { factors.push(num); continue; } const factor = pollardRho(num); if (factor === num) { // If pollardRho fails, fall back to trial division factors.push(num); } else { stack.push(factor); stack.push(num / factor); } } return factors.sort((a, b) => a - b); } /** * Solve a linear Diophantine equation ax + by = c * @param {Number} a - Coefficient of x * @param {Number} b - Coefficient of y * @param {Number} c - Right-hand side constant * @returns {Object} - Object with solution information */ function solveDiophantine(a, b, c) { // Step 1: Find gcd(a, b) using extended Euclidean algorithm const [g, x0, y0] = extendedGcd(Math.abs(a), Math.abs(b)); // Make sure coefficients have correct sign const x0_signed = a < 0 ? -x0 : x0; const y0_signed = b < 0 ? -y0 : y0; // Step 2: Check if c is divisible by gcd(a,b) if (c % g !== 0) { return { solvable: false, reason: `${c} is not divisible by gcd(${a},${b}) = ${g}` }; } // Step 3: Compute the particular solution const factor = c / g; const x = x0_signed * factor; const y = y0_signed * factor; return { solvable: true, particular: { x, y }, general: { x: `${x} + ${b / g}*t`, y: `${y} - ${a / g}*t` }, parameterized: (t) => ({ x: x + (b / g) * t, y: y - (a / g) * t }) }; } /** * Check if a number is a perfect power (n = a^b for some integers a, b with b > 1) * @param {Number} n - Number to check * @returns {Object} - If it's a perfect power, returns {base: a, exponent: b}, otherwise null */ function isPerfectPower(n) { if (n < 2) return null; const logn = Math.log2(n); for (let b = 2; b <= logn; b++) { // Calculate a = n^(1/b) const a = Math.round(n ** (1 / b)); if (a ** b === n) { return { base: a, exponent: b }; } } return null; } /** * Calculate the Möbius function μ(n) * @param {Number} n - Input number * @returns {Number} - μ(n): 1 if n is square-free with even number of prime factors, * -1 if n is square-free with odd number of prime factors, * 0 if n has a squared prime factor */ function mobiusFunction(n) { if (n <= 0) throw new Error('Input must be a positive integer'); if (n === 1) return 1; let factors = 0; let squareFree = true; // Get prime factorization const primeFactors = factorize(n); // Count unique prime factors and check for squares let lastFactor = primeFactors[0]; let count = 1; for (let i = 1; i < primeFactors.length; i++) { if (primeFactors[i] === lastFactor) { count++; if (count === 2) { squareFree = false; break; } } else { lastFactor = primeFactors[i]; count = 1; factors++; } } if (!squareFree) return 0; if (factors % 2 === 0) return 1; return -1; } /** * Calculate the sum of the kth powers of divisors of n * @param {Number} n - Input number * @param {Number} k - Power * @returns {Number} - Sum of the kth powers of divisors */ function divisorSum(n, k = 1) { if (n <= 0) throw new Error('Input must be a positive integer'); let sum = 0; for (let i = 1; i <= Math.sqrt(n); i++) { if (n % i === 0) { sum += i ** k; if (i !== n / i) { sum += (n / i) ** k; } } } return sum; } /** * Find all primitive Pythagorean triples with hypotenuse less than a given limit * @param {Number} limit - Maximum value for the hypotenuse * @returns {Array} - Array of Pythagorean triples [a, b, c] where a^2 + b^2 = c^2 */ function primitivePythagoreanTriples(limit) { const triples = []; // Euclid's formula: For coprime m, n with m > n > 0 and one even, one odd: // a = m^2 - n^2, b = 2mn, c = m^2 + n^2 gives a primitive Pythagorean triple for (let m = 2; m * m <= limit; m++) { const start = m % 2 === 0 ? 1 : 2; for (let n = start; n < m; n += 2) { if (gcd(m, n) !== 1) continue; const a = m * m - n * n; const b = 2 * m * n; const c = m * m + n * n; if (c > limit) break; triples.push([Math.min(a, b), Math.max(a, b), c]); } } return triples.sort((a, b) => a[2] - b[2]); } // Export the number theory functions module.exports = { generatePrimes, factorize, fastFactorize, gcd, lcm, isPrime, isProbablePrime, modPow, eulerTotient, extendedGcd, solveLinearCongruence, chineseRemainderTheorem, legendreSymbol, quadraticResidues, toContinuedFraction, fromContinuedFraction, continuedFractionConvergents, pollardRho, solveDiophantine, isPerfectPower, mobiusFunction, divisorSum, primitivePythagoreanTriples };