UNPKG

herta

Version:

Advanced mathematics framework for scientific, engineering, and financial applications

499 lines (416 loc) 13.5 kB
/** * Discrete Mathematics module for herta.js * Provides combinatorial functions and discrete structures */ const arithmetic = require('../core/arithmetic'); const discreteMath = {}; /** * Calculate the binomial coefficient (n choose k) * @param {number} n - Total number of items * @param {number} k - Number of items to choose * @returns {number} - Binomial coefficient (n choose k) */ discreteMath.binomialCoefficient = function (n, k) { if (k < 0 || k > n) return 0; if (k === 0 || k === n) return 1; // Optimize for large values if (k > n - k) { k = n - k; } let result = 1; for (let i = 1; i <= k; i++) { result *= (n - (k - i)); result /= i; } return Math.round(result); // To handle floating point errors }; /** * Calculate the Stirling number of the first kind * @param {number} n - First parameter * @param {number} k - Second parameter * @returns {number} - Stirling number of the first kind s(n,k) */ discreteMath.stirlingFirst = function (n, k) { if (n === 0 && k === 0) return 1; if (n === 0 || k === 0) return 0; if (k === 1) return arithmetic.factorial(n - 1); if (k === n) return 1; // Recurrence relation: s(n,k) = s(n-1,k-1) - (n-1)*s(n-1,k) return this.stirlingFirst(n - 1, k - 1) - (n - 1) * this.stirlingFirst(n - 1, k); }; /** * Calculate the Stirling number of the second kind * @param {number} n - First parameter * @param {number} k - Second parameter * @returns {number} - Stirling number of the second kind S(n,k) */ discreteMath.stirlingSecond = function (n, k) { if (n === 0 && k === 0) return 1; if (n === 0 || k === 0) return 0; if (k === 1 || k === n) return 1; // Recurrence relation: S(n,k) = k*S(n-1,k) + S(n-1,k-1) return k * this.stirlingSecond(n - 1, k) + this.stirlingSecond(n - 1, k - 1); }; /** * Calculate the Bell number (total number of partitions of a set) * @param {number} n - Size of the set * @returns {number} - Bell number */ discreteMath.bellNumber = function (n) { if (n === 0) return 1; // Compute using Stirling numbers of the second kind let sum = 0; for (let k = 0; k <= n; k++) { sum += this.stirlingSecond(n, k); } return sum; }; /** * Calculate the Catalan number * @param {number} n - Parameter for Catalan number * @returns {number} - nth Catalan number */ discreteMath.catalanNumber = function (n) { return this.binomialCoefficient(2 * n, n) / (n + 1); }; /** * Generate all permutations of an array * @param {Array} arr - Input array * @returns {Array} - Array of all permutations */ discreteMath.permutations = function (arr) { const result = []; // Helper function to generate permutations function permute(arr, m = []) { if (arr.length === 0) { result.push(m); } else { for (let i = 0; i < arr.length; i++) { const curr = arr.slice(); const next = curr.splice(i, 1); permute(curr.slice(), m.concat(next)); } } } permute(arr); return result; }; /** * Generate all combinations of k elements from an array * @param {Array} arr - Input array * @param {number} k - Size of each combination * @returns {Array} - Array of all k-combinations */ discreteMath.combinations = function (arr, k) { const result = []; // Helper function to generate combinations function combine(start, combo) { if (combo.length === k) { result.push([...combo]); return; } for (let i = start; i < arr.length; i++) { combo.push(arr[i]); combine(i + 1, combo); combo.pop(); } } combine(0, []); return result; }; /** * Calculate the number of derangements (permutations with no fixed points) * @param {number} n - Number of elements * @returns {number} - Number of derangements */ discreteMath.derangements = function (n) { if (n === 0) return 1; if (n === 1) return 0; // Using the recurrence relation: D(n) = (n-1) * (D(n-1) + D(n-2)) let prev2 = 1; // D(0) let prev1 = 0; // D(1) for (let i = 2; i <= n; i++) { const current = (i - 1) * (prev1 + prev2); prev2 = prev1; prev1 = current; } return prev1; }; /** * Calculate the partition function (number of ways to partition an integer) * @param {number} n - The integer to partition * @returns {number} - Number of partitions */ discreteMath.partitionFunction = function (n) { if (n < 0) return 0; if (n === 0) return 1; // Use dynamic programming const partitions = Array(n + 1).fill(0); partitions[0] = 1; for (let i = 1; i <= n; i++) { for (let j = i; j <= n; j++) { partitions[j] += partitions[j - i]; } } return partitions[n]; }; /** * Generate the power set (set of all subsets) of an array * @param {Array} arr - Input array * @returns {Array} - Power set */ discreteMath.powerSet = function (arr) { const result = [[]]; for (const elem of arr) { const current = [...result]; for (const subset of current) { result.push([...subset, elem]); } } return result; }; /** * Check if a relation is a function * @param {Array} domain - Domain elements * @param {Array} relation - Array of pairs [x, y] representing the relation * @returns {boolean} - Whether the relation is a function */ discreteMath.isFunction = function (domain, relation) { const mapped = new Set(); for (const [x, y] of relation) { if (mapped.has(x)) { return false; // A function maps each element to exactly one element } mapped.add(x); } // Check if all domain elements are mapped return domain.every((x) => mapped.has(x)); }; /** * Check if a function is injective (one-to-one) * @param {Array} relation - Array of pairs [x, y] representing the function * @returns {boolean} - Whether the function is injective */ discreteMath.isInjective = function (relation) { const rangeValues = new Set(); for (const [x, y] of relation) { if (rangeValues.has(y)) { return false; // Not injective if two domain elements map to the same range element } rangeValues.add(y); } return true; }; /** * Check if a function is surjective (onto) * @param {Array} codomain - Codomain elements * @param {Array} relation - Array of pairs [x, y] representing the function * @returns {boolean} - Whether the function is surjective */ discreteMath.isSurjective = function (codomain, relation) { const rangeValues = new Set(relation.map((pair) => pair[1])); // Function is surjective if every element in the codomain is mapped to return codomain.every((y) => rangeValues.has(y)); }; /** * Check if a function is bijective (one-to-one and onto) * @param {Array} domain - Domain elements * @param {Array} codomain - Codomain elements * @param {Array} relation - Array of pairs [x, y] representing the function * @returns {boolean} - Whether the function is bijective */ discreteMath.isBijective = function (domain, codomain, relation) { return this.isFunction(domain, relation) && this.isInjective(relation) && this.isSurjective(codomain, relation); }; /** * Generate the composition of two functions * @param {Array} f - First function as array of pairs [x, f(x)] * @param {Array} g - Second function as array of pairs [x, g(x)] * @returns {Array} - Composition g ∘ f as array of pairs [x, g(f(x))] */ discreteMath.composeFunction = function (f, g) { const composition = []; const gMap = new Map(g); for (const [x, fx] of f) { if (gMap.has(fx)) { composition.push([x, gMap.get(fx)]); } } return composition; }; /** * Generate the inverse of a function (if it exists) * @param {Array} f - Function as array of pairs [x, f(x)] * @returns {Array|null} - Inverse function or null if no inverse exists */ discreteMath.inverseFunctions = function (f) { if (!this.isInjective(f)) { return null; // Function must be injective to have an inverse } return f.map(([x, y]) => [y, x]); }; /** * Check if a relation is reflexive * @param {Array} set - The set of elements * @param {Array} relation - Array of pairs [a, b] in the relation * @returns {boolean} - Whether the relation is reflexive */ discreteMath.isReflexive = function (set, relation) { const relationSet = new Set(relation.map((pair) => JSON.stringify(pair))); // Check if (a, a) is in the relation for every a in the set return set.every((a) => relationSet.has(JSON.stringify([a, a]))); }; /** * Check if a relation is symmetric * @param {Array} relation - Array of pairs [a, b] in the relation * @returns {boolean} - Whether the relation is symmetric */ discreteMath.isSymmetric = function (relation) { const relationSet = new Set(relation.map((pair) => JSON.stringify(pair))); // Check if (b, a) is in the relation whenever (a, b) is in the relation return relation.every(([a, b]) => a === b || relationSet.has(JSON.stringify([b, a]))); }; /** * Check if a relation is transitive * @param {Array} relation - Array of pairs [a, b] in the relation * @returns {boolean} - Whether the relation is transitive */ discreteMath.isTransitive = function (relation) { const relationMap = new Map(); // Build map for efficient lookup for (const [a, b] of relation) { if (!relationMap.has(a)) { relationMap.set(a, new Set()); } relationMap.get(a).add(b); } // Check transitivity for (const [a, bSet] of relationMap.entries()) { for (const b of bSet) { if (relationMap.has(b)) { for (const c of relationMap.get(b)) { if (!relationMap.has(a) || !relationMap.get(a).has(c)) { return false; } } } } } return true; }; /** * Check if a relation is an equivalence relation * @param {Array} set - The set of elements * @param {Array} relation - Array of pairs [a, b] in the relation * @returns {boolean} - Whether the relation is an equivalence relation */ discreteMath.isEquivalenceRelation = function (set, relation) { return this.isReflexive(set, relation) && this.isSymmetric(relation) && this.isTransitive(relation); }; /** * Generate equivalence classes from an equivalence relation * @param {Array} set - The set of elements * @param {Array} relation - Array of pairs [a, b] in the equivalence relation * @returns {Array} - Array of equivalence classes (each class is an array of elements) */ discreteMath.equivalenceClasses = function (set, relation) { // Check if the relation is an equivalence relation if (!this.isEquivalenceRelation(set, relation)) { throw new Error('Relation is not an equivalence relation'); } const classes = []; const classified = new Set(); // Build relation map for efficient lookup const relationMap = new Map(); for (const [a, b] of relation) { if (!relationMap.has(a)) { relationMap.set(a, new Set()); } relationMap.get(a).add(b); } // Find equivalence classes for (const a of set) { if (classified.has(a)) continue; const eqClass = [a]; classified.add(a); // Add all elements equivalent to a for (const b of set) { if (a !== b && !classified.has(b) && relationMap.has(a) && relationMap.get(a).has(b)) { eqClass.push(b); classified.add(b); } } classes.push(eqClass); } return classes; }; /** * Solve a recurrence relation of the form a(n) = c1*a(n-1) + c2*a(n-2) + ... + ck*a(n-k) + F(n) * This implementation handles linear homogeneous recurrence relations (F(n) = 0) * @param {Array} coefficients - Coefficients [c1, c2, ..., ck] * @param {Array} initialValues - Initial values [a(0), a(1), ..., a(k-1)] * @param {number} n - Term to compute * @returns {number} - Value of a(n) */ discreteMath.linearRecurrence = function (coefficients, initialValues, n) { const k = coefficients.length; if (initialValues.length !== k) { throw new Error('Number of initial values must match number of coefficients'); } if (n < k) { return initialValues[n]; } // Matrix method for linear recurrences const companion = Array(k).fill().map(() => Array(k).fill(0)); // Set the coefficients in the first row for (let j = 0; j < k; j++) { companion[0][j] = coefficients[j]; } // Set the 1's on the subdiagonal for (let i = 1; i < k; i++) { companion[i][i - 1] = 1; } // Calculate companion^(n-k+1) function matrixPower(matrix, power) { const size = matrix.length; // Identity matrix let result = Array(size).fill().map((_, i) => Array(size).fill().map((_, j) => (i === j ? 1 : 0))); // Binary exponentiation let base = matrix.map((row) => [...row]); while (power > 0) { if (power % 2 === 1) { result = matrixMultiply(result, base); } base = matrixMultiply(base, base); power = Math.floor(power / 2); } return result; } function matrixMultiply(a, b) { const m = a.length; const n = b[0].length; const p = b.length; const result = Array(m).fill().map(() => Array(n).fill(0)); for (let i = 0; i < m; i++) { for (let j = 0; j < n; j++) { for (let k = 0; k < p; k++) { result[i][j] += a[i][k] * b[k][j]; } } } return result; } const poweredMatrix = matrixPower(companion, n - k + 1); // Calculate a(n) using matrix-vector multiplication let result = 0; for (let j = 0; j < k; j++) { result += poweredMatrix[0][j] * initialValues[k - j - 1]; } return result; }; module.exports = discreteMath;