* Solve a linear system by fraction-free Gaussian elimination in EXACT integer * arithmetic (`bigint`). The matrix is an augmented matrix [A|b] with * dimensions rows × (numVars+1) and safe-integer entries. Returns reduced * [numerator, denominator] `bigint` pairs per unknown, or null if inconsis
( matrix: number[][], numVars: number )
| 851 | } |
| 852 | |
| 853 | /** |
| 854 | * Walk a Multiply/Power tree and collect factors that contain the variable. |
| 855 | * Numeric constants are ignored since they don't contribute variable-containing factors. |
| 856 | * Identical factors (by .isSame()) are merged with accumulated multiplicities. |
| 857 | */ |
| 858 | function collectFactors(expr: Expression, variable: string): FactorInfo[] { |
| 859 | const rawFactors: FactorInfo[] = []; |
| 860 | collectFactorsRaw(expr, variable, rawFactors); |
| 861 | |
| 862 | // Merge identical factors |
| 863 | const merged: FactorInfo[] = []; |
| 864 | for (const f of rawFactors) { |
| 865 | let found = false; |
| 866 | for (const m of merged) { |
| 867 | if (m.factor.isSame(f.factor)) { |
| 868 | m.multiplicity += f.multiplicity; |
| 869 | found = true; |
| 870 | break; |
| 871 | } |
| 872 | } |
| 873 | if (!found) merged.push({ ...f }); |
| 874 | } |
| 875 | |
| 876 | return merged; |
| 877 | } |
| 878 | |
| 879 | /** Recursively collect raw factors without merging */ |
| 880 | function collectFactorsRaw( |
| 881 | expr: Expression, |
| 882 | variable: string, |
| 883 | result: FactorInfo[] |
| 884 | ): void { |
| 885 | if (isFunction(expr, 'Multiply')) { |
| 886 | for (const op of expr.ops) { |
| 887 | collectFactorsRaw(op, variable, result); |
| 888 | } |
| 889 | return; |
| 890 | } |
| 891 | |
| 892 | if (isFunction(expr, 'Power')) { |
| 893 | const base = expr.op1; |
| 894 | const exp = asSmallInteger(expr.op2); |
| 895 | if (exp !== null && exp > 0 && base.has(variable)) { |
| 896 | const deg = polynomialDegree(base, variable); |
| 897 | result.push({ factor: base, multiplicity: exp, degree: deg }); |
| 898 | return; |
| 899 | } |
| 900 | // If the base doesn't contain the variable or exponent is not a positive integer, |
| 901 | // treat as a numeric constant |
| 902 | if (!expr.has(variable)) return; |
| 903 | // Non-integer exponent with variable — shouldn't happen for polynomials |
| 904 | const deg = polynomialDegree(expr, variable); |
| 905 | result.push({ factor: expr, multiplicity: 1, degree: deg }); |
| 906 | return; |
| 907 | } |
| 908 | |
| 909 | // Plain expression |
| 910 | if (!expr.has(variable)) return; // Numeric constant |
no test coverage detected