UNPKG

equation-solver-js

Version:

a javascript library for solving mathematical equations

273 lines (222 loc) 11.7 kB
import { InvalidCoefficientsError, NoSolutionError, InfiniteSolutionsError } from './errors.js'; import { MAX_SAFE_COEFFICIENT, FLOATING_POINT_PRECISION } from './constants.js'; /** * Validates equation coefficients object * @param {Object} equation - Equation coefficients object * @throws {InvalidCoefficientsError} When coefficients are invalid */ const validateEquationCoefficients = (equation) => { const isInvalidObject = !equation || typeof equation !== 'object'; const isXNotNumber = typeof equation.xCoefficient !== 'number'; const isYNotNumber = typeof equation.yCoefficient !== 'number'; const isConstantNotNumber = typeof equation.constant !== 'number'; if (isInvalidObject) { throw new InvalidCoefficientsError( 'Invalid equation coefficients object', equation ); } if (isXNotNumber || isYNotNumber || isConstantNotNumber) { throw new InvalidCoefficientsError( 'Coefficients must be numbers', equation ); } // NaN and Infinity validation const isXInvalid = !Number.isFinite(equation.xCoefficient); const isYInvalid = !Number.isFinite(equation.yCoefficient); const isConstantInvalid = !Number.isFinite(equation.constant); if (isXInvalid || isYInvalid || isConstantInvalid) { throw new InvalidCoefficientsError( 'Coefficients cannot be NaN or Infinity', equation ); } // Large number validation const isTooLarge = Math.abs(equation.xCoefficient) > MAX_SAFE_COEFFICIENT || Math.abs(equation.yCoefficient) > MAX_SAFE_COEFFICIENT || Math.abs(equation.constant) > MAX_SAFE_COEFFICIENT; if (isTooLarge) { throw new InvalidCoefficientsError( 'Coefficients too large for safe calculation', equation ); } } /** * Chooses which variable to isolate from which equation for optimal solving */ const chooseVariableToIsolate = (firstEquationCoefficients, secondEquationCoefficients) => { const firstEquationXCoefficient = firstEquationCoefficients.xCoefficient; const firstEquationYCoefficient = firstEquationCoefficients.yCoefficient; const secondEquationXCoefficient = secondEquationCoefficients.xCoefficient; const secondEquationYCoefficient = secondEquationCoefficients.yCoefficient; const canIsolateXFromFirst = Math.abs(firstEquationXCoefficient) === 1; const canIsolateYFromFirst = Math.abs(firstEquationYCoefficient) === 1; const canIsolateXFromSecond = Math.abs(secondEquationXCoefficient) === 1; const canIsolateYFromSecond = Math.abs(secondEquationYCoefficient) === 1; const absFirstX = Math.abs(firstEquationXCoefficient); const absFirstY = Math.abs(firstEquationYCoefficient); const absSecondX = Math.abs(secondEquationXCoefficient); const absSecondY = Math.abs(secondEquationYCoefficient); if (canIsolateXFromFirst) { return {variable: 'x', equation: 'first'}; } if (canIsolateYFromFirst) { return {variable: 'y', equation: 'first'}; } if (canIsolateXFromSecond) { return {variable: 'x', equation: 'second'}; } if (canIsolateYFromSecond) { return {variable: 'y', equation: 'second'}; } // Find smallest non-zero coefficient for isolation const candidates = []; if (absFirstX > FLOATING_POINT_PRECISION) { candidates.push({value: absFirstX, variable: 'x', equation: 'first'}); } if (absFirstY > FLOATING_POINT_PRECISION) { candidates.push({value: absFirstY, variable: 'y', equation: 'first'}); } if (absSecondX > FLOATING_POINT_PRECISION) { candidates.push({value: absSecondX, variable: 'x', equation: 'second'}); } if (absSecondY > FLOATING_POINT_PRECISION) { candidates.push({value: absSecondY, variable: 'y', equation: 'second'}); } if (candidates.length === 0) { throw new InvalidCoefficientsError('All coefficients are zero - no solution possible'); } // Choose the smallest non-zero coefficient const smallest = candidates.reduce((min, current) => current.value < min.value ? current : min ); return {variable: smallest.variable, equation: smallest.equation}; } /** * Isolates a variable from an equation */ const isolateVariable = (equationCoefficients, variableToIsolate, detailedSolutionSteps) => { const xCoefficient = equationCoefficients.xCoefficient; const yCoefficient = equationCoefficients.yCoefficient; const constant = equationCoefficients.constant; detailedSolutionSteps.push(`Original equation: ${xCoefficient}x + ${yCoefficient}y = ${constant}`); if (variableToIsolate === 'x') { if (Math.abs(xCoefficient) < FLOATING_POINT_PRECISION) { throw new InvalidCoefficientsError( `Cannot isolate x: coefficient is zero. Equation: ${xCoefficient}x + ${yCoefficient}y = ${constant}` ); } detailedSolutionSteps.push(`Isolating x:`); detailedSolutionSteps.push(`${xCoefficient}x = ${constant} - ${yCoefficient}y`); detailedSolutionSteps.push(`x = (${constant} - ${yCoefficient}y) / ${xCoefficient}`); return { isolatedVariable: 'x', constantTerm: constant / xCoefficient, otherVariableCoefficient: -yCoefficient / xCoefficient, expression: `(${constant} - ${yCoefficient}y) / ${xCoefficient}` }; } else { if (Math.abs(yCoefficient) < FLOATING_POINT_PRECISION) { throw new InvalidCoefficientsError( `Cannot isolate y: coefficient is zero. Equation: ${xCoefficient}x + ${yCoefficient}y = ${constant}` ); } detailedSolutionSteps.push(`Isolating y:`); detailedSolutionSteps.push(`${yCoefficient}y = ${constant} - ${xCoefficient}x`); detailedSolutionSteps.push(`y = (${constant} - ${xCoefficient}x) / ${yCoefficient}`); return { isolatedVariable: 'y', constantTerm: constant / yCoefficient, otherVariableCoefficient: -xCoefficient / yCoefficient, expression: `(${constant} - ${xCoefficient}x) / ${yCoefficient}` }; } } /** * Substitutes isolated variable into target equation and solves */ const substituteAndSolve = (isolatedForm, targetEquationCoefficients, detailedSolutionSteps) => { const targetXCoefficient = targetEquationCoefficients.xCoefficient; const targetYCoefficient = targetEquationCoefficients.yCoefficient; const targetConstant = targetEquationCoefficients.constant; detailedSolutionSteps.push(`Substitute into other equation:`); detailedSolutionSteps.push(`Original: ${targetXCoefficient}x + ${targetYCoefficient}y = ${targetConstant}`); if (isolatedForm.isolatedVariable === 'x') { detailedSolutionSteps.push(`Replace x with ${isolatedForm.expression}:`); detailedSolutionSteps.push(`${targetXCoefficient}(${isolatedForm.expression}) + ${targetYCoefficient}y = ${targetConstant}`); const newYCoefficient = (targetXCoefficient * isolatedForm.otherVariableCoefficient) + targetYCoefficient; const newConstant = targetConstant - (targetXCoefficient * isolatedForm.constantTerm); detailedSolutionSteps.push(`Simplify: ${newYCoefficient}y = ${newConstant}`); if (Math.abs(newYCoefficient) < FLOATING_POINT_PRECISION) { if (Math.abs(newConstant) < FLOATING_POINT_PRECISION) { throw new InfiniteSolutionsError( 'System has infinite solutions - equations are equivalent' ); } else { throw new NoSolutionError( 'System has no solution - equations represent parallel lines' ); } } const yValue = newConstant / newYCoefficient; detailedSolutionSteps.push(`Solve for y: y = ${newConstant} / ${newYCoefficient} = ${yValue}`); const xValue = isolatedForm.constantTerm + (isolatedForm.otherVariableCoefficient * yValue); detailedSolutionSteps.push(`Calculate x: x = ${isolatedForm.constantTerm} + (${isolatedForm.otherVariableCoefficient} * ${yValue}) = ${xValue}`); return {xValue, yValue}; } else { detailedSolutionSteps.push(`Replace y with ${isolatedForm.expression}:`); detailedSolutionSteps.push(`${targetXCoefficient}x + ${targetYCoefficient}(${isolatedForm.expression}) = ${targetConstant}`); const newXCoefficient = targetXCoefficient + (targetYCoefficient * isolatedForm.otherVariableCoefficient); const newConstant = targetConstant - (targetYCoefficient * isolatedForm.constantTerm); if (Math.abs(newXCoefficient) < FLOATING_POINT_PRECISION) { if (Math.abs(newConstant) < FLOATING_POINT_PRECISION) { throw new InfiniteSolutionsError( 'System has infinite solutions - equations are equivalent' ); } else { throw new NoSolutionError( 'System has no solution - equations represent parallel lines' ); } } detailedSolutionSteps.push(`Simplify: ${newXCoefficient}x = ${newConstant}`); const xValue = newConstant / newXCoefficient; detailedSolutionSteps.push(`Solve for x: x = ${newConstant} / ${newXCoefficient} = ${xValue}`); const yValue = isolatedForm.constantTerm + (isolatedForm.otherVariableCoefficient * xValue); detailedSolutionSteps.push(`Calculate y: y = ${isolatedForm.constantTerm} + (${isolatedForm.otherVariableCoefficient} * ${xValue}) = ${yValue}`); return {xValue, yValue}; } } /** * Solves a system of two linear equations with two variables */ const solveLinearEquationSystem = (firstEquationCoefficients, secondEquationCoefficients) => { validateEquationCoefficients(firstEquationCoefficients); validateEquationCoefficients(secondEquationCoefficients); const detailedSolutionSteps = []; detailedSolutionSteps.push(`System of equations:`); detailedSolutionSteps.push(`Equation 1: ${firstEquationCoefficients.xCoefficient}x + ${firstEquationCoefficients.yCoefficient}y = ${firstEquationCoefficients.constant}`); detailedSolutionSteps.push(`Equation 2: ${secondEquationCoefficients.xCoefficient}x + ${secondEquationCoefficients.yCoefficient}y = ${secondEquationCoefficients.constant}`); detailedSolutionSteps.push(''); const isolationChoice = chooseVariableToIsolate(firstEquationCoefficients, secondEquationCoefficients); detailedSolutionSteps.push(`Strategy: Isolate ${isolationChoice.variable} from ${isolationChoice.equation} equation`); detailedSolutionSteps.push(''); const sourceEquation = isolationChoice.equation === 'first' ? firstEquationCoefficients : secondEquationCoefficients; const targetEquation = isolationChoice.equation === 'first' ? secondEquationCoefficients : firstEquationCoefficients; const isolatedForm = isolateVariable(sourceEquation, isolationChoice.variable, detailedSolutionSteps); detailedSolutionSteps.push(''); const solutionResult = substituteAndSolve(isolatedForm, targetEquation, detailedSolutionSteps); detailedSolutionSteps.push(''); detailedSolutionSteps.push(`Final Solution: x = ${solutionResult.xValue}, y = ${solutionResult.yValue}`); return { solution: {x: solutionResult.xValue, y: solutionResult.yValue}, steps: detailedSolutionSteps }; } export {solveLinearEquationSystem};