Commit 52dcf02f authored by Taddeus Kroes's avatar Taddeus Kroes

Modified parser to fit MathJax syntax.

- Division now has a higher precedence than multiplication.
- Integrals and derivatives now have a precedence higher than addition, but
  lower than multiplication.
- 'a2' is now translated to 'a * 2' instead of 'a ^ 2'.
parent fcfc7cef
graph_drawing @ aade5fc5
Subproject commit ade1a95046e5539a4f11535892061e05b7c23b95
Subproject commit aade5fc51f4b19e84d180fcff9b6c6d89db93667
......@@ -86,12 +86,13 @@ class Parser(BisonParser):
# ------------------------------
precedences = (
('left', ('COMMA', )),
('left', ('INTEGRAL', 'DERIVATIVE')),
('left', ('OR', )),
('left', ('AND', )),
('left', ('EQ', )),
('left', ('MINUS', 'PLUS', 'NEG')),
('left', ('TIMES', 'DIVIDE')),
('left', ('INTEGRAL', 'DERIVATIVE')),
('left', ('TIMES', )),
('left', ('DIVIDE', )),
('right', ('FUNCTION', )),
('right', ('POW', )),
('left', ('SUB', )),
......@@ -179,12 +180,11 @@ class Parser(BisonParser):
# a[ -> a * [
+ '|(\))\s*([\x00-\x09\x0b-\x19a-z0-9])' # )a -> ) * a
+ '|([\x00-\x09\x0b-\x19a-z])\s*'
+ '([\x00-\x09\x0b-\x19a-z])' # ab -> a * b
+ '([\x00-\x09\x0b-\x19a-z0-9])' # ab -> a * b
+ '|(\|)(\|)' # || -> | * |
+ '|([0-9])\s*([\x00-\x09\x0b-\x19a-z])' # 4a -> 4 * a
+ '|([\x00-\x09\x0b-\x19a-z])([0-9])' # a4 -> a ^ 4
+ '|([\x00-\x09\x0b-\x19a-z0-9])(\s+[0-9]))' # a 4 -> a * 4
# 4 4 -> 4 * 4
+ '|([\x00-\x09\x0b-\x190-9])(\s+[0-9]))' # 4 4 -> 4 * 4
)
def preprocess_data(match):
......@@ -199,8 +199,8 @@ class Parser(BisonParser):
# If all characters on the right are numbers. e.g. "a4", the
# expression implies exponentiation. Make sure ")4" is not
# converted into an exponentiation, because that's multiplication.
if left != ')' and not left.isdigit() and right.isdigit():
return '%s^%s' % (left, right)
#if left != ')' and not left.isdigit() and right.isdigit():
# return '%s^%s' % (left, right)
# match: ab | abc | abcd (where left = "a")
return '*'.join([left] + list(re.sub(r'^ +', '', right)))
......@@ -219,9 +219,14 @@ class Parser(BisonParser):
for i, keyword in enumerate(words):
data = data.replace(chr(i), keyword)
# Fix TIMES operator next to OR
# Remove TIMES operators around OR that the preprocessor put there
data = re.sub(r'\*?vv\*?', 'vv', data)
# Add parentheses to integrals with matching 'dx' so that the 'dx' acts
# as a right parenthesis for the integral function
data = re.sub(r'(int(?:_.+\^.+\*)?)(.+?)(\*d\*[a-z])',
'\\1(\\2)\\3', data)
if self.verbose and data_before != data: # pragma: nocover
print 'hook_read_after() modified the input data:'
print 'before:', repr(data_before)
......
......@@ -19,7 +19,7 @@ class TestB1Ch10(unittest.TestCase):
('(2+3/7)^4',
N('^', N('+', L(2), N('/', L(3), L(7))), L(4))
),
('x3*x2*x',
('x^3*x^2*x',
N('*',
N('*',
N('^', L('x'), L(3)),
......@@ -27,10 +27,10 @@ class TestB1Ch10(unittest.TestCase):
L('x')
)
),
('-x3*-2x5',
('-x^3*-2x^5',
-(L('x') ** L(3) * -(L(2) * L('x') ** L(5)))
),
('(7x2y3)^2/(7x2y3)',
('(7x^2y^3)^2/(7x^2y^3)',
N('/',
N('^',
N('*',
......
......@@ -34,9 +34,8 @@ class TestCalc(unittest.TestCase):
expressions = [
('xy', N('*', L('x'), L('y'))),
('2x', N('*', L(2), L('x'))),
('x4', N('^', L('x'), L(4))),
('x4', N('*', L('x'), L(4))),
('3 4', N('*', L(3), L(4))),
('xy4', N('*', L('x'), N('^', L('y'), L(4)))),
('(x)4', N('*', L('x'), L(4))),
('(3+4)2', N('*', N('+', L(3), L(4)), L(2))),
]
......
......@@ -15,7 +15,7 @@ class TestLeidenOefenopgave(TestCase):
return
for exp, solution in [
('-5(x2 - 3x + 6)', '-30 + 15x - 5x ^ 2'),
('-5(x^2 - 3x + 6)', '-30 + 15x - 5x ^ 2'),
('(x+1)^2', 'x ^ 2 + 2x + 1'),
('(x-1)^2', 'x ^ 2 - 2x + 1'),
('(2x+x)*x', '3x ^ 2'),
......@@ -172,7 +172,7 @@ class TestLeidenOefenopgave(TestCase):
def test_4_3(self):
self.assertRewrite([
'(7/3)(3/5)',
'7 * 3 / (3 * 5)',
'(7 * 3) / (3 * 5)',
'21 / (3 * 5)',
'21 / 15',
'7 / 5',
......@@ -184,7 +184,7 @@ class TestLeidenOefenopgave(TestCase):
def test_4_5(self):
self.assertRewrite([
'1/4 * 1/x',
'1 / 4 / x',
'(1 * 1) / (4x)',
'1 / (4x)',
])
......
......@@ -4,7 +4,7 @@ from tests.rulestestcase import RulesTestCase as TestCase
class TestLeidenOefenopgaveV12(TestCase):
def test_1_a(self):
self.assertRewrite([
'-5(x2 - 3x + 6)',
'-5(x^2 - 3x + 6)',
'-(5x ^ 2 + 5(-3x) + 5 * 6)',
'-(5x ^ 2 - 5 * 3x + 5 * 6)',
'-(5x ^ 2 - 15x + 5 * 6)',
......@@ -57,44 +57,44 @@ class TestLeidenOefenopgaveV12(TestCase):
def test_2_a(self):
self.assertRewrite([
'(a2b^-1)^3(ab2)',
'(a ^ 2 * (1 / b ^ 1)) ^ 3 * ab ^ 2',
'(a ^ 2 * (1 / b)) ^ 3 * ab ^ 2',
'(1a ^ 2 / b) ^ 3 * ab ^ 2',
'(a^2b^-1)^3(ab^2)',
'(a ^ 2 * 1 / b ^ 1) ^ 3 * ab ^ 2',
'(a ^ 2 * 1 / b) ^ 3 * ab ^ 2',
'((1a ^ 2) / b) ^ 3 * ab ^ 2',
'(a ^ 2 / b) ^ 3 * ab ^ 2',
'(a ^ 2) ^ 3 / b ^ 3 * ab ^ 2',
'a ^ (2 * 3) / b ^ 3 * ab ^ 2',
'a ^ 6 / b ^ 3 * ab ^ 2',
'a ^ 6 * a / b ^ 3 * b ^ 2',
'(a ^ 6 * a) / b ^ 3 * b ^ 2',
'a ^ (6 + 1) / b ^ 3 * b ^ 2',
'a ^ 7 / b ^ 3 * b ^ 2',
'a ^ 7 * b ^ 2 / b ^ 3',
'b ^ 2 / b ^ 3 * (a ^ 7 / 1)',
'b ^ (2 - 3)(a ^ 7 / 1)',
'b ^ (-1)(a ^ 7 / 1)',
'1 / b ^ 1 * (a ^ 7 / 1)',
'1 / b * (a ^ 7 / 1)',
'(a ^ 7 * b ^ 2) / b ^ 3',
'b ^ 2 / b ^ 3 * a ^ 7 / 1',
'b ^ (2 - 3)a ^ 7 / 1',
'b ^ (-1)a ^ 7 / 1',
'1 / b ^ 1 * a ^ 7 / 1',
'1 / b * a ^ 7 / 1',
'1 / b * a ^ 7',
'1a ^ 7 / b',
'(1a ^ 7) / b',
'a ^ 7 / b',
])
def test_2_b(self):
self.assertRewrite([
'a3b2a3',
'a^3b^2a^3',
'a ^ (3 + 3)b ^ 2',
'a ^ 6 * b ^ 2',
])
#def test_2_c(self):
# self.assertRewrite([
# 'a5+a3',
# 'a^5+a^3',
# 'a ^ 5 + a ^ 3',
# ])
def test_2_d(self):
self.assertRewrite([
'a2+a2',
'a^2+a^2',
'(1 + 1)a ^ 2',
'2a ^ 2',
])
......@@ -102,8 +102,8 @@ class TestLeidenOefenopgaveV12(TestCase):
def test_2_e(self):
self.assertRewrite([
'4b^-2',
'4(1 / b ^ 2)',
'1 * 4 / b ^ 2',
'4 * 1 / b ^ 2',
'(1 * 4) / b ^ 2',
'4 / b ^ 2',
])
......@@ -113,7 +113,7 @@ class TestLeidenOefenopgaveV12(TestCase):
'4 ^ (-2)b ^ (-2)',
'1 / 4 ^ 2 * b ^ (-2)',
'1 / 16 * b ^ (-2)',
'1 / 16 * (1 / b ^ 2)',
'1 * 1 / (16b ^ 2)',
'1 / 16 * 1 / b ^ 2',
'(1 * 1) / (16b ^ 2)',
'1 / (16b ^ 2)',
])
......@@ -240,7 +240,7 @@ class TestNode(RulesTestCase):
self.assertEqual(str(tree('int x ^ 2 dx')), 'int x ^ 2 dx')
self.assertEqual(str(tree('int x ^ 2 dy')), 'int x ^ 2 dy')
self.assertEqual(str(tree('int x ^ 2 dy')), 'int x ^ 2 dy')
self.assertEqual(str(tree('int x + 1')), 'int (x + 1) dx')
self.assertEqual(str(tree('int x + 1')), 'int x dx + 1')
self.assertEqual(str(tree('int_a^b x ^ 2')), 'int_a^b x ^ 2 dx')
self.assertEqual(str(tree('int_(a-b)^(a+b) x ^ 2')),
......@@ -259,7 +259,7 @@ class TestNode(RulesTestCase):
self.assertEqual(infinity(), tree('oo'))
def test_absolute(self):
self.assertEqual(absolute(tree('x2')), tree('|x2|'))
self.assertEqual(absolute(tree('x^2')), tree('|x^2|'))
def test_sin(self):
self.assertEqual(sin(tree('x')), tree('sin(x)'))
......@@ -288,9 +288,9 @@ class TestNode(RulesTestCase):
def test_integral(self):
x2, x, y, a, b = tree('x ^ 2, x, y, a, b')
self.assertEqual(integral(x2, x), tree('int x2 dx'))
self.assertEqual(integral(x2, x, a, b), tree('int_a^b x2 dx'))
self.assertEqual(integral(x2, y, a, b), tree('int_a^b x2 dy'))
self.assertEqual(integral(x2, x), tree('int x^2 dx'))
self.assertEqual(integral(x2, x, a, b), tree('int_a^b x^2 dx'))
self.assertEqual(integral(x2, y, a, b), tree('int_a^b x^2 dy'))
def test_indef(self):
x2, a, b, expect = tree('x ^ 2, a, b, [x ^ 2]_a^b')
......
......@@ -52,7 +52,7 @@ class TestParser(RulesTestCase):
def test_preprocessor(self):
self.assertEqual(tree('ab'), tree('a * b'))
self.assertEqual(tree('abc'), tree('a * b * c'))
self.assertEqual(tree('a2'), tree('a ^ 2'))
self.assertEqual(tree('a2'), tree('a * 2'))
self.assertEqual(tree('a 2'), tree('a * 2'))
self.assertEqual(tree('2a'), tree('2 * a'))
self.assertEqual(tree('2(a + b)'), tree('2 * (a + b)'))
......@@ -91,8 +91,8 @@ class TestParser(RulesTestCase):
self.assertEqual(tree('d/dx x ^ 2'), der(exp, x))
self.assertEqual(tree('d / dx x ^ 2'), der(exp, x))
self.assertEqual(tree('d/dx x ^ 2 + x'), der(exp + x, x))
self.assertEqual(tree('(d/dx x ^ 2) + x'), der(exp, x) + x)
self.assertEqual(tree('d/dx x ^ 2 + x'), der(exp, x) + x)
self.assertEqual(tree('d/dx (x ^ 2 + x)'), der(exp + x, x))
self.assertEqual(tree('d/d'), d / d)
# FIXME: self.assertEqual(tree('d(x ^ 2)/dx'), der(exp, x))
......@@ -120,17 +120,27 @@ class TestParser(RulesTestCase):
x, y, dx, a, b, l2 = tree('x, y, dx, a, b, 2')
self.assertEqual(tree('int x'), integral(x, x))
self.assertEqual(tree('int x2'), integral(x ** 2, x))
self.assertEqual(tree('int x2 dx'), integral(x ** 2, x))
self.assertEqual(tree('int x2 dy'), integral(x ** 2, y))
self.assertEqual(tree('int x ^ 2'), integral(x ** 2, x))
self.assertEqual(tree('int x ^ 2 dx'), integral(x ** 2, x))
self.assertEqual(tree('int x ^ 2 dy'), integral(x ** 2, y))
self.assertEqual(tree('int_a^b x2'), integral(x ** 2, x, a, b))
self.assertEqual(tree('int_a^b x2 dy'), integral(x ** 2, y, a, b))
self.assertEqual(tree('int_(a-b)^(a+b) x2'),
self.assertEqual(tree('int_a^b x ^ 2'), integral(x ** 2, x, a, b))
self.assertEqual(tree('int_a^b x ^ 2 dy'), integral(x ** 2, y, a, b))
self.assertEqual(tree('int_(a-b)^(a+b) x ^ 2'),
integral(x ** 2, x, a - b, a + b))
self.assertEqual(tree('int_a^b 2x'), integral(l2 * x, x, a, b))
self.assertEqual(tree('int_a^b2 x'), integral(x, x, a, b ** 2))
self.assertEqual(tree('int_a^b^2 x'), integral(x, x, a, b ** 2))
self.assertEqual(tree('int_a^(b2) x'), integral(x, x, a, b * 2))
self.assertEqual(tree('int x ^ 2 + 1'), integral(x ** 2, x) + 1)
self.assertEqual(tree('int x ^ 2 + 1 dx'), integral(x ** 2 + 1, x))
self.assertEqual(tree('int_a^b x ^ 2 dx'), integral(x ** 2, x, a, b))
self.assertEqual(tree('int_a^(b2) x ^ 2 + 1 dx'),
integral(x ** 2 + 1, x, a, b * 2))
self.assertEqual(tree('int_(a^2)^b x ^ 2 + 1 dx'),
integral(x ** 2 + 1, x, a ** 2, b))
def test_indefinite_integral(self):
x2, a, b = tree('x ^ 2, a, b')
......@@ -141,7 +151,7 @@ class TestParser(RulesTestCase):
x = tree('x')
self.assertEqual(tree('|x|'), absolute(x))
self.assertEqual(tree('|x2|'), absolute(x ** 2))
self.assertEqual(tree('|x2|'), absolute(x * 2))
def test_find_possibilities_basic(self):
l1, l2 = root = tree('1 + 2')
......@@ -157,3 +167,6 @@ class TestParser(RulesTestCase):
def test_no_expression_error(self):
self.assertRaises(RuntimeError, ParserWrapper(Parser).run, ['', '?'])
def test_precedence(self):
self.assertEqual(tree('ab / cd'), tree('a * (b / c) * d'))
......@@ -109,8 +109,8 @@ class TestRulesDerivatives(RulesTestCase):
"x ^ x * ([x]' * ln(x) + x[ln(x)]')",
"x ^ x * (1ln(x) + x[ln(x)]')",
"x ^ x * (ln(x) + x[ln(x)]')",
"x ^ x * (ln(x) + x(1 / x))",
"x ^ x * (ln(x) + 1x / x)",
"x ^ x * (ln(x) + x * 1 / x)",
"x ^ x * (ln(x) + (1x) / x)",
"x ^ x * (ln(x) + x / x)",
"x ^ x * (ln(x) + 1)",
"x ^ x * ln(x) + x ^ x * 1",
......
......@@ -198,14 +198,14 @@ class TestRulesFractions(RulesTestCase):
self.assertEqual(divide_by_fraction(root, (a, b, c)), -(a * c / b))
def test_match_extract_fraction_terms(self):
root, a, b, c = tree('ab / (ca), a, b, c')
root, a, b, c = tree('(ab) / (ca), a, b, c')
n, d = root
self.assertEqualPos(match_extract_fraction_terms(root),
[P(root, extract_fraction_terms, (Scope(n), Scope(d), a, a))])
lscp = lambda l: Scope(N(OP_MUL, l))
n, d = root = tree('ab / a')
n, d = root = tree('(ab) / a')
self.assertEqualPos(match_extract_fraction_terms(root),
[P(root, extract_fraction_terms, (Scope(n), lscp(d), a, a))])
......@@ -213,7 +213,7 @@ class TestRulesFractions(RulesTestCase):
self.assertEqualPos(match_extract_fraction_terms(root),
[P(root, extract_fraction_terms, (lscp(n), Scope(d), a, a))])
n, d = root = tree('abc / (cba)')
n, d = root = tree('(abc) / (cba)')
self.assertEqualPos(match_extract_fraction_terms(root),
[P(root, extract_fraction_terms, (Scope(n), Scope(d), a, a)),
P(root, extract_fraction_terms, (Scope(n), Scope(d), b, b)),
......@@ -222,19 +222,19 @@ class TestRulesFractions(RulesTestCase):
root = tree('a / a')
self.assertEqualPos(match_extract_fraction_terms(root), [])
(ap, b), aq = n, d = root = tree('a ^ p * b / a ^ q')
(ap, b), aq = n, d = root = tree('(a ^ p * b) / a ^ q')
self.assertEqualPos(match_extract_fraction_terms(root),
[P(root, extract_fraction_terms, (Scope(n), lscp(d), ap, aq))])
(a, b), aq = n, d = root = tree('a * b / a ^ q')
(a, b), aq = n, d = root = tree('(ab) / a ^ q')
self.assertEqualPos(match_extract_fraction_terms(root),
[P(root, extract_fraction_terms, (Scope(n), lscp(d), a, aq))])
(ap, b), a = n, d = root = tree('a ^ p * b / a')
(ap, b), a = n, d = root = tree('(a ^ p * b) / a')
self.assertEqualPos(match_extract_fraction_terms(root),
[P(root, extract_fraction_terms, (Scope(n), lscp(d), ap, a))])
(l2, a), l3 = n, d = root = tree('2a / 3')
(l2, a), l3 = n, d = root = tree('(2a) / 3')
self.assertEqualPos(match_extract_fraction_terms(root),
[P(root, extract_nominator_term, (2, a))])
......@@ -242,16 +242,16 @@ class TestRulesFractions(RulesTestCase):
self.assertEqualPos(match_extract_fraction_terms(root),
[P(root, extract_nominator_term, (1, a))])
root = tree('2*4 / 3')
root = tree('(2 * 4) / 3')
self.assertEqualPos(match_extract_fraction_terms(root), [])
n, d = root = tree('2a / 2')
n, d = root = tree('(2a) / 2')
self.assertEqualPos(match_extract_fraction_terms(root),
[P(root, extract_nominator_term, (2, a)),
P(root, extract_fraction_terms, (Scope(n), lscp(d), 2, 2))])
def test_extract_nominator_term(self):
root, expect = tree('2a / 3, 2 / 3 * a')
root, expect = tree('(2a) / 3, 2 / 3 * a')
l2, a = root[0]
self.assertEqual(extract_nominator_term(root, (l2, a)), expect)
......@@ -259,28 +259,28 @@ class TestRulesFractions(RulesTestCase):
self.assertEqual(extract_nominator_term(root, (l1, root[0])), expect)
def test_extract_fraction_terms_basic(self):
root, expect = tree('ab / (ca), a / a * (b / c)')
root, expect = tree('(ab) / (ca), a / a * b / c')
n, d = root
self.assertEqual(extract_fraction_terms(root,
(Scope(n), Scope(d), n[0], d[1])), expect)
def test_extract_fraction_terms_leaf(self):
root, expect = tree('ba / a, a / a * (b / 1)')
root, expect = tree('(ba) / a, a / a * b / 1')
n, d = root
self.assertEqual(extract_fraction_terms(root,
(Scope(n), Scope(N(OP_MUL, d)), n[1], d)), expect)
root, expect = tree('a / (ab), a / a * (1 / b)')
root, expect = tree('a / (ab), a / a * 1 / b')
n, d = root
self.assertEqual(extract_fraction_terms(root,
(Scope(N(OP_MUL, n)), Scope(d), n, d[0])), expect)
def test_extract_fraction_terms_chain(self):
self.assertRewrite([
'a ^ 3 * 4 / (a ^ 2 * 5)',
'a ^ 3 / a ^ 2 * (4 / 5)',
'a ^ (3 - 2)(4 / 5)',
'a ^ 1 * (4 / 5)',
'a(4 / 5)',
'(a ^ 3 * 4) / (a ^ 2 * 5)',
'a ^ 3 / a ^ 2 * 4 / 5',
'a ^ (3 - 2)4 / 5',
'a ^ 1 * 4 / 5',
'a * 4 / 5',
# FIXME: '4 / 5 * a',
])
......@@ -37,7 +37,7 @@ class TestRulesIntegrals(RulesTestCase):
self.assertEqual(solve_integral(root, F), Fx(b) - Fx(a))
def test_solve_indef(self):
root, expect = tree('[x ^ 2]_a^b, b2 - a2')
root, expect = tree('[x ^ 2]_a^b, b ^ 2 - a ^ 2')
self.assertEqual(solve_indef(root, ()), expect)
def test_match_integrate_variable_power(self):
......@@ -98,11 +98,11 @@ class TestRulesIntegrals(RulesTestCase):
[P(root, split_negation_to_constant)])
def test_split_negation_to_constant(self):
root, expect = tree('int -x2 dx, int (-1)x2 dx')
root, expect = tree('int -x ^ 2 dx, int (-1)x ^ 2 dx')
self.assertEqual(split_negation_to_constant(root, ()), expect)
def test_factor_out_constant(self):
root, expect = tree('int cx2 dx, c int x2 dx')
root, expect = tree('int cx dx, c int x dx')
c, x2 = cx2 = root[0]
self.assertEqual(factor_out_constant(root, (Scope(cx2), c)), expect)
......@@ -124,7 +124,7 @@ class TestRulesIntegrals(RulesTestCase):
def test_match_division_integral_chain(self):
self.assertRewrite([
'int a / x',
'int a(1 / x) dx',
'int a * 1 / x dx',
# FIXME: 'a int 1 / x dx', # fix with strategy
# FIXME: 'aln|x| + c',
])
......
......@@ -59,7 +59,7 @@ class TestRulesLineq(RulesTestCase):
self.assertEqual(subtract_term(root, (a,)), expect)
def test_divide_term(self):
root, a, expect = tree('x * a = b, a, x * a / a = b / a')
root, a, expect = tree('x * a = b, a, (xa) / a = b / a')
self.assertEqual(divide_term(root, (a,)), expect)
def test_multiply_term(self):
......@@ -89,7 +89,7 @@ class TestRulesLineq(RulesTestCase):
'5x = 0 * 3x - 5',
'5x = 0 - 5',
'5x = -5',
'5x / 5 = (-5) / 5',
'(5x) / 5 = (-5) / 5',
'5 / 5 * x = (-5) / 5',
'1x = (-5) / 5',
'x = (-5) / 5',
......
......@@ -179,7 +179,7 @@ class TestRulesPowers(RulesTestCase):
N('sqrt', a, m))
def test_match_constant_exponent(self):
a0, a1, a2 = tree('a0,a1,a2')
a0, a1, a2 = tree('a ^ 0, a ^ 1, a ^ 2')
self.assertEqualPos(match_constant_exponent(a0),
[P(a0, remove_power_of_zero, ())])
......
......@@ -5,17 +5,17 @@ from src.validation import validate
class TestValidation(TestCase):
def test_simple_success(self):
self.assertTrue(validate('3a+a', '4a'))
self.assertTrue(validate('3a + a', '4a'))
def test_simple_failure(self):
self.assertFalse(validate('3a+a', '4a+1'))
self.assertFalse(validate('3a + a', '4a + 1'))
def test_intermediate_success(self):
self.assertTrue(validate('3a+a+b+2b', '4a+3b'))
self.assertTrue(validate('a/b/(c/d)', 'ad/(bc)'))
self.assertTrue(validate('3a + a + b + 2b', '4a + 3b'))
self.assertTrue(validate('a / b / (c / d)', '(ad) / (bc)'))
def test_intermediate_failure(self):
self.assertFalse(validate('3a+a+b+2b', '4a+4b'))
self.assertFalse(validate('3a + a + b + 2b', '4a + 4b'))
#def test_advanced_failure(self):
# self.assertFalse(validate('(x-1)^3+(x-1)^3', '4a+4b'))
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment