@khanacademy/kas 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,364 @@
1
+ import _ from "underscore";
2
+
3
+ import * as KAS from "../index.js";
4
+
5
+ expect.extend({
6
+ toHaveGCD(input: [string, string], expected: string) {
7
+ const [a, b] = input;
8
+ const actualRep = KAS.parse(a).expr.findGCD(KAS.parse(b).expr).repr();
9
+ const expectedRep = KAS.parse(expected).expr.repr();
10
+
11
+ return actualRep === expectedRep
12
+ ? {pass: true}
13
+ : {
14
+ pass: false,
15
+ message: () => `(${a}).findGCD(${b}) = ${expected}`,
16
+ };
17
+ },
18
+ toBeSimplified(input: string, options: $FlowFixMe) {
19
+ const actual = KAS.parse(input, options).expr.isSimplified();
20
+
21
+ if (this.isNot) {
22
+ return actual
23
+ ? {pass: true}
24
+ : {
25
+ pass: false,
26
+ message: () => `${input} is simplified`,
27
+ };
28
+ }
29
+ return actual
30
+ ? {pass: true}
31
+ : {
32
+ pass: false,
33
+ message: () => `${input} is NOT simplified`,
34
+ };
35
+ },
36
+ toHaveConsts(
37
+ input: string,
38
+ expected: $ReadOnlyArray<string>,
39
+ options: $FlowFixMe,
40
+ ) {
41
+ const actual = KAS.parse(input, options).expr.getConsts();
42
+
43
+ if (this.isNot) {
44
+ expect(actual).not.toEqual(expected);
45
+ } else {
46
+ expect(actual).toEqual(expected);
47
+ }
48
+
49
+ return {pass: !this.isNot};
50
+ },
51
+ toBeExpr(input: string, reference: string) {
52
+ var actual = KAS.parse(input).expr.asExpr().simplify().normalize().print();
53
+ var expected = KAS.parse(reference).expr.normalize().print();
54
+
55
+ return actual === expected
56
+ ? {pass: true}
57
+ : {
58
+ pass: false,
59
+ message: () => `${input} as an expression is ${reference}`,
60
+ };
61
+ },
62
+ });
63
+
64
+ describe("KAS", () => {
65
+ it("should parse", () => {
66
+ const {expr} = KAS.parse("3x \\frac{42}{42} sin^2y");
67
+
68
+ const result = expr.print();
69
+
70
+ expect(result).toEqual("3*x*42/42*sin(y)^(2)");
71
+ });
72
+
73
+ it("should evaluate expressions", () => {
74
+ const {expr} = KAS.parse("(x^2+y^2)^.5");
75
+
76
+ const result = expr.eval({x: 3, y: 4});
77
+
78
+ expect(result).toEqual(5);
79
+ });
80
+
81
+ it("should compare expressions", () => {
82
+ const expr1 = KAS.parse("(1-x)(-1-6x)").expr;
83
+ const expr2 = KAS.parse("(6x+1)(x-1)").expr;
84
+
85
+ const {equal} = KAS.compare(expr1, expr2);
86
+
87
+ expect(equal).toBeTrue();
88
+ });
89
+
90
+ it("should compare equations", () => {
91
+ const eq1 = KAS.parse("2w+50/w=25").expr;
92
+ const eq2 = KAS.parse("w(12.5-w)=25").expr;
93
+
94
+ const {equal} = KAS.compare(eq1, eq2);
95
+
96
+ expect(equal).toBeTrue();
97
+ });
98
+
99
+ it("can collect like terms", () => {
100
+ const {expr} = KAS.parse("1+1+x+x+x+y");
101
+
102
+ const result = expr.collect().print();
103
+
104
+ expect(result).toEqual("2+3*x+y");
105
+ });
106
+
107
+ it("can simplify exp/log expressions", () => {
108
+ const {expr} = KAS.parse("b^(2*y*log_b x)");
109
+
110
+ const result = expr.collect().print();
111
+
112
+ expect(result).toEqual("x^(2*y)");
113
+ });
114
+
115
+ it("can expand expressions", () => {
116
+ const {expr} = KAS.parse("ab(c+d)e^f");
117
+
118
+ const expansion = expr.expand().print();
119
+
120
+ expect(expansion).toEqual("a*b*e^(f)*c+a*b*e^(f)*d");
121
+ });
122
+
123
+ it("can expand and factor expressions", () => {
124
+ const {expr} = KAS.parse("ab(c+d)e^f");
125
+
126
+ const factored = expr.expand().factor().print();
127
+
128
+ expect(factored).toEqual("a*b*e^(f)*(c+d)");
129
+ });
130
+
131
+ it("can simplify complex expressions", () => {
132
+ const {expr} = KAS.parse("((nx^5)^5)/(n^-2x^2)^-3");
133
+
134
+ const simplified = expr.simplify().print();
135
+
136
+ expect(simplified).toEqual("n^(-1)*x^(31)");
137
+ });
138
+
139
+ it("can simplify if more complex expressions", () => {
140
+ const {expr} = KAS.parse(
141
+ "(15np-25mp)/(15p^2-5p)+(20mp+10p^2)/(15p^2-5p)",
142
+ );
143
+
144
+ const simplified = expr.simplify().print();
145
+
146
+ expect(simplified).toEqual("(-1+3*p)^(-1)*(3*n+-1*m+2*p)");
147
+ });
148
+
149
+ test("equation to expression", () => {
150
+ const forms = [
151
+ "y=2x-5",
152
+ "-2x+5=-y",
153
+ "2x-5<>y",
154
+ "2x-y<>5",
155
+ "(y+5)/2=x",
156
+ "(y+5)/x=2",
157
+ "1/2(y+5)=x",
158
+ ".5(y+5)=x",
159
+ "y-3=2(x-4)",
160
+ "2y=4x-10",
161
+ "yz=2xz-5z"
162
+ ];
163
+
164
+ _.each(forms, (form) => {
165
+ if (form.includes("<>")) {
166
+ expect(form).toBeExpr("-y+2x-5");
167
+ } else {
168
+ expect(form).toBeExpr("y-2x+5");
169
+ }
170
+ });
171
+
172
+ var forms2 = [
173
+ "1/3p-3=114",
174
+ "1/3p=117",
175
+ "p=351",
176
+ "p-351=0",
177
+ ];
178
+
179
+ _.each(forms2, function(form) {
180
+ expect(form).toBeExpr("p-351");
181
+ });
182
+ });
183
+
184
+ describe("findGCD", () => {
185
+ test("findGCD on ints", () => {
186
+ expect(["40", "30"]).toHaveGCD("10");
187
+ expect(["14", "21"]).toHaveGCD("7");
188
+ expect(["13", "26"]).toHaveGCD("13");
189
+ });
190
+
191
+ test("findGCD on rationals", () => {
192
+ expect(["40/3", "55/6"]).toHaveGCD("5/6");
193
+ expect(["3/4", "1/2"]).toHaveGCD("1/4");
194
+ expect(["3/7", "12/22"]).toHaveGCD("3/77");
195
+ expect(["3/10", "4/15"]).toHaveGCD("1/30");
196
+ expect(["2/3", "23/6"]).toHaveGCD("1/6");
197
+ expect(["2/3", "22/6"]).toHaveGCD("1/3");
198
+
199
+ expect(["2/3", "2"]).toHaveGCD("2/3");
200
+ expect(["2/3", "3"]).toHaveGCD("1/3");
201
+ expect(["1/2", "4"]).toHaveGCD("1/2");
202
+ expect(["4", "3/4"]).toHaveGCD("1/4");
203
+ expect(["3", "3/4"]).toHaveGCD("3/4");
204
+ });
205
+
206
+ test("findGCD on floats", () => {
207
+ // Feel free to change this when we do something other
208
+ // than a naive "return 1" for floats
209
+ expect(["1.23", "1.42"]).toHaveGCD("1");
210
+ expect(["1", String(Math.PI)]).toHaveGCD("1");
211
+ });
212
+ });
213
+
214
+ describe("isSimplified", () => {
215
+ test("isSimplified (addition/subtraction)", () => {
216
+ expect("a+b+c").toBeSimplified();
217
+ expect("a-b-c").toBeSimplified();
218
+ expect("a+b+c+c").not.toBeSimplified();
219
+ expect("a-b-c-d-d+d").not.toBeSimplified();
220
+ expect("x").toBeSimplified();
221
+ expect("x+0").not.toBeSimplified();
222
+ });
223
+
224
+ test("isSimplified (multiplication/division/negation)", () => {
225
+ expect("1/2").toBeSimplified();
226
+ expect("2/1").not.toBeSimplified();
227
+ expect("(2x)/(5x)").not.toBeSimplified();
228
+ expect("-x").toBeSimplified();
229
+ expect("-1*x").toBeSimplified();
230
+ expect("--x").not.toBeSimplified();
231
+ expect("x/1").not.toBeSimplified();
232
+ expect("x/y").toBeSimplified();
233
+ expect("xy/z").toBeSimplified();
234
+ expect("-3x").toBeSimplified();
235
+ expect("-x*3").toBeSimplified();
236
+ expect("-x*3*y").toBeSimplified();
237
+ expect("(x+1)/(2(x+1))").not.toBeSimplified();
238
+ });
239
+
240
+ test("isSimplified (exponentiation)", () => {
241
+ expect("x^-1").toBeSimplified();
242
+ expect("1/x").toBeSimplified();
243
+ expect("1/x^-1").not.toBeSimplified();
244
+ });
245
+
246
+ test("isSimplified (logarithms)", () => {
247
+ expect("ln(x)").toBeSimplified();
248
+ expect("ln(x+y)").toBeSimplified();
249
+
250
+ // Will only expand logarithms if leads to a simpler expression
251
+ // TODO(alex): Combine all simplify(assert, ) and isSimplified(assert, ) tests!
252
+ expect("ln(x/y)").toBeSimplified();
253
+ expect("ln(x/y)+ln(y)").not.toBeSimplified();
254
+ });
255
+
256
+ test("isSimplified (equations)", () => {
257
+ expect("x=10").toBeSimplified();
258
+ expect("x=-10").toBeSimplified();
259
+ expect("x=10y").toBeSimplified();
260
+ expect("x=-10y").toBeSimplified();
261
+ expect("x=10+y").toBeSimplified();
262
+ expect("x=-10+y").toBeSimplified();
263
+
264
+ expect("f(2x) = 10").toBeSimplified({functions: ["f"]});
265
+ expect("f(x+x) = 10").not.toBeSimplified({functions: ["f"]});
266
+ });
267
+
268
+ test("isSimplified (equalities)", () => {
269
+ expect("y=x").toBeSimplified();
270
+ expect("y=x^2").toBeSimplified();
271
+ expect("y=x*x").not.toBeSimplified();
272
+
273
+ expect("y=x^2+1").toBeSimplified();
274
+ expect("xy=x^2+1").toBeSimplified();
275
+ expect("y+1=x^2+1").not.toBeSimplified();
276
+
277
+ expect("xy=x^2").not.toBeSimplified();
278
+ expect("x^2y=x^3").not.toBeSimplified();
279
+ expect("alnx=blnx+clnx").not.toBeSimplified();
280
+
281
+ expect("xy=0").toBeSimplified();
282
+ expect("2xy=0").not.toBeSimplified();
283
+ expect("xy/z=0").toBeSimplified();
284
+ });
285
+
286
+ test("isSimplified (inequalities)", () => {
287
+ expect("y<x").toBeSimplified();
288
+ expect("y<x^2").toBeSimplified();
289
+ expect("y<x*x").not.toBeSimplified();
290
+
291
+ expect("y<x^2+1").toBeSimplified();
292
+ expect("xy<x^2+1").toBeSimplified();
293
+ expect("y+1<x^2+1").not.toBeSimplified();
294
+
295
+ expect("xy<x^2"); // x might be negativ.toBeSimplified();
296
+ expect("x^2y<x^3").not.toBeSimplified();
297
+ expect("alnx<blnx+clnx"); // lnx might be negativ.toBeSimplified();
298
+
299
+ expect("xy<0").toBeSimplified();
300
+ expect("2xy<0").not.toBeSimplified();
301
+ expect("xy/z<0").toBeSimplified();
302
+ });
303
+
304
+ test("isSimplified (rational expressions)", () => {
305
+ expect("3/4 x").toBeSimplified();
306
+ expect("3/(4x)").toBeSimplified();
307
+ expect("3/4 1/x").toBeSimplified();
308
+
309
+ expect("(x+1)/(x+2)").toBeSimplified();
310
+ expect("(x+1)/(2x+2)").not.toBeSimplified();
311
+ expect("(2x+2)/(x+1)").not.toBeSimplified();
312
+
313
+ expect("xy+2y=x+1").toBeSimplified();
314
+ expect("y=(x+1)/(x+2)").toBeSimplified();
315
+ expect("y/(x+1)=1/(x+2)").toBeSimplified();
316
+
317
+ // TODO(alex): Combine all isSimplified(assert, ) and simplify(assert, ) tests!
318
+ expect("y=(x+1)/(2x+2)").not.toBeSimplified();
319
+ expect("y=1/2").toBeSimplified();
320
+
321
+ expect("y=(2x+2)/(x+1)").not.toBeSimplified();
322
+ expect("y=2").toBeSimplified();
323
+
324
+ // same denominators (adding_and_subtracting_rational_expressions_1.5)
325
+ expect(
326
+ "(15np-25mp)/(15p^2-5p)+(20mp+10p^2)/(15p^2-5p)",
327
+ ).not.toBeSimplified();
328
+ expect("(3n-m+2p)/(3p-1)").toBeSimplified();
329
+
330
+ expect("5yx/(2x^2+4yx)-(2x^2-6yx)/(2x^2+4yx)").not.toBeSimplified();
331
+ expect("(11y-2x)/(2x+4y)").toBeSimplified();
332
+
333
+ expect("(25m^2-20pm)/(5pm+5m)-20m^2/(5pm+5m)").not.toBeSimplified();
334
+ expect("(m-4p)/(p+1)").toBeSimplified();
335
+
336
+ // pathological examples
337
+ expect(" (a + b)/(2c+2d)").toBeSimplified();
338
+ expect(" (3a+3b)/(2c+2d)").toBeSimplified();
339
+ expect(" (2a+2b)/( c+ d)").toBeSimplified();
340
+ expect(" (2a+2b)/(3c+3d)").toBeSimplified();
341
+ expect(" (2a+2b)/(4c+4d)").not.toBeSimplified();
342
+ expect(" (4a+4b)/(2c+2d)").not.toBeSimplified();
343
+ expect("y=(a + b)/(2c+2d)").toBeSimplified();
344
+ expect("y=(3a+3b)/(2c+2d)").toBeSimplified();
345
+ expect("y=(2a+2b)/( c+ d)").toBeSimplified();
346
+ expect("y=(2a+2b)/(3c+3d)").toBeSimplified();
347
+ expect("y=(2a+2b)/(4c+4d)").not.toBeSimplified();
348
+ expect("y=(4a+4b)/(2c+2d)").not.toBeSimplified();
349
+ });
350
+
351
+ test("getConsts", () => {
352
+ expect("4").toHaveConsts([]);
353
+ expect("4x").toHaveConsts([]);
354
+ expect("4pi").toHaveConsts(["pi"]);
355
+ expect("4e").toHaveConsts(["e"]);
356
+ expect("4pi+e").toHaveConsts(["e", "pi"]);
357
+ expect("4pi+pi").toHaveConsts(["pi"]);
358
+ expect("4cos(pi)").toHaveConsts(["pi"]);
359
+ expect("4cos(xpi)").toHaveConsts(["pi"]);
360
+ expect("(4x)/(2pi)").toHaveConsts(["pi"]);
361
+ expect("4sec(x)").toHaveConsts([]);
362
+ });
363
+ });
364
+ });