diff --git a/index.js b/index.js index de68c33..294d78c 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,7 @@ import {defineRule, definePatternRule, canApplyRule, applyRule, rewriteNode} from './lib/rule' import {flattenOperands} from './lib/utils' -import * as rules from './lib/rule-list.js' +import * as simple_rules from './lib/simple-rules.js' +import * as factor_rules from './lib/factor-rules.js' export { applyRule, @@ -9,5 +10,6 @@ export { defineRule, rewriteNode, flattenOperands, - rules, + simple_rules, + factor_rules, } diff --git a/lib/__test__/factor_rules.test.js b/lib/__test__/factor_rules.test.js new file mode 100644 index 0000000..c7f6f02 --- /dev/null +++ b/lib/__test__/factor_rules.test.js @@ -0,0 +1,81 @@ +import assert from 'assert' +import {parse, print, evaluate} from 'math-parser' + +import {applyRule, canApplyRule} from '../rule' +import * as factor_rules from '../factor-rules' + +const applyRuleString = (rule, input) => print(applyRule(rule, parse(input))) +const canApplyRuleString = (rule, input) => canApplyRule(rule, parse(input)) + +const suite = (title, rule, tests) => describe(title, () => { + tests.forEach(t => { + it(`${t[0]} => ${t[1]}`, () => { + assert.equal(print(applyRule(rule, parse(t[0]))), t[1]) + }) + }) +}) + +suite.only = (title, rule, tests) => describe.only(title, () => { + tests.forEach(t => { + it(`${t[0]} => ${t[1]}`, () => { + assert.equal(t[1], print(applyRule(rule, parse(t[0])))) + }) + }) +}) + +suite('factor symbol', factor_rules.FACTOR_SYMBOL, [ + //['x^2 + x^5 + x^16', ''], + //['x^2 - x^5 - x^16', ''], +]) + +suite('factor difference of squares helper', factor_rules.FACTOR_DIFFERENCE_OF_SQUARES_HELPER, [ + ['4(xy)^2 - 16x^2', '(2 xy^1)^2 - (4 x^1)^2'], + ['1 x^2 - 1 y^2', '(1 x^1)^2 - (1 y^1)^2'] +]) + +suite('factor difference of squares', factor_rules.FACTOR_DIFFERENCE_OF_SQUARES, [ + ['(2x)^2 - (3y)^2', '(2 x + 3 y) (2 x - 3 y)'], + ['(1 x^1)^2 - (1 y^1)^2', '(1 x^1 + 1 y^1) (1 x^1 - 1 y^1)'] +]) + +suite('factor perfect squares', factor_rules.FACTOR_PERFECT_SQUARE_TRINOMIALS, [ + ['4x^2 + 12x^1 + 9', '(2 x^1 + 3)^2'], + ['4x^4 - 12x^2 + 9', '(2 x^2 - 3)^2'], + ['4x^2 - 12x^1y^1 + 9y^2', '(2 x^1 - 3 y^1)^2'], + ['1a^2 + 2a^1 b^1 + 1b^2', '(1 a^1 + 1 b^1)^2'], + ['1x^2 + 10x^1 + 25', '(1 x^1 + 5)^2'], + // TODO: handle this case + //['1x^2 + bx + (b/2)^4', '(x + b/2)^2'] +]) + +suite('rearrange terms', factor_rules.REARRANGE_TERMS, [ + ['2 + 3x', '3 x + 2'], + ['4 + 3x^2 + 2x', '3 x^2 + 2 x + 4'], + ['3x - 2x^4 + 2x', '-2 x^4 + 3 x + 2 x'], + ['9 + 12x^2 + 4x^4', '4 x^4 + 12 x^2 + 9'] +]) + +suite('factor sum product rule', factor_rules.FACTOR_SUM_PRODUCT_RULE, [ + // 1 + ['4x^4 + 12x^2 + 9','(2 x^2 + 3) (2 x^2 + 3)'], + // 2 + ['6x^4 + 13x^2 + 6', '(2 x^2 + 3) (3 x^2 + 2)'], + // 3 + ['2x^4 - 7x^2 + 6', '(1 x^2 - 2) (2 x^2 - 3)'], + // 4 + ['6x^2 - 13x^1 + 6', '(2 x^1 - 3) (3 x^1 - 2)'], + // 5 + ['2x^4 - 1x^2 - 6', '(1 x^2 - 2) (2 x^2 + 3)'], + // 6 + ['6x^4 + 7x^2 - 3', '(2 x^2 + 3) (3 x^2 - 1)'], + // 7 + ['2x^4 + 1x^2 - 6', '(1 x^2 + 2) (2 x^2 - 3)'], + // 8 + ['6x^4 - 7x^2 - 3', '(2 x^2 - 3) (3 x^2 + 1)'], + // TODO: handle this case + //['4x^2y^2 + 12a^2b^2x^1y^1 + 9x^b^4', ''], + ['4a^2b^2 + 12a^1b^1x^1y^2 + 9x^2y^4', '(2 a^1 b^1 + 3 x^1 y^2) (2 a^1 b^1 + 3 x^1 y^2)'], + ['12x^2 + 17 x^1 y^1 + 6y^2', '(3 x^1 + 2 y^1) (4 x^1 + 3 y^1)'], + ['12x^2 + 17x^1 + 6', '(3 x^1 + 2) (4 x^1 + 3)'], + ['4x^2 + 12x^1 + 9', '(2 x^1 + 3) (2 x^1 + 3)'], +]) diff --git a/lib/__test__/rule.test.js b/lib/__test__/rule.test.js index 3b387a4..170c268 100644 --- a/lib/__test__/rule.test.js +++ b/lib/__test__/rule.test.js @@ -4,7 +4,7 @@ import {build, query} from 'math-nodes' import {defineRule, definePatternRule, canApplyRule, applyRule, rewriteNode} from '../rule' import {populatePattern, patternToMatchFn} from '../pattern' -import * as rules from '../rule-list' +import * as simple_rules from '../simple-rules' // returns the rewritten string const rewriteString = (matchPattern, rewritePattern, input) => { @@ -73,13 +73,13 @@ describe('rule functions', () => { describe.only('rewriteNode', () => { it('should rewrite nodes that that the rule matches', () => { const ast = parse('2 * (x + 0)') - const result = rewriteNode(rules.REMOVE_ADDING_ZERO, ast.args[1]) + const result = rewriteNode(simple_rules.REMOVE_ADDING_ZERO, ast.args[1]) assert.deepEqual(result, build.identifier('x')) }) it('should return null when the rule does not match', () => { const ast = parse('2 * (x + 0)') - const result = rewriteNode(rules.REMOVE_ADDING_ZERO, ast.args[0]) + const result = rewriteNode(simple_rules.REMOVE_ADDING_ZERO, ast.args[0]) assert.equal(result, null) }) }) diff --git a/lib/__test__/rule-list.test.js b/lib/__test__/simple_rules.test.js similarity index 71% rename from lib/__test__/rule-list.test.js rename to lib/__test__/simple_rules.test.js index 32f70c0..3275d7e 100644 --- a/lib/__test__/rule-list.test.js +++ b/lib/__test__/simple_rules.test.js @@ -2,7 +2,7 @@ import assert from 'assert' import {parse, print, evaluate} from 'math-parser' import {applyRule, canApplyRule} from '../rule' -import * as rules from '../rule-list' +import * as simple_rules from '../simple-rules.js' const applyRuleString = (rule, input) => print(applyRule(rule, parse(input))) const canApplyRuleString = (rule, input) => canApplyRule(rule, parse(input)) @@ -23,37 +23,36 @@ suite.only = (title, rule, tests) => describe.only(title, () => { }) }) - describe('rules', () => { - suite('negation', rules.NEGATION, [ + suite('negation', simple_rules.NEGATION, [ ['--1','1'], ['--x','x'], ['--(x + 1)', 'x + 1'], ['x^(--(x + 1))', 'x^(x + 1)'] ]) - suite('rearrange coefficient', rules.REARRANGE_COEFF, [ + suite('rearrange coefficient', simple_rules.REARRANGE_COEFF, [ ['y^3 * 5', '5 y^3'], ['yz * 3', '3 yz'], // TODO: handle this case better //['3x^2 * 5', '5 (3 x^2)'] ]) - suite('division by negative one', rules.DIVISION_BY_NEGATIVE_ONE, [ + suite('division by negative one', simple_rules.DIVISION_BY_NEGATIVE_ONE, [ ['2 / -1','-2'], ['x / -1','-x'], ['(x + 1) / -1', '-(x + 1)'], ['x ^ (2 / -1)', 'x^-2'], ]) - suite('division by one', rules.DIVISION_BY_ONE, [ + suite('division by one', simple_rules.DIVISION_BY_ONE, [ ['2 / 1', '2'], ['x / 1', 'x'], ['(x + 1) / 1', 'x + 1'], ['x^((x + 2) / 1)', 'x^(x + 2)'], ]) - suite('multiply by zero', rules.MULTIPLY_BY_ZERO, [ + suite('multiply by zero', simple_rules.MULTIPLY_BY_ZERO, [ ['2 * 0', '0'], ['x * 0', '0'], ['x 0', '0'], @@ -61,7 +60,7 @@ describe('rules', () => { ['x^((x + 1) * 0)', 'x^0'], ]) - suite('multiply by zero reverse', rules.MULTIPLY_BY_ZERO_REVERSE, [ + suite('multiply by zero reverse', simple_rules.MULTIPLY_BY_ZERO_REVERSE, [ ['0 * 2', '0'], ['0 * x', '0'], ['0 x', '0'], @@ -69,21 +68,21 @@ describe('rules', () => { ['x^(0 * (x + 1))', 'x^0'], ]) - suite('reduce exponent by zero', rules.REDUCE_EXPONENT_BY_ZERO, [ + suite('reduce exponent by zero', simple_rules.REDUCE_EXPONENT_BY_ZERO, [ ['2 ^ 0', '1'], ['x ^ 0', '1'], ['(x + 1) ^ 0', '1'], ['x^((x + 1) ^ 0)', 'x^1'], ]) - suite('reduce zero numerator', rules.REDUCE_ZERO_NUMERATOR, [ + suite('reduce zero numerator', simple_rules.REDUCE_ZERO_NUMERATOR, [ ['0 / 2', '0'], ['0 / x', '0'], ['0 / (x + 1)', '0'], ['x^(0 / (x + 1))', 'x^0'], ]) - suite('remove adding zero', rules.REMOVE_ADDING_ZERO, [ + suite('remove adding zero', simple_rules.REMOVE_ADDING_ZERO, [ ['2 + 0', '2'], ['2 + 0 + x', '2 + x'], ['x + 0', 'x'], @@ -91,28 +90,28 @@ describe('rules', () => { ['x^(x + 0)', 'x^x'], ]) - suite('remove adding zero reverse', rules.REMOVE_ADDING_ZERO_REVERSE, [ + suite('remove adding zero reverse', simple_rules.REMOVE_ADDING_ZERO_REVERSE, [ ['0 + 2', '2'], ['0 + x', 'x'], ['0 + (x + 1)', 'x + 1'], ['x^(0 + x)', 'x^x'], ]) - suite('remove exponent by one', rules.REMOVE_EXPONENT_BY_ONE, [ + suite('remove exponent by one', simple_rules.REMOVE_EXPONENT_BY_ONE, [ ['2 ^ 1', '2'], ['x ^ 1', 'x'], ['(x + 1) ^ 1', 'x + 1'], ['x^((x + 1)^1)', 'x^(x + 1)'], ]) - suite('remove exponent by base one', rules.REMOVE_EXPONENT_BASE_ONE, [ + suite('remove exponent by base one', simple_rules.REMOVE_EXPONENT_BASE_ONE, [ ['1 ^ 2', '1'], ['1 ^ x', '1'], ['1 ^ (x + 1)', '1'], ['x^(1 ^ (x + 1))', 'x^1'], ]) - suite('remove multiplying by negative one', rules.REMOVE_MULTIPLYING_BY_NEGATIVE_ONE, [ + suite('remove multiplying by negative one', simple_rules.REMOVE_MULTIPLYING_BY_NEGATIVE_ONE, [ ['2 * -1', '-2'], ['x * -1', '-x'], ['(x + 1) * -1', '-(x + 1)'], @@ -120,7 +119,7 @@ describe('rules', () => { ['2x * 2 * -1', '2 x * -2'], ]) - suite('remove multiplying by one', rules.REMOVE_MULTIPLYING_BY_ONE, [ + suite('remove multiplying by one', simple_rules.REMOVE_MULTIPLYING_BY_ONE, [ ['2 * 1', '2'], ['x * 1', 'x'], ['x 1', 'x'], @@ -129,7 +128,7 @@ describe('rules', () => { ['2 * 1 * z^2', '2 * z^2'], ]) - suite('remove multiplying by one reverse', rules.REMOVE_MULTIPLYING_BY_ONE_REVERSE, [ + suite('remove multiplying by one reverse', simple_rules.REMOVE_MULTIPLYING_BY_ONE_REVERSE, [ ['1 * 2', '2'], ['1 * x', 'x'], ['1 x', 'x'], @@ -137,49 +136,49 @@ describe('rules', () => { ['x^(1 * (x + 1))', 'x^(x + 1)'], ]) - suite('resolve double minus', rules.RESOLVE_DOUBLE_MINUS, [ + suite('resolve double minus', simple_rules.RESOLVE_DOUBLE_MINUS, [ ['2 - -1', '2 + 1'], ['x - -1', 'x + 1'], ['(x + 1) - -1', '(x + 1) + 1'], ['x^((x + 1) - -1)', 'x^((x + 1) + 1)'], ]) - suite('multiplying negatives', rules.MULTIPLY_NEGATIVES, [ + suite('multiplying negatives', simple_rules.MULTIPLY_NEGATIVES, [ ['-2 * -1', '2 * 1'], ['-x * -1', 'x * 1'], ['-(x + 1) * -1', '(x + 1) * 1'], ['x^(-(x + 1) * -1)', 'x^((x + 1) * 1)'], ]) - suite('remove multiplying by negative one', rules.REMOVE_MULTIPLYING_BY_NEGATIVE_ONE, [ + suite('remove multiplying by negative one', simple_rules.REMOVE_MULTIPLYING_BY_NEGATIVE_ONE, [ ['2 * -1', '-2'], ['x * -1', '-x'], ['(x + 1) * -1', '-(x + 1)'], ['x^((x + 1) * -1)', 'x^-(x + 1)'], ]) - suite('cancel minuses', rules.CANCEL_MINUSES, [ + suite('cancel minuses', simple_rules.CANCEL_MINUSES, [ ['-2 / -1', '2 / 1'], ['-x / -1', 'x / 1'], ['-(x + 1) / -1', '(x + 1) / 1'], ['x^(-(x + 1) / -1)', 'x^((x + 1) / 1)'], ]) - suite('simplify signs', rules.SIMPLIFY_SIGNS, [ + suite('simplify signs', simple_rules.SIMPLIFY_SIGNS, [ ['2 / -1', '-2 / 1'], ['x / -1', '-x / 1'], ['(x + 1) / -1', '-(x + 1) / 1'], ['x^((x + 1) / -1)', 'x^(-(x + 1) / 1)'], ]) - suite('add numerators', rules.COMBINE_NUMERATORS, [ + suite('add numerators', simple_rules.COMBINE_NUMERATORS, [ ['1/3 + 2/3', '(1 + 2) / 3'], ['1/x + 2/x + 3/x', '(1 + 2 + 3) / x'], ['2/3 - 1/3', '(2 - 1) / 3'], ['(1/3 + 2/3) / x', '(1 + 2) / 3 / x'], ]) - suite('common denominators', rules.COMMON_DENOMINATOR, [ + suite('common denominators', simple_rules.COMMON_DENOMINATOR, [ ['2/6 + 1/4', '(2 * 2) / (6 * 2) + (1 * 3) / (4 * 3)'], ['2/6 - 1/4', '(2 * 2) / (6 * 2) - (1 * 3) / (4 * 3)'], ['2/6 + 1/4 - 2/5', '(2 * 10) / (6 * 10) + (1 * 15) / (4 * 15) - (2 * 12) / (5 * 12)'], @@ -189,47 +188,49 @@ describe('rules', () => { ['2/4 - 1/4', '(2 * 1) / (4 * 1) - (1 * 1) / (4 * 1)'], ]) - suite('multiply fractions', rules.MULTIPLY_FRACTIONS, [ + suite('multiply fractions', simple_rules.MULTIPLY_FRACTIONS, [ ['2 / 3 * 2 / 3', '(2 * 2) / (3 * 3)'], ['x / 2 * x / 2', '(x * x) / (2 * 2)'], ['(x + 1) / 2 * (x + 1) / 2', '((x + 1) * (x + 1)) / (2 * 2)'], ['x^((x + 1) / 2 * (x + 1) / 2)', 'x^(((x + 1) * (x + 1)) / (2 * 2))'], ]) - suite('simplify division', rules.SIMPLIFY_DIVISION, [ + suite('simplify division', simple_rules.SIMPLIFY_DIVISION, [ ['2 / 3 / 4', '2 / (3 * 4)'], ['x / 2 / 2', 'x / (2 * 2)'], ['(x + 1) / 2 / (x + 1)', '(x + 1) / (2 * (x + 1))'], ['x^((x + 1) / 2 / 2)', 'x^((x + 1) / (2 * 2))'], ]) - suite('multiply by inverse', rules.MULTIPLY_BY_INVERSE, [ + suite('multiply by inverse', simple_rules.MULTIPLY_BY_INVERSE, [ ['2 / (3 / 4)', '2 * 4 / 3'], ['x / (2 / 2)', 'x * 2 / 2'], ['(x + 1) / (2 / (x + 1))', '(x + 1) * (x + 1) / 2'], ['x^((x + 1) / (2 / 2))', 'x^((x + 1) * 2 / 2)'], ]) - suite('absolute value', rules.ABSOLUTE_VALUE, [ + suite('absolute value', simple_rules.ABSOLUTE_VALUE, [ ['|-2|', '2'], ['|-x|', 'x'], ['|-(x + 1)|', 'x + 1'], ['x^(|-(x + 1)|)', 'x^(x + 1)'], ]) - suite('simplify fraction', rules.SIMPLIFY_FRACTION, [ + suite('simplify fraction', simple_rules.SIMPLIFY_FRACTION, [ ['-2/6', '-1 / 3'], ['3/-6', '-1 / 2'], ['-3/-6', '1 / 2'], ['1/3', '1 / 3'], ['2/6', '1 / 3'], - ['15/24', '5 / 8'], + ['15/24', '5 / 8'] ]) - suite('cancel exponent', rules.CANCEL_EXPONENT, [ + suite('cancel exponent', simple_rules.CANCEL_EXPONENT, [ ['nthRoot(x^2, 4)', 'nthRoot(x^1, 2)'], + ['nthRoot(y^3, 4)', 'nthRoot(y^3, 4)'], ['nthRoot(a^15, 24)', 'nthRoot(a^5, 8)'], ['nthRoot(b^4, 2)', 'b^2'], + ['nthRoot(c^8, 3)', 'nthRoot(c^8, 3)'], ['nthRoot(d^10, 10)', 'd^1'], ['nthRoot(x^2)', 'x^1'], ['nthRoot(x^-2, 4)', 'nthRoot(x^-1, 2)'], @@ -238,7 +239,7 @@ describe('rules', () => { ['nthRoot(z^-3, 3)', 'z^-1'], ]) - suite('combine under root', rules.COMBINE_UNDER_ROOT, [ + suite('combine under root', simple_rules.COMBINE_UNDER_ROOT, [ ['nthRoot(2, 2) * nthRoot(3, 2)', 'nthRoot(2 * 3, 2)'], ['nthRoot(4, 5) * nthRoot(5, 5) * nthRoot(6,5)', 'nthRoot(4 * 5 * 6, 5)'], ['nthRoot(x, 2) * nthRoot(y, 2)', 'nthRoot(x * y, 2)'], @@ -246,27 +247,26 @@ describe('rules', () => { ['nthRoot(2, x^2) * nthRoot(3, x^2)', 'nthRoot(2 * 3, x^2)'] ]) - suite('distribute nthRoot', rules.DISTRIBUTE_NTH_ROOT, [ + suite('distribute nthRoot', simple_rules.DISTRIBUTE_NTH_ROOT, [ ['nthRoot(2 * x, 2)', 'nthRoot(2, 2) * nthRoot(x, 2)'], ['nthRoot(3 * 3 * x, 3)', 'nthRoot(3, 3) * nthRoot(3, 3) * nthRoot(x, 3)'], ['nthRoot(x^2 * y^3 * z^4)', 'nthRoot(x^2, 2) * nthRoot(y^3, 2) * nthRoot(z^4, 2)'] ]) - suite('convert multiplication to exponent', rules.CONVERT_MULTIPLICATION_TO_EXPONENT, [ + suite('convert multiplication to exponent', simple_rules.CONVERT_MULTIPLICATION_TO_EXPONENT, [ ['2^1 * 2^1 * 2^3', '2^5'], ['3^2 * 3^1 * 3^20', '3^23'], ]) - suite('evaluate distributed nthRoot', rules.EVALUATE_DISTRIBUTED_NTH_ROOT, [ + suite('evaluate distributed nthRoot', simple_rules.EVALUATE_DISTRIBUTED_NTH_ROOT, [ ['nthRoot(4, 2) * nthRoot(x^2, 2)', '2 * x^1'], - ['nthRoot(4, 2) * nthRoot(x, 2)', '2 * nthRoot(x, 2)'], ['nthRoot(x^3, 3) * nthRoot(36, 2)', 'x^1 * 6'], ['nthRoot(x^-6, -4) * nthRoot(64, 3) * nthRoot(z^-50, 100)', 'nthRoot(x^3, 2) * 4 * nthRoot(z^-1, 2)'] // TODO: handle this test case // ['x * nthRoot(4, 2) * nthRoot(x^2, 2) * y', 'x * 2 * x^1 * y' ]) - suite('factor into prime', rules.FACTOR_INTO_PRIME, [ + suite('factor into prime', simple_rules.FACTOR_INTO_PRIME, [ ['12' ,'2 * 2 * 3'], ['36', '2 * 2 * 3 * 3'], ['91', '7 * 13'], @@ -274,7 +274,7 @@ describe('rules', () => { ['1', '1'], ]) - suite('group terms by root', rules.GROUP_TERMS_BY_ROOT, [ + suite('group terms by root', simple_rules.GROUP_TERMS_BY_ROOT, [ ['nthRoot(2 * 2 * 2 * 3, 2)', 'nthRoot((2 * 2) * 2 * 3, 2)'], ['nthRoot(2 * 3 * 3 * 2, 3)', 'nthRoot((2 * 2) * (3 * 3), 3)'], ['nthRoot(5 * 7 * 9 * 7 * 7 * 7, 4)', 'nthRoot(5 * (7 * 7 * 7 * 7) * 9, 4)'], @@ -282,7 +282,7 @@ describe('rules', () => { ['nthRoot(xyz * xyz * x y z * x y z, 4)', 'nthRoot((xyz * xyz) * (x y z * x y z), 4)'] ]) - suite('nthRoot value', rules.NTH_ROOT_VALUE, [ + suite('nthRoot value', simple_rules.NTH_ROOT_VALUE, [ ['nthRoot(4, 2)', '2'], ['nthRoot(16, 2)', '4'], ['nthRoot(-8, 3)', '-2'], @@ -290,7 +290,7 @@ describe('rules', () => { ['nthRoot(16, -2)', '.25'], ]) - suite('collects like terms', rules.COLLECT_LIKE_TERMS, [ + suite('collects like terms', simple_rules.COLLECT_LIKE_TERMS, [ ['2x + 1 - 2x', '(2 x - 2 x) + 1'], ['2x + 1 - x', '(2 x - x) + 1'], ['x^2 + 1 + x^2', '(x^2 + x^2) + 1'], @@ -303,13 +303,13 @@ describe('rules', () => { ['2x + 7y + 5 + 3y + 9x + 11', '(2 x + 9 x) + (7 y + 3 y) + (5 + 11)'], ]) - suite('fractional polynomials', rules.FRACTIONAL_POLYNOMIALS, [ + suite('fractional polynomials', simple_rules.FRACTIONAL_POLYNOMIALS, [ ['2x/3', '2 / 3 x'], ['3y^2/3', '3 / 3 y^2'], ['3x + 2x/3','3 x + 2 / 3 x'] ]) - suite('add polynomials', rules.ADD_POLYNOMIAL_TERMS, [ + suite('add polynomials', simple_rules.ADD_POLYNOMIAL_TERMS, [ ['2x + 2x + 2 + 4', '4 x + (2 + 4)'], ['3y^2 - 2y^2 + y^4', '1 y^2 + 1 y^4'], ['x - x', '0 x'], @@ -320,7 +320,7 @@ describe('rules', () => { ['2 x y + 2 y x', '4 x y'], ]) - suite('handles basic arithmetic', rules.SIMPLIFY_ARITHMETIC, [ + suite('handles basic arithmetic', simple_rules.SIMPLIFY_ARITHMETIC, [ ['1 + 2', '3'], ['1 + 2 + 3', '6'], ['3 * 8', '24'], @@ -336,26 +336,26 @@ describe('rules', () => { // ['x + 1 + 2 + y + 3 + 4 + z', 'x + 3 + y + 7 + z'], ]) - suite('evaluate addition', rules.EVALUATE_ADDITION, [ + suite('evaluate addition', simple_rules.EVALUATE_ADDITION, [ ['1 + 2', '3'], ['1 + 2 + 3 + 4', '10'], ['x + 1 + 2 + y', 'x + 3 + y'], ]) - suite('evaluate multiplication', rules.EVALUATE_MULTIPLICATION, [ + suite('evaluate multiplication', simple_rules.EVALUATE_MULTIPLICATION, [ ['2 * 4', '8'], ['2 * 4 * 6', '48'], ['x * 2 * 4 * y', 'x * 8 * y'], ]) - suite('evaluate division', rules.EVALUATE_DIVISION, [ + suite('evaluate division', simple_rules.EVALUATE_DIVISION, [ ['10 / 5', '2'], ['x^(10 / 5)', 'x^2'], ['10 / 5 / x', '2 / x'], ['x / (10 / 5)', 'x / 2'], ]) - suite('evaluate power', rules.EVALUATE_POWER, [ + suite('evaluate power', simple_rules.EVALUATE_POWER, [ ['(-2)^2', '4'], ['-2^2', '-4'], ['(-2)^3', '-8'], @@ -364,7 +364,7 @@ describe('rules', () => { ['(2^3)^x', '8^x'], ]) - suite('product rule', rules.PRODUCT_RULE, [ + suite('product rule', simple_rules.PRODUCT_RULE, [ ['10^2 * 10^5 * 10^3', '10^(2 + 5 + 3)'], ['x^a * x^b * x^c', 'x^(a + b + c)'], ['x^a * x^(b+c) * x^(d-e)', 'x^(a + (b + c) + (d - e))'], @@ -375,12 +375,12 @@ describe('rules', () => { // ['10^2 * 10^3 * x^a * x^b', '10^(2 + 3) * x^(a + b)'], ]) - suite('quotient rule', rules.QUOTIENT_RULE, [ + suite('quotient rule', simple_rules.QUOTIENT_RULE, [ ['x^5 / x^3', 'x^(5 - 3)'], ['x^-a / x^-b', 'x^(-a - -b)'], ]) - suite('power of a product', rules.POWER_OF_A_PRODUCT, [ + suite('power of a product', simple_rules.POWER_OF_A_PRODUCT, [ ['(2*3)^x', '2^x * 3^x'], ['(2*3*5)^x', '2^x * 3^x * 5^x'], ['(a*b*c*d)^x', 'a^x * b^x * c^x * d^x'], @@ -388,29 +388,29 @@ describe('rules', () => { ['(p*q)^(x-y)', 'p^(x - y) * q^(x - y)'], ]) - suite('power of a quotient', rules.POWER_OF_A_QUOTIENT, [ + suite('power of a quotient', simple_rules.POWER_OF_A_QUOTIENT, [ ['(5 / 3)^x', '5^x / 3^x'], ]) - suite('negative exponent', rules.NEGATIVE_EXPONENT, [ + suite('negative exponent', simple_rules.NEGATIVE_EXPONENT, [ ['2^-2', '1 / 2^2'], ['2^-(5x)','1 / 2^(5 x)'], ['(3x)^-(2 - 4x)', '1 / (3 x)^(2 - 4 x)'], ]) - suite('to negative exponent', rules.TO_NEGATIVE_EXPONENT, [ + suite('to negative exponent', simple_rules.TO_NEGATIVE_EXPONENT, [ ['1 / 2^2', '1 * 2^-2'], ['x / 2^(-2)', 'x * 2^--2'], ['(3 - x) / (x + 5)^3', '(3 - x) * (x + 5)^-3'] ]) - suite('fractional exponents', rules.FRACTIONAL_EXPONENTS, [ + suite('fractional exponents', simple_rules.FRACTIONAL_EXPONENTS, [ ['a^(p/q)', 'a^(1 / q)^p'], ['(a + b)^(2/3)', '(a + b)^(1 / 3)^2'], ['a^((2 + x) / (2 - x))', 'a^(1 / (2 - x))^(2 + x)'] ]) - suite('break up fraction', rules.BREAK_UP_FRACTION, [ + suite('break up fraction', simple_rules.BREAK_UP_FRACTION, [ ['(a + b) / 2', 'a / 2 + b / 2'], ['(a + b + c) / 2', 'a / 2 + b / 2 + c / 2'], ['(a + b) / (2n)', 'a / (2 n) + b / (2 n)'], @@ -418,7 +418,7 @@ describe('rules', () => { ['(a - b) / 2', 'a / 2 - b / 2'], ]) - suite('distribute', rules.DISTRIBUTE, [ + suite('distribute', simple_rules.DISTRIBUTE, [ ['2 * (x + 1)', '2 * x + 2 * 1'], ['2 * (x - 1)', '2 * x - 2 * 1'], ['(a + b) * (x + y)', '(a + b) * x + (a + b) * y'], @@ -427,45 +427,45 @@ describe('rules', () => { ['1 - 3 * (y - 1)', '1 - (3 * y - 3 * 1)'], ]) - suite('distribute right', rules.DISTRIBUTE_RIGHT, [ + suite('distribute right', simple_rules.DISTRIBUTE_RIGHT, [ ['(x + 1) * 2', 'x * 2 + 1 * 2'], ['(x - 1) * 2', 'x * 2 - 1 * 2'], ['(a + b) * (x + y)', 'a * (x + y) + b * (x + y)'], ['(a - b) * (x - y)', 'a * (x - y) - b * (x - y)'], ]) - suite('distribute negative one', rules.DISTRIBUTE_NEGATIVE_ONE, [ + suite('distribute negative one', simple_rules.DISTRIBUTE_NEGATIVE_ONE, [ ['-(x + 1)', '-1 * x + -1 * 1'], ['-(x - 1)', '-1 * x - -1 * 1'], ['-(a + b + c)', '-1 * a + -1 * b + -1 * c'], ]) // SOLVING FOR A VARIABLE - suite('add to both sides', rules.ADD_TO_BOTH_SIDES, [ + suite('add to both sides', simple_rules.ADD_TO_BOTH_SIDES, [ ['x - 3 = 2', 'x - 3 + 3 = 2 + 3'], ]) - suite('subtract from both sides', rules.SUBTRACT_FROM_BOTH_SIDES, [ + suite('subtract from both sides', simple_rules.SUBTRACT_FROM_BOTH_SIDES, [ ['x + 3 = 2', 'x + 3 - 3 = 2 - 3'], ]) - suite('multiple both sides', rules.MULTIPLY_BOTH_SIDES, [ + suite('multiple both sides', simple_rules.MULTIPLY_BOTH_SIDES, [ ['x / 2 = 1', 'x / 2 * 2 = 1 * 2'], ]) - suite('divide from both sides', rules.DIVIDE_FROM_BOTH_SIDES, [ + suite('divide from both sides', simple_rules.DIVIDE_FROM_BOTH_SIDES, [ ['2 x = 1', '(2 x) / 2 = 1 / 2'], ]) - suite('multiple both sides by inverse fraction', rules.MULTIPLY_BOTH_SIDES_BY_INVERSE_FRACTION, [ + suite('multiple both sides by inverse fraction', simple_rules.MULTIPLY_BOTH_SIDES_BY_INVERSE_FRACTION, [ ['2 / 3 * x = 1', '2 / 3 * x * 3 / 2 = 1 * 3 / 2'], ]) - suite('multiple both sides by negative one', rules.MULTIPLY_BOTH_SIDES_BY_NEGATIVE_ONE, [ + suite('multiple both sides by negative one', simple_rules.MULTIPLY_BOTH_SIDES_BY_NEGATIVE_ONE, [ ['-x = 2', '-1 * -x = -1 * 2'], ]) - suite('swap sides', rules.SWAP_SIDES, [ + suite('swap sides', simple_rules.SWAP_SIDES, [ ['2 = x', 'x = 2'], ]) }) @@ -473,21 +473,21 @@ describe('rules', () => { describe('canApplyRule', () => { describe('COLLECT_LIKE_TERMS', () => { it('2x + 1 - 2x should pass', () => { - assert(canApplyRuleString(rules.COLLECT_LIKE_TERMS, '2x + 1 - 2x')) + assert(canApplyRuleString(simple_rules.COLLECT_LIKE_TERMS, '2x + 1 - 2x')) }) it('2 x y + 1 - y x should pass', () => { - assert(canApplyRuleString(rules.COLLECT_LIKE_TERMS, '2 x y + 1 - y x')) + assert(canApplyRuleString(simple_rules.COLLECT_LIKE_TERMS, '2 x y + 1 - y x')) }) it('2x + 1 - 3y should fail', () => { - assert.equal(canApplyRuleString(rules.COLLECT_LIKE_TERMS, '2x + 1 - 3y'), false) + assert.equal(canApplyRuleString(simple_rules.COLLECT_LIKE_TERMS, '2x + 1 - 3y'), false) }) }) describe('SIMPLIFY_ARITHMETIC', () => { it('a + b + c should fail', () => { - assert.equal(canApplyRuleString(rules.SIMPLIFY_ARITHMETIC, 'a + b + c'), false) + assert.equal(canApplyRuleString(simple_rules.SIMPLIFY_ARITHMETIC, 'a + b + c'), false) }) }) }) diff --git a/lib/factor-rules.js b/lib/factor-rules.js new file mode 100644 index 0000000..b8f09df --- /dev/null +++ b/lib/factor-rules.js @@ -0,0 +1,385 @@ +import {parse, print} from 'math-parser' +import evaluate from 'math-evaluator' +import {gcd, lcm, nthRoot, primeFactorization, abs} from 'math-evaluator' +import {build, query} from 'math-nodes' +import {traverse} from 'math-traverse' + +import {defineRule, definePatternRule, applyRule, canApplyRule} from './rule' +import {isPolynomialTerm, getCoefficient, getVariableFactors, getCoefficientsAndConstants} from './rules/collect-like-terms' +import {clone, getRanges, flattenOperands} from './utils' + +const defineRuleString = (matchPattern, rewritePattern, constraints) => { + const matchAST = parse(matchPattern) + const rewriteAST = parse(rewritePattern) + + traverse(matchAST, { + leave(node) { + delete node.loc + } + }) + + traverse(rewriteAST, { + leave(node) { + delete node.loc + } + }) + + return definePatternRule(matchAST, rewriteAST, constraints) +} + +// FACTOR + +// TODO: add minus case +// assume monic polynomial +// TODO: add min and max to evaluate + +// e.g. x^2 + x^5 + x^6 -> x^2(x^0 + x^3 + x^4) +export const FACTOR_SYMBOL = defineRuleString('#a^#b_0 + ...', '#a^#eval(min(#b_0, ...)) (#a^#eval(#b_0 - min(#b_0, ...)) + ...)', {b: query.isNumber}) + +// TODO: add restrictions (#c_0 divisible by 2 && #a_0 is perfect square) + +// e.g. 4x^2 - 9y^2 -> (2x)^2 - (3y)^2 +export const FACTOR_DIFFERENCE_OF_SQUARES_HELPER = + defineRuleString('#a #b^#c - #d #e^#f', '(#eval(nthRoot(#a)) #b^(#eval(#c/2)))^2 - (#eval(nthRoot(#d)) #e^(#eval(#f/2)))^2') + +// e.g. (2x)^2 - (3y)^2 -> (2x + 3y)(2x - 3y) +export const FACTOR_DIFFERENCE_OF_SQUARES = + defineRuleString('#a^2 - #b^2', '(#a + #b)(#a - #b)') + + +export const hello = (num, root = 2, precision = 12) => { + // e.g 2^-3 = 1/(2^3) + const inv = root < 0 + if (inv) { + root = -root + } + + if (root === 0) { + throw new Error('Root must be non-zero') + } + if (num < 0 && (Math.abs(root) % 2 !== 1)) { + throw new Error('Root must be odd when a is negative.') + } + + // Edge cases zero and infinity. + // e.g 0^3 = 0, 0^-3 = Infinity + if (num === 0) { + return inv ? Infinity : 0 + } + + if (num === 1) { + return 1 + } + + if (!isFinite(num)) { + return inv ? 0 : num + } + + // Source: https://rosettacode.org/wiki/Nth_root#JavaScript + const n = root + const prec = precision + + let x = 1 // Initial guess + for (let i = 0 ; i < prec ; i++) { + x = 1 / n * ((n - 1) * x + (num / Math.pow(x, n - 1))) + } + + return inv ? 1 / x : x +} + +// TODO: handle fractional coefficients +// TODO: isPolynomialTerm should handle fractional coeffs + +// e.g. 4x^2 + 12x + 9 -> (2x + 3)^2 +export const FACTOR_PERFECT_SQUARE_TRINOMIALS = defineRule( + (node) => { + const isFactorable = canApplyRule(FACTOR_SUM_PRODUCT_RULE, node) + if (isFactorable) { + const result = applyRule(FACTOR_SUM_PRODUCT_RULE, node) + return print(result.args[0]) === print(result.args[1]) + ? {node} : null + } + }, + + (node) => { + const result = applyRule(FACTOR_SUM_PRODUCT_RULE, node) + return build.pow(result.args[0], build.number(2)) + } +) + +// TODO: handle multivariable polynomials +// Get degree of a polynomial term +// e.g. 6x^2 -> 2 +const getExponent = (node) => { + if (query.isNumber(node)) { + return 0 + } else if (query.isIdentifier(node)){ + return 1 + } else if (query.isPow(node)) { + return query.getValue(node.args[1]) + } else if (query.isMul(node)){ + return getExponent(node.args[1]) + } else if (query.isNeg(node)) { + const variable = node.args[0] + return getExponent(variable.args[1]) + } else { + return null + } +} + +// TODO: handle multivariable polynomials +// e.g. 2 + 3x^2 + 3x - 4x^3 -> -4x^3 + 3x^2 + 3x + 2 +export const REARRANGE_TERMS = defineRule( + (node) => { + if (query.isAdd(node)) { + return node.args.some(arg => { + return isPolynomialTerm(arg) + }) ? {node} : null + } + }, + + (node) => { + const ordered = node.args.sort(function(a,b) {return getExponent(b) - getExponent(a)}) + return build.add(...ordered) + } +) + +const getFactorPairs = (num) => { + var factors = [] + + for(var i = 1; i <= Math.sqrt(num); i++){ + if(Number.isInteger(num/i)){ + factors.push([i, num/i]) + } + } + + return factors +} + +/* + Cross factoring method + + Example: (6x^2 + 7x + 2) + + 1. Get factors for 6 and 2 + 6: [1,6], [2,3] 2: [1,2] + + 2. For each pair of factors (one from the first + and one from the last term) ... + [1,6] and [1,2] | [2,3] and [1,2] + + 3. Cross multiply the factors and check if + the result matches one of the eight conditions + (the second factor should also multiply to equal the thirdCoef) + [1,6] and [1,2] + + Check: 1 * 2 + 6 * 1 ?= 7 so on and so forth + Once we reach 2 * 2 + 3 * 1 ?= 7, we have found a match. + (Note: 3 * 1 = 3 satisfying the second condition) + + At this point, we find that the correct combination is + : [2,3] and [1,2] -> (2x + 3)(1x + 2) +*/ + +const factor_trinomial_helper = (node) => { + const {constants, coefficientMap} = getCoefficientsAndConstants(node) + const variables = Object.keys(coefficientMap).map(key => parse(key)) + const coeffs = Object.keys(coefficientMap).map(key => coefficientMap[key][0]) + + const firstCoef = query.getValue(coeffs[0]) + const firstPoly = query.isMul(variables[0]) ? variables[0].args : [variables[0]] + + const secondCoef = query.getValue(coeffs[1]) + + let thirdPoly = null; + if (variables[2]) { + if (query.isMul(variables[2])) { + thirdPoly = variables[2].args + } else { + thirdPoly = [variables[2]] + } + } + + let thirdCoef = null + if (thirdPoly) { + thirdCoef = query.getValue(coeffs[2]) + } else if (constants[0]) { + thirdCoef = query.getValue(constants[0]) + } + + const firstFactors = getFactorPairs(firstCoef) + const thirdFactors = getFactorPairs(Math.abs(thirdCoef)) + + let combo + for (var i in firstFactors){ + for (var j in thirdFactors) { + const l1 = firstFactors[i] + const l2 = thirdFactors[j] + // both positive + let one = l1[0] * l2[1] + l1[1] * l2[0] + let two = l1[0] * l2[0] + l1[1] * l2[1] + // both negative + let three = l1[0] * -l2[1] + l1[1] * -l2[0] + let four = l1[0] * -l2[0] + l1[1] * -l2[1] + // l2[0] is negative and l2[1] is positive + let five = l1[0] * l2[1] + l1[1] * -l2[0] + let six = l1[0] * -l2[0] + l1[1] * l2[1] + // l2[0] is positive and l2[1] is negative + let seven = l1[0] * -l2[1] + l1[1] * l2[0] + let eight = l1[0] * l2[0] + l1[1] * -l2[1] + + if (one == secondCoef && l2[0] * l2[1] == thirdCoef){ + combo = [[l1[0],l2[0]],[l1[1],l2[1]]] + } else if (two == secondCoef && l2[0] * l2[1] == thirdCoef){ + combo = [[l1[0],l2[1]],[l1[1],l2[0]]] + } else if (three == secondCoef && -l2[0] * -l2[1] == thirdCoef){ + combo = [[l1[0],-l2[0]],[l1[1],-l2[1]]] + } else if (four == secondCoef && -l2[0] * -l2[1] == thirdCoef){ + combo = [[l1[0],-l2[1]],[l1[1],-l2[0]]] + } else if (five == secondCoef && -l2[0] * l2[1] == thirdCoef){ + combo = [[l1[0],-l2[0]],[l1[1],l2[1]]] + } else if (six == secondCoef && -l2[0] * l2[1] == thirdCoef){ + combo = [[l1[0],l2[1]],[l1[1],-l2[0]]] + } else if (seven == secondCoef && l2[0] * -l2[1] == thirdCoef){ + combo = [[l1[0],l2[0]],[l1[1],-l2[1]]] + } else if (eight == secondCoef && l2[0] * -l2[1] == thirdCoef){ + combo = [[l1[0],-l2[1]],[l1[1],l2[0]]] + } else { + combo = null + } + } + } + return combo +} + +// TODO: handle case when exponent is a polynomial (x^b^4) +// TODO: have a rule to order terms in a polynomial in descending order + +/* + Factoring trinomials algorithm + Assumes expression is in standard form ax^2 + bx + c (descending order) + Assumes GCD has already been factored out +*/ + +// e.g. 12x^2 + 17x + 6 -> (3 x^1 + 2) (4 x^1 + 3) +export const FACTOR_SUM_PRODUCT_RULE = defineRule( + (node) => { + if (query.isAdd(node) && node.args.length === 3) { + const [firstTerm, secondTerm, thirdTerm] = node.args + + // First two terms should be polynomials + // Third term is either number or polynomial + const isTrinomial = + isPolynomialTerm(firstTerm) + && isPolynomialTerm(secondTerm) + && (query.isNumber(thirdTerm) || isPolynomialTerm(thirdTerm)) + + if (isTrinomial) { + let isFactorable = false + const {constants, coefficientMap} = getCoefficientsAndConstants(node) + const variables = Object.keys(coefficientMap).map(key => parse(key)) + const coeffs = Object.keys(coefficientMap).map(key => coefficientMap[key][0]) + + const firstPoly = query.isMul(variables[0]) ? variables[0].args : [variables[0]] + let thirdPoly = null; + if (variables[2]) { + if (query.isMul(variables[2])) { + thirdPoly = variables[2].args + } else { + thirdPoly = [variables[2]] + } + } + + const secondCoef = coeffs[1] + + // Manually generate the second term in the trinomial by multiplying together + // the square root of the variable in the 1st and 3rd terms + + // e.g. 2x^2 + 6x + 3y^2 + // firstMiddle: square root of x^2 = x^1 + // secondMiddle: square root of y^2 = y^1 + // matchSecondTerm: secondCoef (6) * firstMiddle * secondMiddle = 6x^1y^1 + // the second terms don't match and hence this trinomial is not factorable + + // Note: secondMiddle exists only when the third term is a polynomial + + const firstMiddle = firstPoly.map(factor => { + const identifier = factor.args[0] + const exponent = build.number(query.getValue(factor.args[1]) / 2) + return build.pow(identifier, exponent) + }) + + let secondMiddle + if (thirdPoly) { + secondMiddle = thirdPoly.map(factor => { + const identifier = factor.args[0] + const exponent = build.number(query.getValue(factor.args[1]) / 2) + return build.pow(identifier, exponent) + }) + } + + const matchSecondTerm = secondMiddle + ? build.implicitMul(secondCoef, ...firstMiddle, ...secondMiddle) + : build.implicitMul(secondCoef, ...firstMiddle) + + // General conditions to determine factorability: + // All degrees in 1st and 3rd term must be even + // secondTerm = matchSecondTerm + // A valid combination has to exist for the given a, b, and c values + + isFactorable = + firstPoly.every(term => term.args[1] % 2 == 0) + && thirdPoly ? thirdPoly.every(term => term.args[1] % 2 == 0) : true + && print(secondTerm) == print(flattenOperands(matchSecondTerm)) + && factor_trinomial_helper(node) + return isFactorable ? {node} : null + } + } + }, + + (node) => { + const {constants, coefficientMap} = getCoefficientsAndConstants(node) + const variables = Object.keys(coefficientMap).map(key => parse(key)) + + const firstPoly = query.isMul(variables[0]) ? variables[0].args : [variables[0]] + + let thirdPoly = null; + if (variables[2]) { + if (query.isMul(variables[2])) { + thirdPoly = variables[2].args + } else { + thirdPoly = [variables[2]] + } + } + + const combo = factor_trinomial_helper(node) + + const result = build.implicitMul( + ...combo.map(factor => { + const firstFactor = + build.implicitMul( + ...[build.number(factor[0]), ...firstPoly.map( + factor => + {const identifier = factor.args[0] + const exponent = build.number(query.getValue(factor.args[1]) / 2) + return build.pow(identifier, exponent)} + )]) + + const secondFactor = thirdPoly + ? build.implicitMul( + ...[build.number(Math.abs(factor[1])), ...thirdPoly.map( + factor => + {const identifier = factor.args[0] + const exponent = build.number(query.getValue(factor.args[1]) / 2) + return build.pow(identifier, exponent)} + )]) + : build.number(Math.abs(factor[1])) + return Math.sign(factor[1]) < 0 + ? build.sub(firstFactor, secondFactor) + : build.add(firstFactor, secondFactor) + }) + ) + + return result + } +) diff --git a/lib/rules/collect-like-terms.js b/lib/rules/collect-like-terms.js index 25e439f..b1e082d 100644 --- a/lib/rules/collect-like-terms.js +++ b/lib/rules/collect-like-terms.js @@ -11,7 +11,7 @@ const populatePatternString = (pattern, placeholders) => populatePattern(parse(p const pattern = parse('#a #x') const constantPattern = parse('#a') -const isPolynomial = (node) => { +export const isPolynomial = (node) => { return query.isAdd(node) && node.args.every(isPolynomialTerm) } @@ -76,7 +76,7 @@ export const getVariableFactors = (node) => { } } -const getVariableFactorName = (node) => { +export const getVariableFactorName = (node) => { if (query.isIdentifier(node)) { return node.name } else if (query.isPow(node)) { @@ -100,7 +100,7 @@ const isImplicit = (node) => { } } -const getCoefficientsAndConstants = (node) => { +export const getCoefficientsAndConstants = (node) => { const coefficientMap = {} const constants = [] diff --git a/lib/rule-list.js b/lib/simple-rules.js similarity index 87% rename from lib/rule-list.js rename to lib/simple-rules.js index 45f7c5e..201deed 100644 --- a/lib/rule-list.js +++ b/lib/simple-rules.js @@ -5,7 +5,7 @@ import {build, query} from 'math-nodes' import {traverse} from 'math-traverse' import {defineRule, definePatternRule, applyRule, canApplyRule} from './rule' -import {isPolynomialTerm, getCoefficient, getVariableFactors} from './rules/collect-like-terms.js' +import {isPolynomialTerm, getCoefficient, getVariableFactors} from './rules/collect-like-terms' import {clone, getRanges} from './utils' const defineRuleString = (matchPattern, rewritePattern, constraints) => { @@ -195,22 +195,13 @@ export const ABSOLUTE_VALUE = defineRuleString('|-#a|', '#a') // e.g. nthRoot(x^2, 4) -> nthRoot(x^1 ,2) export const CANCEL_EXPONENT = defineRule( (node) => { - // Checks if node isNthRoot, then check if exponent can be simplified - let canCancel = false - if (query.isNthRoot(node)) { - const [radicand, index] = node.args - if (query.isPow(radicand)) { - // get the fraction exponent, simplify it, and - // see if the new fraction = old fraction - const [base, exponent] = radicand.args - if (query.isNumber(exponent) && query.isNumber(index)){ - const oldRoot = build.div(exponent, index) - const newRoot = applyRule(SIMPLIFY_FRACTION, build.div(exponent, index)) - canCancel = print(oldRoot) != print(newRoot) - } - } + let isNthRoot = false + let validRadicand = false + if (query.isApply(node)) { + isNthRoot = (node.op == 'nthRoot') + validRadicand = query.isPow(node.args[0]) } - return (canCancel) ? {node} : null + return (isNthRoot && validRadicand) ? {node} : null }, (node) => { const radicand = node.args[0] @@ -220,7 +211,8 @@ export const CANCEL_EXPONENT = defineRule( // simplify exponent / index // e.g. nthRoot(x^2, 4) -> 2/4 -> 1/2 - const newRoot = applyRule(SIMPLIFY_FRACTION, build.div(exponent, index)) + const newRoot = applyRule(SIMPLIFY_FRACTION, + build.applyNode('div', [exponent, index])) let newExponent = newRoot.args[0] let newIndex = newRoot.args[1] @@ -235,11 +227,14 @@ export const CANCEL_EXPONENT = defineRule( // numerator > denominator && denominator != 1, return nthRoot // e.g nthRoot(x^2, 4) -> 2/4 -> 1/2 -> nthRoot(x^1, 2) if ((exponentVal > indexVal || exponentVal == -1) && indexVal == 1) { - return build.pow(variable, newExponent) + return build.applyNode('pow', [variable, newExponent]) } else if (exponentVal === indexVal) { - return build.pow(variable, build.number(1)) + return build.applyNode('pow', [variable, build.numberNode(1)]) } else { - return build.nthRoot(build.pow(variable, newExponent), newIndex) + return build.applyNode ( + 'nthRoot', + [build.applyNode('pow', [variable, newExponent]), newIndex] + ) } } ) @@ -262,22 +257,28 @@ export const EVALUATE_DISTRIBUTED_NTH_ROOT = defineRule( let isDistributed = query.isMul(node) let canEvaluate = false if (isDistributed) { - canEvaluate = node.args.some(arg => canApplyRule(CANCEL_EXPONENT, arg) || canApplyRule(NTH_ROOT_VALUE, arg)) + canEvaluate = node.args.every(arg => canApplyRule(CANCEL_EXPONENT, arg) || canApplyRule(NTH_ROOT_VALUE, arg)) } return (isDistributed && canEvaluate) ? {node} : null }, (node) => { - const result = build.mul( - ...node.args.map(nthRoot => { + const result = build.applyNode( + 'mul', + node.args.map(nthRoot => { const [radicand, index] = nthRoot.args - const root = build.nthRoot(radicand, index) if (query.isNumber(radicand)) { - return applyRule(NTH_ROOT_VALUE, root) - } else if (canApplyRule(CANCEL_EXPONENT, root)){ - return applyRule(CANCEL_EXPONENT, root) + return applyRule(NTH_ROOT_VALUE, + build.applyNode( + 'nthRoot', + [radicand, index] + )) } else { - return nthRoot + return applyRule(CANCEL_EXPONENT, + build.applyNode( + 'nthRoot', + [radicand, index] + )) } }) ) @@ -293,7 +294,7 @@ export const FACTOR_INTO_PRIME = defineRule( (node) => { const factors = primeFactorization(query.getValue(node)) - return build.mul(...factors.map(build.number)) + return build.applyNode('mul', factors.map(build.numberNode)) } ) @@ -320,9 +321,11 @@ export const GROUP_TERMS_BY_ROOT = defineRule( (acc, val) => acc.concat(val), [] ) - const result = build.nthRoot( - build.mul( - ...flatten(Object.keys(count).map(key => { + const result = build.applyNode( + 'nthRoot', + [build.applyNode( + 'mul', + flatten(Object.keys(count).map(key => { let leftover = count[key] const term = JSON.parse(key) const times = query.getValue(index) @@ -331,16 +334,16 @@ export const GROUP_TERMS_BY_ROOT = defineRule( while (leftover - times > 0) { leftover -= times - args.push(build.mul(...Array(times).fill(term))) + args.push(build.applyNode('mul', Array(times).fill(term))) } const arg = leftover === 1 ? term - : build.mul(...Array(leftover).fill(term)) + : build.applyNode('mul', Array(leftover).fill(term)) args.push(arg) return args })) - ), index + ), index] ) return result diff --git a/yarn.lock b/yarn.lock index e3d8def..c5a18eb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2379,11 +2379,7 @@ math-evaluator@^0.0.9: dependencies: babel-loader "^7.0.0" -math-nodes@^0.1.2: - version "0.1.5" - resolved "https://registry.yarnpkg.com/math-nodes/-/math-nodes-0.1.5.tgz#dfd25c7a9458b91413288ebfcf525cd3a80af6c7" - -math-nodes@^0.1.6: +math-nodes@^0.1.2, math-nodes@^0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/math-nodes/-/math-nodes-0.1.6.tgz#ac38eba233d24c7d3094a0e99035054e55058167"