Skip to content
Open
Show file tree
Hide file tree
Changes from 44 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
04bb0ee
added rules for patterns w/o constraints
aliang8 Apr 30, 2017
f37f48c
added rules test, some errors
aliang8 Apr 30, 2017
5745443
rm file
aliang8 Apr 30, 2017
b462d8a
rm file
aliang8 Apr 30, 2017
3ca4c7f
format
aliang8 Apr 30, 2017
adcbc10
fixed tabing, remove comments, added rules, need to check variables
aliang8 Apr 30, 2017
3cb89d4
added extra test cases, not done
aliang8 Apr 30, 2017
75ab164
not sure what's up with these tests
aliang8 Apr 30, 2017
3beef3e
finished adding cases, still some errors
aliang8 May 1, 2017
c35f087
fixed merge errors
aliang8 May 2, 2017
20bc705
one last test
aliang8 May 2, 2017
336c5c3
fixed tests and yarn
aliang8 May 2, 2017
3dbb8cd
done
aliang8 May 2, 2017
2335275
removed lint, added TODO
aliang8 May 2, 2017
d91eb26
merging
aliang8 May 5, 2017
7fdbd7e
rebasing
aliang8 May 7, 2017
e8e86a1
merge
aliang8 May 9, 2017
7a3e271
merged
aliang8 May 10, 2017
fbd4938
merge
aliang8 May 14, 2017
3ea7468
merge
aliang8 May 15, 2017
96d2bb9
merged
aliang8 May 18, 2017
caa0ae0
addingPolynomials
aliang8 May 20, 2017
14a091f
fixed comments
aliang8 May 20, 2017
aa21a98
added more tests, one of them fails
aliang8 May 20, 2017
08b3ca1
remove . file
aliang8 May 20, 2017
03ffdf1
merge
aliang8 May 20, 2017
648f0f7
remove alphabetize
aliang8 May 20, 2017
632b525
fixed comments
aliang8 May 20, 2017
7403644
removed comment
aliang8 May 20, 2017
2cb2f76
starting to work on LCM
aliang8 May 20, 2017
fa1c35c
fractional polynomial and rearrange coeffs
aliang8 May 21, 2017
85f51d0
remove . file
aliang8 May 21, 2017
9f81447
fixed failing case
aliang8 May 21, 2017
ac44821
multiplyingPolynomials
aliang8 May 21, 2017
56d4872
rules.js
aliang8 May 21, 2017
e02838b
multiplyingPolynomials
aliang8 May 21, 2017
06914d0
merge
aliang8 May 21, 2017
510afb7
fixed comments
aliang8 May 21, 2017
eb95348
working on adding one exponent
aliang8 May 21, 2017
2e67877
exponent of one
aliang8 May 22, 2017
b485d9e
fixed adding exponent of one
aliang8 May 22, 2017
7784dd7
remove comment
aliang8 May 22, 2017
ef26741
fixing comments
aliang8 May 23, 2017
894bbbc
fixing last comment
aliang8 May 23, 2017
d73a190
Merge branch 'master' into multiplyingPolynomials
aliang8 May 26, 2017
9741fbf
minor fixes
aliang8 May 28, 2017
ba2d8f6
minor fixes
aliang8 May 28, 2017
da48fc1
merge
aliang8 Jun 8, 2017
d5d9576
merge
aliang8 Jun 8, 2017
8bf1170
change rules
aliang8 Jun 8, 2017
9d07321
fixed test cases
aliang8 Jun 8, 2017
5b4a5f2
merge
aliang8 Jun 11, 2017
bb1dbd4
merge with master
aliang8 Jun 15, 2017
0e1724a
update multiplying polynomial functionality
aliang8 Jun 15, 2017
2599741
remove random
aliang8 Jun 15, 2017
26b3697
fixed weird behavior
aliang8 Jun 19, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 148 additions & 5 deletions lib/rules.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import {parse, print} from 'math-parser'
import evaluate from 'math-evaluator'
import {query} from 'math-nodes'
import {traverse} from 'math-traverse'
import {build, query} from 'math-nodes'
import {traverse, replace} from 'math-traverse'

import {defineRule, definePatternRule} from './matcher'
import {defineRule, definePatternRule, canApplyRule, applyRule} from './matcher'
import {isPolynomialTerm, getCoefficient, getVariableFactors, getCoefficientsAndConstants} from './rules/collect-like-terms.js'
import {clone, getRanges} from './utils'

const defineRuleString = (matchPattern, rewritePattern, constraints) => {
Expand Down Expand Up @@ -151,7 +152,7 @@ export const SIMPLIFY_SIGNS = defineRuleString('#a / -#b', '-#a / #b')

// ADDING FRACTIONS

export const ADD_NUMERATORS =
export const COMBINE_NUMERATORS =
defineRuleString('#a_0 / #b + ...', '(#a_0 + ...) / #b')

export const COMMON_DENOMINATOR =
Expand Down Expand Up @@ -184,9 +185,60 @@ export const ABSOLUTE_VALUE = defineRuleString('|-#a|', '#a')

// MULTIPLYING POLYNOMIALS

// e.g. 6x y z -> 6 (x^1y^1z^1) (where x, y, z are
// separate identifiers)

export const ADD_EXPONENT_OF_ONE_HELPER = defineRule(
(node) => {
const hasConstantVariable = query.isMul(node) && node.args.some(query.isIdentifier)

return (hasConstantVariable) ? {node} : null
},

(node) => {
const result = build.applyNode(
'mul',
node.args.map(arg => {
if (query.isIdentifier(arg)){
return build.applyNode('pow', [arg, build.numberNode(1)])
}
return arg
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you use an else block here or change this to ternary statement?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

}), {implicit: node.implicit}
)
return result
}
)

// e.g. x^2 * x -> x^2 * x^1
// export const ADD_EXPONENT_OF_ONE = ...
export const ADD_EXPONENT_OF_ONE = defineRule(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This rule could be implemented without the helper once we address #23. Can you add a TODO here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

(node) => {
let isMulOfPolynomials = false

if (query.isMul(node)) {
const {constants, coefficientMap} = getCoefficientsAndConstants(node)
isMulOfPolynomials = Object.keys(coefficientMap).length > 1
|| Object.keys(coefficientMap)
.some(key => coefficientMap[key].length > 1)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is kind of confusing. The coefficientMap is designed for collecting like terms, but makes this task more complicated. Instead try using getVariableFactors.

Copy link
Contributor Author

@aliang8 aliang8 May 28, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getVariableFactors doesn't really help in the case of :
2x + 3x^2 * x y z => 2 x + 3 x^2 * x^1 y^1 z^1

It returns []

}

return (isMulOfPolynomials && !node.implicit) ? {node} : null
},

(node) => {
const result = build.applyNode(
'mul',
node.args.map(arg => {
if (canApplyRule(ADD_EXPONENT_OF_ONE_HELPER, arg)) {
return applyRule(ADD_EXPONENT_OF_ONE_HELPER, arg)
} else if (query.isIdentifier(arg)) {
return build.applyNode('pow', [arg, build.numberNode(1)])
}
return arg
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you put this in an else block?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

})
)
return result
}
)

// EXPONENT RULES

Expand All @@ -196,6 +248,97 @@ export const PRODUCT_RULE = defineRuleString('#a^#b_0 * ...', '#a^(#b_0 + ...)')
// e.g. x^5 / x^3 -> x^(5 - 3)
export const QUOTIENT_RULE = defineRuleString('#a^#p / #a^#q', '#a^(#p - #q)')

// e.g. 3x^2 * 2x^2 -> (3 * 2)(x^2 * x^2)
export const MULTIPLY_COEFFICIENTS = defineRule(
(node) => {
return canApplyRule(ADD_EXPONENT_OF_ONE, node) ? {node} : null
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't ADD_EXPONENT_OF_ONE only be matching things like x or x * x? This matching function though seems to be accepting x^2 which I would expect ADD_EXPONENT_OF_ONE to ignore.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess ADD_EXPONENT_OF_ONE_HELPER does what you want. ADD_EXPONENT_OF_ONE looks through the given expression and applies the helper where necessary.

},
(node) => {
const terms = []
const coeffs = []
traverse(node, {
enter(node) {
if(query.isPow(node)){
terms.push(node)
}
}
})
node.args.forEach(arg =>
coeffs.push(getCoefficient(arg)))

const newCoeff = build.applyNode(
'mul',
coeffs
)
const newVariable = build.applyNode(
'mul',
terms
)
const result = build.applyNode(
'mul',
[newCoeff, newVariable],
{implicit: true}
)
return result
}
)

// e.g. 3x^3 * y^2 -> 3 (x^3 y^2)
export const MULTIPLY_POLYNOMIAL_TERMS = defineRule(
(node) => {
return canApplyRule(MULTIPLY_COEFFICIENTS, node) ? {node} : null
},

(node) => {
const terms = {}
traverse(node, {
enter(node) {
if(query.isPow(node)){
const variable = print(node.args[0])
const exponent = node.args[1]
if(!(variable in terms)){
terms[variable] = [query.getValue(exponent)]
} else {
terms[variable].push(query.getValue(exponent))
}
}
}
})

let newVariable

if(Object.keys(terms).length > 1) {
newVariable = build.applyNode(
'mul',
Object.keys(terms).map(key => {
const exponent = terms[key].reduce((a,b) => a + b)
const expression = `${key}^${exponent}`
return parse(expression)
}), {implicit: true}
)
} else {
newVariable = Object.keys(terms).map(key => {
const exponent = terms[key].reduce((a,b) => a + b)
const expression = `${key}^${exponent}`
return parse(expression)
})
newVariable = newVariable[0]
}

let newCoeff = 1
node.args.forEach(arg => newCoeff *= query.getValue(getCoefficient(arg)))

const newCoeffNode = build.numberNode(newCoeff)

const result = build.applyNode(
'mul',
[newCoeffNode, newVariable],
{implicit: true}
)
return result
}
)

// e.g. (a * b)^x -> a^x * b^x
export const POWER_OF_A_PRODUCT =
defineRuleString('(#a_0 * ...)^#b', '#a_0^#b * ...')
Expand Down
8 changes: 4 additions & 4 deletions lib/rules/collect-like-terms.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const isPolynomial = (node) => {
// but really we want #b to match either a number or a fraction with numbers for
// numerator and denominator

const isPolynomialTerm = (node) => {
export const isPolynomialTerm = (node) => {
if (query.isNumber(node)) {
return true
} else if (query.isIdentifier(node)) {
Expand All @@ -35,7 +35,7 @@ const isPolynomialTerm = (node) => {
}
}

const getCoefficient = (node) => {
export const getCoefficient = (node) => {
if (query.isNumber(node)) {
return node
} else if (query.isIdentifier(node) || query.isPow(node)) {
Expand All @@ -62,7 +62,7 @@ const isVariableFactor = (node) =>
(query.isNumber(node.args[1]) || isVariableFactor(node.args[1]))


const getVariableFactors = (node) => {
export const getVariableFactors = (node) => {
if (isVariableFactor(node)) {
return [node]
} else if (query.isMul(node)) {
Expand Down Expand Up @@ -99,7 +99,7 @@ const isImplicit = (node) => {
}
}

const getCoefficientsAndConstants = (node) => {
export const getCoefficientsAndConstants = (node) => {
const coefficientMap = {}
const constants = []

Expand Down
27 changes: 26 additions & 1 deletion test/rules_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ describe('rules', () => {
['x^((x + 1) / -1)', 'x^(-(x + 1) / 1)'],
])

suite('add numerators', rules.ADD_NUMERATORS, [
suite('add numerators', 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'],
Expand Down Expand Up @@ -210,6 +210,17 @@ describe('rules', () => {
['x^(|-(x + 1)|)', 'x^(x + 1)'],
])

suite('adding exponent of one helper', rules.ADD_EXPONENT_OF_ONE_HELPER, [
['6x y z', '6 x^1 y^1 z^1'],
['2x y^2 z', '2 x^1 y^2 z^1'],
])

suite('adding exponent of one', rules.ADD_EXPONENT_OF_ONE, [
['x^2 * x', 'x^2 * x^1'],
['x^2 * 2 * x * x', 'x^2 * 2 * x^1 * x^1'],
['2x + 3x^2 * x y z', '2 x + 3 x^2 * x^1 y^1 z^1'],
])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix test cases and uncomment.


suite('collects like terms', rules.COLLECT_LIKE_TERMS, [
['2x + 1 - 2x', '(2 x - 2 x) + 1'],
['2x + 1 - x', '(2 x - x) + 1'],
Expand Down Expand Up @@ -294,6 +305,20 @@ describe('rules', () => {
['x^-a / x^-b', 'x^(-a - -b)'],
])

suite('multiplying coefficients', rules.MULTIPLY_COEFFICIENTS, [
['x^2 * x^1', '(1 * 1) (x^2 * x^1)'],
['3x^2 * x^2', '(3 * 1) (x^2 * x^2)'],
['x^3 * 2y^2', '(1 * 2) (x^3 * y^2)'],
['x^3 + 2x + 3x^1 * 5x^1', 'x^3 + 2 x + (3 * 5) (x^1 * x^1)'],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice test cases. I was thinking it'd be nice to be able to check similar inputs which fail to meet the rule's criteria. I've added a task for it #36.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a test case involving the product of three polynomials that are the same?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

])

suite('multiplying polynomials', rules.MULTIPLY_POLYNOMIAL_TERMS, [
['x^2 * x^1', '1 x^3'],
['3x^2 * x^2', '3 x^4'],
['x^3 * 2y^2', '2 (x^3 y^2)'],
['x^3 + 2x + 3x^1 * 5x^1', 'x^3 + 2 x + 15 x^2'],
])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does a couple of steps:

  • multiplying the coefficients
  • actually doing evaluating the product

Mathsteps still wants all of the steps. I feel like we need to take a step back and think about we can combine multiple steps. We could use MULTIPLY_COEFFICIENTS + SIMPLIFY_ARITHMETIC, but we currently don't have a way to tell SIMPLIFY_ARITHMETIC that we want it to simplify only the nodes that MULTIPLY_COEFFICIENTS manipulated.


suite('power of a product', rules.POWER_OF_A_PRODUCT, [
['(2*3)^x', '2^x * 3^x'],
['(2*3*5)^x', '2^x * 3^x * 5^x'],
Expand Down