Skip to content

Commit 1ea4d4b

Browse files
authored
Merge pull request #46 from pie-framework/beta
chore: merge beta in master
2 parents 3e4350d + c495c52 commit 1ea4d4b

15 files changed

Lines changed: 682 additions & 179 deletions

.DS_Store

-2 KB
Binary file not shown.

README.md

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
- an api
44
- a large set of test data
5+
- aims to determine if two mathematical expressions are equal, either having the same form or not
56
- have a public website where you can add some math and check if it works - also the link should be shareable
67

78

@@ -13,6 +14,7 @@ yarn demo
1314

1415
Then go to `http://localhost:$PORT/demo`
1516

17+
1618
## tests
1719

1820
```shell
@@ -27,9 +29,9 @@ There is one test that runs fixture data located here: `src/fixtures/latex-equal
2729
yarn jest src/__tests__/latex-equal.spec.ts -t src/fixtures/latex-equal/7119.ts --reporters default
2830
```
2931

32+
3033
## Next Steps
3134

32-
* Check that we have a test case for any outstanding jira tickets relating to math-validation.
3335
* Check that api we expose will support what is needed.
3436
* Do triage on the test failures, add a note to failing test so we can build a picture of the work needed
3537

@@ -52,27 +54,55 @@ yarn jest src/__tests__/latex-equal.spec.ts -t src/fixtures/latex-equal/7119.ts
5254
* more advanced literal validation (todo)
5355
* block input that is clearly too large/unrelated (eg: a user can type in gobbledy-gook - we should just abort if we see that)
5456

57+
58+
## Capabilities
59+
60+
It can determine mathematical equivalence between:
61+
62+
- linear equations in one variable
63+
- linear equations in two variables
64+
- 2-way inequalities in one or two variables
65+
- compound inequalities in one variable
66+
- trigonometric identities and functions
67+
- inverse trigonometric functions
68+
- similar notation for logarithms and based logarithms
69+
70+
It can also handle degrees, radians and gradians
71+
72+
5573
### things that'd be great (but we may have to park until we have more time)
5674

5775
* a faster latex parser
5876
* faster math evaluation
5977

78+
6079
## modes
6180

6281
There are 2 modes - literal and symbolic
6382

64-
Literal: needs to more advanced than the legacy literal implementation which was essentially a string check.
83+
Literal: is at its most basic a tuned version of a string validation
84+
85+
By default - ignores spaces and parentheses as long as they do not change the meaning of operations (ex. “a+7 +b” will validate against “ ((a) + (7))+b ”)
86+
- ignores leading zeros: “0.1” will validate against “.1”
87+
- accepts commas for decimal marks. For example “1,000” will be equivalent with 1000
88+
89+
Literal Validation offers two configuration options that can be used to validate some variety of forms for an expression:
90+
91+
Ignore trailing zeros option; allows the evaluation to accept zeros to the right of the decimal place “4.5” will validate against “4.50000000000000”
92+
Ignore order option; makes validation indifferent to the variables order, as long as it does not change operations meaning. In this case “a+7 +b*c” will validate against “7 + a+bc”, but not against “ac+7+b”; without it “a+7 +b” will not validate against “7 + a+b”
93+
94+
Symbolic: attempts to decide if expressions are mathematically equivalent or not
95+
96+
By default, it offers all configurations presented for literal validation, exceeding it by quite a lot
97+
In order to check equivalence between 2 expressions, we have to reduce both expressions to the simplest one. Then distribute all coefficients, combine any like terms on each side of the expression, and arrange them in the same order.
6598

66-
Symbolic:
6799

68100
### Notes
69101

70102
* `@babel/runtime` is a devDependency if you ever need to link this repo to another package for testing
71103

72104
## TODO
73-
* strip logs on compile
74105
* set up api that is compatible w/ ui component options
75-
* start going through the tests, build up literal + symbolic a bit att the start
76106
* derivatives kind of work and kind of not - how to use?
77107

78108
### CI
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { AstToMathJs } from "../conversion/ast-to-mathjs";
2+
import { LatexToAst } from "../conversion/latex-to-ast";
3+
4+
import { splitInequality } from "../symbolic/compare-compound-inequations";
5+
6+
const lta = new LatexToAst();
7+
const atm = new AstToMathJs();
8+
9+
describe("splitInequality", () => {
10+
it.each`
11+
compoundInequality | leftPart | rightPart
12+
${"20>x>7"} | ${"20>x"} | ${"x>7"}
13+
${" 5 ≥ -4 + x > -1 - 1"} | ${" 5 ≥ -4 + x"} | ${"-4 + x > -1 - 1"}
14+
${"x/x<x+y≤1.5*2"} | ${"x/x<x+y"} | ${"x+y≤1.5*2"}
15+
${"2 < 4x > 20"} | ${"2<4x"} | ${"4x>20"}
16+
${"-3 < 2x+5 < 17"} | ${"-3<2x+5"} | ${"2x+5 < 17"}
17+
${"-3 = 6x = -3"} | ${"-3 = 6x"} | ${"6x = -3"}
18+
${"a≥b≥c "} | ${"a≥b"} | ${"b≥c"}
19+
${"a+2≥b-10≥c-100 "} | ${"a+2≥b-10"} | ${"b-10≥c-100"}
20+
${"a≠b≠c "} | ${"a≠b"} | ${"b≠c"}
21+
`(
22+
"$compoundInequality => $leftPart, $rightPart",
23+
({ compoundInequality, leftPart, rightPart }) => {
24+
const inequality = atm.convert(lta.convert(compoundInequality));
25+
const broken = splitInequality(inequality);
26+
27+
const leftSide = atm.convert(lta.convert(leftPart));
28+
const rightSide = atm.convert(lta.convert(rightPart));
29+
30+
expect(broken.left).toEqual(leftSide);
31+
expect(broken.right).toEqual(rightSide);
32+
}
33+
);
34+
});
Lines changed: 74 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,77 @@
11
import { AstToMathJs } from "../conversion/ast-to-mathjs";
22
import { LatexToAst } from "../conversion/latex-to-ast";
3+
import { simplify } from "../symbolic";
4+
35
import {
4-
getUnknowns,
6+
getVariables,
57
getCoefficients,
68
setXToOne,
79
solveLinearEquation,
8-
} from "../symbolic/compare-equations";
10+
expressionsCanBeCompared,
11+
transformEqualityInExpression,
12+
} from "../symbolic/utils";
913

1014
const lta = new LatexToAst();
1115
const atm = new AstToMathJs();
1216

13-
describe("getUnknowns", () => {
17+
describe("expressionsCanBeCompared", () => {
18+
it('equations: "x = x" and "2=2" - should return false: equations can not be compared because second equation does not have a variable', () => {
19+
const firstEquation = atm.convert(lta.convert("x=x"));
20+
const secondEquation = atm.convert(lta.convert("2=2"));
21+
const result = expressionsCanBeCompared(firstEquation, secondEquation);
22+
23+
expect(result).toEqual(false);
24+
});
25+
26+
it('equations: "x = x" and "\\log x=2" - should return false: equations can not be compared because second equation contains a function', () => {
27+
const firstEquation = atm.convert(lta.convert("x=x"));
28+
const secondEquation = atm.convert(lta.convert("\\log x=2"));
29+
const result = expressionsCanBeCompared(firstEquation, secondEquation);
30+
31+
expect(result).toEqual(false);
32+
});
33+
34+
it('equations: "5z = 0" and "2y+3=m" - should return true: both equations have variables and does not contain functions', () => {
35+
const firstEquation = atm.convert(lta.convert("x=x"));
36+
const secondEquation = atm.convert(lta.convert("2y+3=m"));
37+
const result = expressionsCanBeCompared(firstEquation, secondEquation);
38+
39+
expect(result).toEqual(true);
40+
});
41+
42+
it('equations: "x" and "y" - should return true: both expressions have variables and does not contain functions', () => {
43+
const firstEquation = atm.convert(lta.convert("x"));
44+
const secondEquation = atm.convert(lta.convert("y"));
45+
const result = expressionsCanBeCompared(firstEquation, secondEquation);
46+
47+
expect(result).toEqual(true);
48+
});
49+
});
50+
51+
describe("transformEqualityInExpression", () => {
1452
it.each`
15-
expression | unknowns
53+
equation | transformedExpression
54+
${"x+5= 2x+3"} | ${"2-x"}
55+
${"5-2(3-m)= 4m+10"} | ${"-5-4m-2(3-m)"}
56+
${"a=2b+3"} | ${"a-2b-3"}
57+
`(
58+
"$equation => $transformedExpression",
59+
({ equation, transformedExpression }) => {
60+
const equationToTransform = atm.convert(lta.convert(equation));
61+
const expression = simplify(
62+
atm.convert(lta.convert(transformedExpression))
63+
);
64+
65+
const result = transformEqualityInExpression(equationToTransform);
66+
67+
expect(result.equals(expression)).toEqual(true);
68+
}
69+
);
70+
});
71+
72+
describe("getVariables", () => {
73+
it.each`
74+
expression | variables
1675
${"x"} | ${["x"]}
1776
${"x +1"} | ${["x"]}
1877
${"((x^2 + x) / x) - 1"} | ${["x"]}
@@ -21,30 +80,31 @@ describe("getUnknowns", () => {
2180
${"((y^2 + z) / x) - 1"} | ${["x", "y", "z"]}
2281
${"109h"} | ${["h"]}
2382
${"m+n+10"} | ${["m", "n"]}
24-
`("$expression => $unknowns", ({ expression, unknowns }) => {
83+
`("$expression => $variables", ({ expression, variables }) => {
2584
const equation = atm.convert(lta.convert(expression));
26-
const unknownsName = getUnknowns(equation);
85+
const variablesName = getVariables(equation);
2786

28-
expect(unknownsName).toEqual(unknowns);
87+
expect(variablesName).toEqual(variables);
2988
});
3089
});
3190

3291
describe("getCoefficients", () => {
3392
it.each`
3493
expression | coefficients
3594
${"x+0"} | ${[0, 1]}
95+
${"2x^2 = 2x"} | ${[1, 0]}
3696
${"x +1"} | ${[1, 1]}
3797
${"((x^2 + x) / x) - 1"} | ${[0, 0, 1]}
38-
${"1+2"} | ${[1, 0]}
39-
${"a +1+c"} | ${[1, 0]}
98+
${"1+2"} | ${[]}
99+
${"a +1+c"} | ${[]}
40100
${"y^2+5y - 1"} | ${[-1, 5, 1]}
41101
${"2y^2+4y"} | ${[0, 4, 2]}
42102
${"109h"} | ${[0, 109]}
43-
${"m+n+10"} | ${[1, 0]}
103+
${"m+n+10"} | ${[]}
44104
${"x-x"} | ${[0, 0]}
45105
${"x + 5 - 3 + x - 6 - x + 2"} | ${[-2, 1]}
46106
${"2x-x"} | ${[0, 1]}
47-
${"x - x - 2"} | ${[1, 0]}
107+
${"x - x - 2"} | ${[]}
48108
`("$expression => $coefficients", ({ expression, coefficients }) => {
49109
const equation = atm.convert(lta.convert(expression));
50110
const coefficientsList = getCoefficients(equation);
@@ -61,11 +121,11 @@ describe("getCoefficients", () => {
61121
expect(coefficientsList).toEqual([0, 0]);
62122
});
63123

64-
it('equation: "1 = -2" - if equation has no coefficient for x it will return coefficients [1, 0]', () => {
124+
it('equation: "1 = -2" - if equation has no coefficient for x but can be rationalized it will return an empty array', () => {
65125
const equation = atm.convert(lta.convert("1+2"));
66126
const coefficientsList = getCoefficients(equation);
67127

68-
expect(coefficientsList).toEqual([1, 0]);
128+
expect(coefficientsList).toEqual([]);
69129
});
70130

71131
it('equation: "m + n = - 2" - if equation has more than one variable, will return coefficients [1, 0]', () => {
@@ -148,7 +208,7 @@ describe("solveLinearEquation", () => {
148208
expect(result).toEqual(-Infinity);
149209
});
150210

151-
it('equation: "2x^2 = 2x" - has no solution', () => {
211+
it('equation: "2y^2+4y" - solution is -2', () => {
152212
const coefficients = [0, 4, 2];
153213
const result = solveLinearEquation(coefficients);
154214

src/conversion/latex-to-ast.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -298,10 +298,10 @@ export const latex_rules = [
298298
["\\\\lnot(?![a-zA-Z])", "NOT"],
299299

300300
["=", "="],
301+
["≠", "NE"],
301302
["\\\\neq(?![a-zA-Z])", "NE"],
302303
["\\\\ne(?![a-zA-Z])", "NE"],
303304
["\\\\not\\s*=", "NE"],
304-
["≠", "NE"],
305305
["\\\\leq(?![a-zA-Z])", "LE"],
306306
["\\\\le(?![a-zA-Z])", "LE"],
307307
["\\\\geq(?![a-zA-Z])", "GE"],
@@ -689,7 +689,11 @@ export class LatexToAst {
689689
var lhs = this.expression(params);
690690

691691
let relationalToken = (token) =>
692-
token === "<" || token === "LE" || token === ">" || token === "GE";
692+
token === "<" ||
693+
token === "LE" ||
694+
token === ">" ||
695+
token === "GE" ||
696+
token === "NE";
693697

694698
while (
695699
this.token.token_type === "=" ||
@@ -733,6 +737,9 @@ export class LatexToAst {
733737
case "GE":
734738
case "ge":
735739
return "largerEq";
740+
case "NE":
741+
case "ne":
742+
return "unequal";
736743
}
737744
};
738745

src/difference.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const log = debug("difference");
66
export const differenceIsTooGreat = (a: RawAst, b: RawAst) => {
77
const smallest = Math.min(a.toString().length, b.toString().length);
88
const biggest = Math.max(a.toString().length, b.toString().length);
9-
const errorAcceptance = 6;
9+
const errorAcceptance = 17;
1010
const limit = (1 / smallest) * 100 + 10 + errorAcceptance;
1111
const diff = biggest - smallest;
1212

0 commit comments

Comments
 (0)