UNPKG

mathsteps-experimental-fork

Version:

Step by step math solutions. Experimental Fork

302 lines (295 loc) 10.8 kB
import { kemuFlatten } from '../../simplifyExpression/kemuSimplifyCommonServices.js'; import Node from '../../node/index.js'; import stepThrough from '../../simplifyExpression/index.js'; const NODE_ZERO = Node.Creator.constant(0); const NODE_ONE = Node.Creator.constant(1); const NODE_TWO = Node.Creator.constant(2); const NODE_FOUR = Node.Creator.constant(4); // TODO: Move to better place? function _getNodeDegree(node, unknownVariable) { let rv = null; if (Node.Type.isSymbol(node) && (node.name === unknownVariable)) { // Node is just a variable without any power: x. rv = NODE_ONE; } else if (Node.Type.isOperator(node, '^') && Node.Type.isNamedSymbol(node.args[0], unknownVariable)) { // Powered variable: x^n rv = node.args[1]; } else { throw new Error(`_getNodeDegree: unexpected node (${node.op}): ${node}`) } return rv } function _parsePolynomial(node, unknownVariable, rv) { // Create new coefficient array if needed. if (rv == null) { rv = { doAllExponentsAreInteger: true, coeffNodes: [0], }; node = kemuFlatten(node); } // Handle unary minus: -(...) let sign = 1; while (Node.Type.isUnaryMinus(node)) { sign *= -1; node = node.args[0]; } let c = null; let n = null; if (Node.Type.isOperator(node)) { // x op y switch (node.op) { case '+': { // ... an x^n + am x^m ... node.args.forEach((oneTerm) => { _parsePolynomial(oneTerm, unknownVariable, rv); }); break } case '*': { // c * x^n if (node.args.length !== 2) throw new Error(`_parsePolynomial: unexpected node (${node.op}): ${node}`) const arg1 = node.args[0]; const arg2 = node.args[1]; if (Node.Type.doesContainSymbol(arg2, unknownVariable)) { c = arg1; n = _getNodeDegree(arg2, unknownVariable); } else { c = arg2; n = _getNodeDegree(arg1, unknownVariable); } break } case '^': { // x^n (without coefficient) c = NODE_ONE; n = _getNodeDegree(node, unknownVariable); break } default: { throw new Error(`_parsePolynomial: unexpected node (${node.op}): ${node}`) } } } else if ((Node.Type.isSymbol(node)) && (node.name === unknownVariable)) { // Just a lonely variable: // x = 1 * x^1 c = NODE_ONE; n = NODE_ONE; } if (c && n) { if (Node.Type.kemuIsConstantInteger(n)) { // Collect found coefficients in result array. // Possible improevement: Avoid huge array for x^987837489234324 like. if (sign < 0) c = Node.Creator.unaryMinus(c); n = Number.parseInt(n.value); rv.coeffNodes[n] = c; } else { // Non-constant / non-integer polynomial exponent. rv.doAllExponentsAreInteger = false; } } // console.log("<- _parsePolynomial", node.toString(), rv) return rv } function _createNodePolynomialTerm(c, n, x) { let rv = null; // Possible improvement: Bignumber exponent (n) ? switch (n) { case 0: { // c * x^0 = c rv = c; break } case 1: { // c * x^1 = c*x rv = x; break } default: { const xPowN = Node.Creator.operator('^', [x, Node.Creator.constant(n)]); if (Node.Type.kemuIsConstantInteger(c, 1)) { // 1 * x^n rv = xPowN; } else { // General case: c * x^n rv = Node.Creator.operator('*', [c, xPowN]); } } } return rv } function _simplifyNode(node) { const steps = stepThrough.oldApi(node); if (steps.length > 0) node = (steps[steps.length - 1].rootNode); return { node, steps } } const WparenxEQ_C = { id: 'WparenxEQ_C', pattern: 'W(x)=C', solveFunction: (equation) => { // Pool of known transformations. const poolOfRules = [ // Common rules. { l: 'EQ(fx + a , a2)', r: 'EQ(fx , a2 - a)', id: 'move_c_to_right' }, { l: 'EQ(fx - a , a2)', r: 'EQ(fx , a2 + a)', id: 'move_c_to_right' }, { l: 'EQ(fx1 + a + fx2 , a2)', r: 'EQ(fx1 + fx2 , a2 - a)', id: 'move_c_to_right' }, { l: 'EQ(fx1 + a - fx2 , a2)', r: 'EQ(fx1 - fx2 , a2 - a)', id: 'move_c_to_right' }, { l: 'EQ(a + fx, a2)', r: 'EQ( fx , a2 - a)', id: 'move_c_to_right' }, { l: 'EQ(a - fx, a2)', r: 'EQ(-fx , a2 - a)', id: 'move_c_to_right' }, { l: 'EQ(a * fx , a2)', r: 'EQ(fx , a2 / a)', id: 'div_by_c' }, { l: 'EQ(fx * a , a2)', r: 'EQ(fx , a2 * a)', id: 'div_by_c' }, { l: 'EQ(fx / a , a2)', r: 'EQ(fx , a2 * a)', id: 'mul_by_c' }, // Linear equation: W1(x) = C { l: 'EQ(a + x, a2)', r: 'EQ( x , a2 - a)', id: 'move_c_to_right' }, { l: 'EQ(a - x, a2)', r: 'EQ(-x , a2 - a)', id: 'move_c_to_right' }, { l: 'EQ(a x , a2)', r: 'EQ(x , a2 / a)', id: 'div_by_c' }, { l: 'EQ(x/a , a2)', r: 'EQ(x , a2 * a)', id: 'mul_by_c' }, { l: 'EQ(- x , a2)', r: 'EQ(x , -a2)', id: 'mul_by_minus_one' }, { l: 'EQ(x, a1)', r: '[a1]', id: 'solution' }, // f(x)^a = 0 gives f(x) = 0 { l: 'EQ(fx^a , 0)', r: 'EQ(fx , 0)', id: 'remove_pow' }, // TODO: General handler for x^n = C { l: 'EQ(x^2, a1)', r: '[-sqrt(a1), sqrt(a1)]', id: 'solution' }, { l: 'EQ(x^3, a1)', r: '[nthRoot(a1,3)]', id: 'solution' }, { l: 'EQ(x^4, a1)', r: '[-nthRoot(a1,4), nthRoot(a1,4)]', id: 'solution' }, { l: 'EQ(x^5, a1)', r: '[nthRoot(a1,5)]', id: 'solution' }, { l: 'EQ(x^6, a1)', r: '[-nthRoot(a1,6), nthRoot(a1,6)]', id: 'solution' }, ]; equation.applyRules(poolOfRules, { simplifyAfterEachStepEnabled: true, }); if (!equation.isSolved()) { let polyObj; // TODO: Move to another W2(x)=C handler. // TODO -Grayson, I don't really understand this yet. the thrown error happens in case: steps: ['2*(x-4)-4*(x+2*3)=6', '(2*(x-4)-4*(x+2*3))/2=6/2']. Ignoring whatever this does seems to work fine for now. try { polyObj = _parsePolynomial(equation.left.node, equation.unknownVariable); } catch (error) { // Ignore error. } if (polyObj?.doAllExponentsAreInteger) { if (polyObj.coeffNodes.length === 3) { // W2(x) = ax^2 + bx = c = quadratic equation. const a = polyObj.coeffNodes[2]; const b = polyObj.coeffNodes[1]; const c = Node.Creator.unaryMinus(equation.right.node); if (a && b && c) { // Pure quadratic equation: ax^2 + bx = c // We're going to solve by delta scheme. // Calculate helper values. const minusB = Node.Creator.unaryMinus(b); // -b const twoA = Node.Creator.operator('*', [NODE_TWO, a]); // 2a // Calculate delta. let delta = Node.Creator.operator('-', [ Node.Creator.operator('^', [b, NODE_TWO]), Node.Creator.operator('*', [NODE_FOUR, a, c], true), // - 4ac ]); let result = _simplifyNode(delta); const deltaSteps = result.steps; delta = result.node; // Classify delta sign. const isDeltaZero = Node.Type.isZero(delta); const isDeltaPositive = Node.Type.kemuIsConstantPositive(delta); const isDeltaNegative = Node.Type.kemuIsConstantNegative(delta); // Caculate sqrt(delta) let sqrtDelta = Node.Creator.kemuCreateSqrt(delta); result = _simplifyNode(sqrtDelta); sqrtDelta = result.node; const sqrtDeltaSteps = result.steps; // Dispatch delta sign. let x1 = null; let x2 = null; let x1Steps = null; let x2Steps = null; if (isDeltaNegative) ; else if (isDeltaZero) { // Delta is zero - there is exacly one solution. // Calculate solution: x // Possible improvement: Check delta sign. // x1 = -b/2a x1 = Node.Creator.operator('/', [minusB, twoA]); } else { // The sign of delta is positive or unknown. // Apply general formulas for x1 and x2. // Calculate solution: x1 // Possible improvement: Check delta sign. x1 = Node.Creator.operator('/', [ Node.Creator.operator('-', [minusB, sqrtDelta]), // ----------------- twoA, // 2a ]); // Calculate solution: x2 // Possible improvement: Check delta sign. x2 = Node.Creator.operator('/', [ Node.Creator.operator('+', [minusB, sqrtDelta]), // ----------------- twoA, // 2a ]); } // Simplify solutions if any. const solutions = []; if (x1) { result = _simplifyNode(x1); x1 = result.node; x1Steps = result.steps; solutions.push(x1); } if (x2) { result = _simplifyNode(x2); x2 = result.node; x2Steps = result.steps; solutions.push(x2); } // Log usage of delta scheme. equation._logStep('solving_quadratic', true, { a, b, c, delta, deltaSteps, isDeltaZero, isDeltaPositive, isDeltaNegative, sqrtDelta, sqrtDeltaSteps, x1, x2, x1Steps, x2Steps, }); // Apply solutions. equation.applySolution(solutions); } } else if (Node.Type.isZero(equation.right.node)) { // Wn(x) = 0 gives x * Wn-1(x) = 0 const x = Node.Creator.symbol(equation.unknownVariable); // Build Wn-1(x) polynomial. const terms = []; polyObj.coeffNodes.forEach((c, n) => { if (c) terms.push(_createNodePolynomialTerm(c, n - 1, x)); }); // L = x * Wn-1(x) const Wnm1 = Node.Creator.operator('+', terms.reverse()); const L = Node.Creator.operator('*', [x, Wnm1]); equation.applyStep('move_x_before', { args: [L, NODE_ZERO] }); } } } }, }; export { WparenxEQ_C };