@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,272 @@
1
+ import _ from "underscore";
2
+
3
+ import * as KAS from "../index.js";
4
+
5
+ expect.extend({
6
+ toRenderTex(input: sstring, expected: string, options: $FlowFixMe) {
7
+ const actual = KAS.parse(input, options).expr.tex();
8
+
9
+ if (actual !== expected) {
10
+ return {
11
+ pass: false,
12
+ message: () => `${input} renders as ${expected}`,
13
+ };
14
+ }
15
+
16
+ return {pass: !this.isNot};
17
+ },
18
+ toRenderTexOpt(
19
+ input: string,
20
+ expected: string,
21
+ ...optlist: $ReadOnlyArray<$FlowFixMe>
22
+ ) {
23
+ const options = {
24
+ display: false,
25
+ dynamic: false,
26
+ times: false,
27
+ };
28
+ _.each(optlist, function (opt) {
29
+ options[opt] = true;
30
+ });
31
+
32
+ const actual = KAS.parse(input).expr.asTex(options);
33
+
34
+ if (actual !== expected) {
35
+ return {
36
+ pass: false,
37
+ message: () => `${input} renders with options as ${expected}`,
38
+ };
39
+ }
40
+
41
+ return {pass: true};
42
+ },
43
+ });
44
+
45
+
46
+ describe("rendering", () => {
47
+ test("positive and negative primitives", () => {
48
+ expect("0").toRenderTex("0");
49
+ expect("-1").toRenderTex("-1");
50
+ expect("--1").toRenderTex("--1");
51
+ expect("-2").toRenderTex("-2");
52
+ expect("--2").toRenderTex("--2");
53
+ expect("x").toRenderTex("x");
54
+ expect("theta").toRenderTex("\\theta");
55
+ expect("1/2").toRenderTex("\\frac{1}{2}");
56
+ expect("-1/2").toRenderTex("-\\frac{1}{2}");
57
+ expect("1/-2").toRenderTex("-\\frac{1}{2}");
58
+ expect("-1/-2").toRenderTex("--\\frac{1}{2}");
59
+ });
60
+
61
+ test("addition", () => {
62
+ expect("1-2").toRenderTex("1-2");
63
+ expect("a+b").toRenderTex("a+b");
64
+ expect("a-b").toRenderTex("a-b");
65
+ expect("a-1b").toRenderTex("a-1b");
66
+ expect("a+-b").toRenderTex("a+-b");
67
+ expect("a+-1b").toRenderTex("a+-1b");
68
+ });
69
+
70
+ test("multiplication", () => {
71
+ expect("ab").toRenderTex("ab");
72
+ expect("a*b").toRenderTex("ab");
73
+ expect("a/b").toRenderTex("\\frac{a}{b}");
74
+ expect("a/bc/d").toRenderTex("\\frac{ac}{bd}");
75
+
76
+ expect("1/(x+y)").toRenderTex("\\frac{1}{x+y}");
77
+ expect("2/(x+y)").toRenderTex("\\frac{2}{x+y}");
78
+ expect("(z+2)/(x+y)").toRenderTex("\\frac{z+2}{x+y}");
79
+ expect("(z+2)/4").toRenderTex("\\frac{z+2}{4}");
80
+ });
81
+
82
+ test("rational expressions", () => {
83
+ expect("x+1/2").toRenderTex("x+\\frac{1}{2}");
84
+ expect("x-1/2").toRenderTex("x-\\frac{1}{2}");
85
+
86
+ expect("1/2x").toRenderTex("\\frac{1}{2}x");
87
+ expect("1/2x/y").toRenderTex("\\frac{1}{2}\\frac{x}{y}");
88
+ expect("5*1/2x/y").toRenderTex("\\frac{1}{2}\\frac{5x}{y}");
89
+ expect("1/2*4*x/y").toRenderTex("\\frac{1}{2}\\frac{4x}{y}");
90
+ expect("-1/2x").toRenderTex("-\\frac{1}{2}x");
91
+ expect("a-1/2x").toRenderTex("a-\\frac{1}{2}x");
92
+
93
+ expect("1/(2x)").toRenderTex("\\frac{1}{2x}");
94
+ expect("8/(7p^4)").toRenderTex("\\frac{8}{7p^{4}}");
95
+
96
+ expect("x/2").toRenderTex("\\frac{x}{2}");
97
+ expect("1x/2").toRenderTex("\\frac{1x}{2}");
98
+ expect("-x/2").toRenderTex("-\\frac{x}{2}");
99
+ expect("x/-2").toRenderTex("-\\frac{x}{2}");
100
+ expect("x/-2/-3").toRenderTex("--\\frac{x}{2 \\cdot 3}");
101
+ expect("--x/2/3").toRenderTex("--\\frac{x}{2 \\cdot 3}");
102
+ expect("a-x/2").toRenderTex("a-\\frac{x}{2}");
103
+
104
+ expect("1*-2").toRenderTex("1 \\cdot -2");
105
+ expect("1*-2*3").toRenderTex("1 \\cdot -2 \\cdot 3");
106
+ expect("1*-2*3/4").toRenderTex("1 \\cdot -2 \\cdot \\frac{3}{4}");
107
+ expect("1*-2*3/4/5").toRenderTex(
108
+ "1 \\cdot -2 \\cdot \\frac{3}{4} \\cdot \\frac{1}{5}",
109
+ );
110
+ expect("1/2*1/2").toRenderTex("\\frac{1}{2} \\cdot \\frac{1}{2}");
111
+ });
112
+
113
+ test("exponentiation", () => {
114
+ expect("x^y").toRenderTex("x^{y}");
115
+ expect("xy^z").toRenderTex("xy^{z}");
116
+ expect("(xy)^z").toRenderTex("(xy)^{z}");
117
+ expect("(x+y)^z").toRenderTex("(x+y)^{z}");
118
+ expect("x^(yz)").toRenderTex("x^{yz}");
119
+ expect("x^-(yz)").toRenderTex("x^{-yz}");
120
+ expect("x^(y+z)").toRenderTex("x^{y+z}");
121
+ expect("x^-(y+z)").toRenderTex("x^{-(y+z)}");
122
+ expect("(x^y)^z").toRenderTex("(x^{y})^{z}");
123
+ expect("pir^2").toRenderTex("\\pi r^{2}");
124
+ });
125
+
126
+ test("square root", () => {
127
+ expect("sqrt(x)").toRenderTex("\\sqrt{x}");
128
+ expect("sqrt(x)y").toRenderTex("\\sqrt{x}y");
129
+ expect("1/sqrt(x)").toRenderTex("\\frac{1}{\\sqrt{x}}");
130
+ expect("1/sqrt(x)y").toRenderTex("\\frac{y}{\\sqrt{x}}");
131
+
132
+ expect("sqrt(2)/2").toRenderTex("\\frac{\\sqrt{2}}{2}");
133
+ expect("sqrt(2)^2").toRenderTex("(\\sqrt{2})^{2}");
134
+ });
135
+
136
+ test("nth root", () => {
137
+ // This is an unfortunate case, but only nth degree roots with integer
138
+ // n's get nicely printed as tex.
139
+ expect("sqrt[z]{x}").toRenderTex("x^{\\frac{1}{z}}");
140
+
141
+ expect("sqrt[3]{x}").toRenderTex("\\sqrt[3]{x}");
142
+ expect("sqrt[3]{x}z").toRenderTex("\\sqrt[3]{x}z");
143
+ expect("1/sqrt[4]{x}").toRenderTex("\\frac{1}{\\sqrt[4]{x}}");
144
+ expect("1/sqrt[4]{x}y").toRenderTex("\\frac{y}{\\sqrt[4]{x}}");
145
+
146
+ expect("sqrt[9]{2}/2").toRenderTex("\\frac{\\sqrt[9]{2}}{2}");
147
+ expect("sqrt[9]{2}^2").toRenderTex("(\\sqrt[9]{2})^{2}");
148
+ });
149
+
150
+ test("absolute value", () => {
151
+ expect("|x|").toRenderTex("\\left|x\\right|");
152
+ expect("|x|y").toRenderTex("\\left|x\\right|y");
153
+ });
154
+
155
+ test("logarithms", () => {
156
+ expect("lnx").toRenderTex("\\ln(x)");
157
+ expect("logx").toRenderTex("\\log_{10}(x)");
158
+ expect("lnx^y").toRenderTex("\\ln(x^{y})");
159
+ expect("logx^y").toRenderTex("\\log_{10}(x^{y})");
160
+ expect("(lnx)^y").toRenderTex("[\\ln(x)]^{y}");
161
+ expect("(logx)^y").toRenderTex("[\\log_{10}(x)]^{y}");
162
+ });
163
+
164
+ test("trig functions", () => {
165
+ expect("sinx").toRenderTex("\\sin(x)");
166
+
167
+ expect("arcsin x").toRenderTex("\\arcsin(x)");
168
+ expect("sin^-1 x").toRenderTex("\\arcsin(x)");
169
+
170
+ expect("(sinx)^2").toRenderTex("\\sin^{2}(x)");
171
+ expect("sin^2 x").toRenderTex("\\sin^{2}(x)");
172
+
173
+ expect("1/(sinx)^2").toRenderTex("\\frac{1}{\\sin^{2}(x)}");
174
+ expect("1/sin^2x").toRenderTex("\\frac{1}{\\sin^{2}(x)}");
175
+
176
+ expect("sin^2 x + cos^2 x = 1").toRenderTex(
177
+ "\\sin^{2}(x)+\\cos^{2}(x) = 1",
178
+ );
179
+ });
180
+
181
+ test("hyperbolic functions", () => {
182
+ expect("sinhx").toRenderTex("\\sinh(x)");
183
+ expect("sinh^2 x").toRenderTex("\\sinh^{2}(x)");
184
+ });
185
+
186
+ test("multiplication with numbers", () => {
187
+ expect("4*10").toRenderTex("4 \\cdot 10");
188
+ expect("10^5").toRenderTex("10^{5}");
189
+ expect("4*10^5").toRenderTex("4 \\cdot 10^{5}");
190
+ expect("10^5x").toRenderTex("10^{5}x");
191
+ expect("4*10^5x").toRenderTex("4 \\cdot 10^{5}x");
192
+ expect("x*(10+4)^5").toRenderTex("x(10+4)^{5}");
193
+
194
+ expect("-1*2").toRenderTex("-1 \\cdot 2");
195
+ expect("1*-2").toRenderTex("1 \\cdot -2");
196
+ expect("-1*-2").toRenderTex("-1 \\cdot -2");
197
+ expect("-1*2*3").toRenderTex("-1 \\cdot 2 \\cdot 3");
198
+ });
199
+
200
+ test("inverses and division", () => {
201
+ expect("x^-1").toRenderTex("x^{-1}");
202
+ expect("2x^-1").toRenderTex("2x^{-1}");
203
+ expect("1/x").toRenderTex("\\frac{1}{x}");
204
+ expect("-1/x").toRenderTex("\\frac{-1}{x}");
205
+ expect("2/x").toRenderTex("\\frac{2}{x}");
206
+ expect("1/x^2").toRenderTex("\\frac{1}{x^{2}}");
207
+ expect("2/x^2").toRenderTex("\\frac{2}{x^{2}}");
208
+ expect("1/1/x").toRenderTex("\\frac{1}{x}");
209
+ expect("1/(1/x)").toRenderTex("\\frac{1}{\\frac{1}{x}}");
210
+ expect("1/x/x").toRenderTex("\\frac{1}{xx}");
211
+ expect("1/(x/x)").toRenderTex("\\frac{1}{\\frac{x}{x}}");
212
+ expect("-1/1/x").toRenderTex("\\frac{-1}{x}");
213
+ expect("-1/(1/x)").toRenderTex("\\frac{-1}{\\frac{1}{x}}");
214
+ expect("-1/x/x").toRenderTex("\\frac{-1}{xx}");
215
+ expect("-1/(x/x)").toRenderTex("\\frac{-1}{\\frac{x}{x}}");
216
+ });
217
+
218
+ test("distributive property", () => {
219
+ expect("ab+c").toRenderTex("ab+c");
220
+ expect("ab+ac").toRenderTex("ab+ac");
221
+ expect("a(b+c)").toRenderTex("a(b+c)");
222
+ });
223
+
224
+ test("numerical exponents", () => {
225
+ expect("9^4").toRenderTex("9^{4}");
226
+ expect("-9^4").toRenderTex("-9^{4}");
227
+ expect("1-9^4").toRenderTex("1-9^{4}");
228
+ });
229
+
230
+ test("negating a Mul", () => {
231
+ expect("-3x").toRenderTex("-3x");
232
+ expect("--3x").toRenderTex("--3x");
233
+ expect("-x*3").toRenderTex("-3x");
234
+ expect("--x*3").toRenderTex("--3x");
235
+ });
236
+
237
+ test("equations", () => {
238
+ expect("y=x").toRenderTex("y = x");
239
+ expect("y<x").toRenderTex("y < x");
240
+ expect("y>x").toRenderTex("y > x");
241
+ expect("y<>x").toRenderTex("y \\ne x");
242
+ expect("y=/=x").toRenderTex("y \\ne x");
243
+ expect("y<=x").toRenderTex("y \\le x");
244
+ expect("y>=x").toRenderTex("y \\ge x");
245
+ });
246
+
247
+ test("function variables", () => {
248
+ expect("f(x)").toRenderTex("fx");
249
+ expect("f(x)").toRenderTex("f(x)", {functions: ["f"]});
250
+ expect("f(sin x)").toRenderTex("f(\\sin(x))", {functions: ["f"]});
251
+ expect("sin f(x)").toRenderTex("\\sin(f(x))", {functions: ["f"]});
252
+ });
253
+
254
+ test("options", () => {
255
+ expect("x").toRenderTexOpt("x");
256
+ expect("x").toRenderTexOpt("\\displaystyle x", "display");
257
+ expect("a(b+c(d+e))").toRenderTexOpt("a(b+c(d+e))");
258
+ expect("a(b+c(d+e))").toRenderTexOpt(
259
+ "a\\left(b+c\\left(d+e\\right)\\right)",
260
+ "dynamic",
261
+ );
262
+ expect("2*2").toRenderTexOpt("2 \\cdot 2");
263
+ expect("2*2").toRenderTexOpt("2 \\times 2", "times");
264
+
265
+ expect("1*2(3+4)").toRenderTexOpt(
266
+ "\\displaystyle 1 \\times 2\\left(3+4\\right)",
267
+ "display",
268
+ "dynamic",
269
+ "times",
270
+ );
271
+ });
272
+ });
@@ -0,0 +1,331 @@
1
+ import _ from "underscore";
2
+
3
+ import * as KAS from "../index.js";
4
+
5
+ expect.extend({
6
+ toFactorAs(input: string, reference: string) {
7
+ const actual = KAS.parse(input).expr.factor().normalize().repr();
8
+ const expected = KAS.parse(reference).expr.normalize().repr();
9
+
10
+ return actual === expected
11
+ ? {pass: true}
12
+ : {pass: false, message: () => `${input} factors as ${reference}`};
13
+ },
14
+ toExpandAs(input: string, reference: string) {
15
+ const actual = KAS.parse(input)
16
+ .expr.expand()
17
+ .normalize()
18
+ .print();
19
+ const expected = KAS.parse(reference).expr.normalize().print();
20
+
21
+ return actual === expected
22
+ ? {pass: true}
23
+ : {pass: false, message: () => `${input} expands as ${reference}`};
24
+ },
25
+ toExpandAsRepr(input: string, reference: string) {
26
+ const actual = KAS.parse(input).expr.expand().repr();
27
+
28
+ return actual === reference
29
+ ? {pass: true}
30
+ : {pass: false, message: () => `${input} expands as ${reference}`};
31
+ },
32
+ toExpandAsTex(input: string, reference: string) {
33
+ const actual = KAS.parse(input).expr.expand().tex();
34
+
35
+ return actual === reference
36
+ ? {pass: true}
37
+ : {pass: false, message: () => `${input} expands as ${reference}`};
38
+ },
39
+ toCollectAs(input: string, reference: string) {
40
+ const actual = KAS.parse(input)
41
+ .expr.collect()
42
+ .normalize()
43
+ .print();
44
+ const expected = KAS.parse(reference)
45
+ .expr.collect()
46
+ .normalize()
47
+ .print();
48
+
49
+ return actual === expected
50
+ ? {pass: true}
51
+ : {pass: false, message: () => `${input} collects as ${reference}`};
52
+ },
53
+ toCollectAsRepr(input: string, reference: string) {
54
+ const actual = KAS.parse(input).expr.collect().repr();
55
+
56
+ return actual === reference
57
+ ? {pass: true}
58
+ : {pass: false, message: () => `${input} collects as ${reference}`};
59
+ },
60
+ toCollectAsTex(input: string, reference: string) {
61
+ const actual = KAS.parse(input).expr.collect().tex();
62
+
63
+ return actual === reference
64
+ ? {pass: true}
65
+ : {pass: false, message: () => `${input} collects as ${reference}`};
66
+ },
67
+ toSimplifyAs(input: string, reference: string) {
68
+ var actual = KAS.parse(input).expr.simplify().normalize().print();
69
+ var expected = KAS.parse(reference).expr.normalize().print();
70
+
71
+ return actual === expected
72
+ ? {pass: true}
73
+ : {pass: false, message: () => `${input} simplifies as ${reference}`};
74
+ },
75
+ });
76
+
77
+ describe("transforming", () => {
78
+ test("factoring Adds", () => {
79
+ expect("2+2").toFactorAs("2(1+1)");
80
+ expect("-2-2").toFactorAs("-2(1+1)");
81
+ expect("2x+2").toFactorAs("2(x+1)");
82
+ expect("x^3+x^2").toFactorAs("x^2(x+1)");
83
+ expect("2x+xy").toFactorAs("x(2+y)");
84
+ expect("2xy+xy^2").toFactorAs("xy(2+y)");
85
+ expect("2+2/3").toFactorAs("2/3(3+1)"); // a little questionable, but 2/3 is what
86
+ // wolframalpha returns for the gcd, so
87
+ // we pull it out
88
+ expect("2x+1.1").toFactorAs("2x+1.1");
89
+ });
90
+
91
+ test("factoring Muls", () => {
92
+ expect("(2x+2)/(x+1)").toFactorAs("2 (x+1)/(x+1)");
93
+ expect("(x+1)/(2x+2)").toFactorAs("1/2 (x+1)/(x+1)");
94
+ });
95
+
96
+ test("factoring Pows", () => {
97
+ expect("x^y+x^(2y)").toFactorAs("x^y(1+x^y)");
98
+ expect("x^y+x^z").toFactorAs("x^y+x^z");
99
+ });
100
+
101
+ test("distribute over multiplication", () => {
102
+ expect("a(b+c)").toExpandAs("ab+ac");
103
+ expect("a(b+c)").toExpandAsTex("ab+ac");
104
+ expect("a(b+c)").toExpandAsRepr(
105
+ "Add(Mul(Var(a),Var(b)),Mul(Var(a),Var(c)))",
106
+ );
107
+
108
+ expect("a(b-c)").toExpandAs("ab-ac");
109
+ expect("a(b-c)").toExpandAsTex("ab-ac");
110
+ expect("a(b-c)").toExpandAsRepr(
111
+ "Add(Mul(Var(a),Var(b)),Mul(Var(a),-1,Var(c)))",
112
+ );
113
+
114
+ expect("a(b+c)d").toExpandAs("abd+acd");
115
+ expect("a(b+c)d").toExpandAsRepr(
116
+ "Add(Mul(Var(a),Var(d),Var(b)),Mul(Var(a),Var(d),Var(c)))",
117
+ );
118
+
119
+ expect("(a+b)(c+d)").toExpandAs("ac+ad+bc+bd");
120
+ expect("(a+b)(c+d)ef").toExpandAs("acef+adef+bcef+bdef");
121
+ expect("(a+b)c^d").toExpandAs("ac^d+bc^d");
122
+ expect("ab(c+d)e^f").toExpandAs("abce^f+abde^f");
123
+
124
+ expect("(a+b(c+d))e").toExpandAs("ae+bce+bde");
125
+ expect("(a+b(c+d))e").toExpandAsRepr(
126
+ "Add(Mul(Const(e),Var(a)),Mul(Const(e),Var(b),Var(c)),Mul(Const(e),Var(b),Var(d)))",
127
+ );
128
+ });
129
+
130
+ test("distribute over rational expressions", () => {
131
+ expect("(a+b)/(c+d)").toExpandAs("(a+b)/(c+d)");
132
+ expect("(a+b)/(c+d)*a").toExpandAs("(aa+ab)/(c+d)");
133
+ expect("(a+b)/(c+d)*1/e").toExpandAs("(a+b)/(ce+de)");
134
+ expect("(a+b)/(c+d)*a/e").toExpandAs("(aa+ab)/(ce+de)");
135
+ });
136
+
137
+ test("expand exponentiation", () => {
138
+ expect("(ab)^2").toExpandAs("a^2 b^2");
139
+ expect("2*(ab)^2").toExpandAs("2 a^2 b^2");
140
+ expect("(a+b)^2").toExpandAs("a^2+2ab+b^2");
141
+
142
+ expect("(ab)^-2").toExpandAs("a^-2 b^-2");
143
+ expect("2*(ab)^-2").toExpandAs("2 a^-2 b^-2");
144
+ expect("(a+b)^-2").toExpandAs("(a^2+2ab+b^2)^-1");
145
+ });
146
+
147
+ test("expand absolute value", () => {
148
+ expect("|a+b|").toExpandAs("|a+b|");
149
+ expect("|ab|").toExpandAs("|a|*|b|");
150
+ });
151
+
152
+ test("expand logarithms", () => {
153
+ expect("ln(xy)").toExpandAs("lnx+lny");
154
+ expect("log_b(x)").toExpandAs("lnx/lnb");
155
+ expect("log_b(xy)").toExpandAs("lnx/lnb+lny/lnb");
156
+ expect("ln(xy/z)").toExpandAs("lnx+lny-lnz");
157
+
158
+ expect("ln(x^y)").toExpandAs("ylnx");
159
+ expect("log_b(x^y)").toExpandAs("ylnx/lnb");
160
+ expect("ln(x^y^z)").toExpandAs("y^zlnx");
161
+
162
+ expect("ln(x^y/z)").toExpandAs("ylnx-lnz");
163
+
164
+ // ln((xy)^z) -> ln(x^z*y^z) -> z*ln(x)+z*ln(y)
165
+ expect("ln((xy)^z)").toExpandAs("zlnx+zlny");
166
+
167
+ expect("log_b(x)log_x(y)").toExpandAs("ln(x)/ln(b)*ln(y)/ln(x)");
168
+ expect("log_b(x)log_x(y)log_y(z)").toExpandAs(
169
+ "ln(x)/ln(b)*ln(y)/ln(x)*ln(z)/ln(y)",
170
+ );
171
+ });
172
+
173
+ test("expand trig functions", () => {
174
+ expect("sin(x)").toExpandAs("sin(x)");
175
+ expect("cos(x)").toExpandAs("cos(x)");
176
+ expect("tan(x)").toExpandAs("sin(x)/cos(x)");
177
+ expect("csc(x)").toExpandAs("1/sin(x)");
178
+ expect("sec(x)").toExpandAs("1/cos(x)");
179
+ expect("cot(x)").toExpandAs("cos(x)/sin(x)");
180
+ });
181
+
182
+ test("expand hyperbolic functions", () => {
183
+ expect("sinh(x)").toExpandAs("sinh(x)");
184
+ expect("cosh(x)").toExpandAs("cosh(x)");
185
+ expect("tanh(x)").toExpandAs("sinh(x)/cosh(x)");
186
+ expect("csch(x)").toExpandAs("1/sinh(x)");
187
+ expect("sech(x)").toExpandAs("1/cosh(x)");
188
+ expect("coth(x)").toExpandAs("cosh(x)/sinh(x)");
189
+ });
190
+
191
+ test("collect over addition", () => {
192
+ expect("").toCollectAs("0");
193
+ expect("0").toCollectAs("0");
194
+ expect("1+3").toCollectAs("4");
195
+ expect("x+3").toCollectAs("3+x");
196
+ expect("x+3x").toCollectAs("4x");
197
+ expect("x+3x").toCollectAsRepr("Mul(4,Var(x))");
198
+ expect("a+a+a").toCollectAs("3a");
199
+ expect("a+a+a").toCollectAsRepr("Mul(3,Var(a))");
200
+ expect("a+b+b+c").toCollectAs("a+2b+c");
201
+ expect("a+b+b+c").toCollectAsRepr("Add(Var(a),Mul(2,Var(b)),Var(c))");
202
+ expect("4x^2-x^2+8x+7-5x-4").toCollectAs("3+3x+3x^2");
203
+ });
204
+
205
+ test("collect over multiplication", () => {
206
+ expect("5*7").toCollectAs("35");
207
+ expect("5*7x+20x").toCollectAs("55x");
208
+ expect("3x*xy+2yx^2").toCollectAs("5x^2y");
209
+ expect("4/6").toCollectAs("2/3");
210
+ expect("1/1").toCollectAs("1");
211
+ expect("1/2+1/3").toCollectAs("5/6");
212
+ expect("1/2+1/3+1").toCollectAs("11/6");
213
+ expect("1.2+1/2").toCollectAs("1.7");
214
+ expect("1/2-1/2").toCollectAs("0");
215
+ expect("1/2-.5").toCollectAs("0");
216
+ });
217
+
218
+ test("collect over exponentiation", () => {
219
+ expect("x^0").toCollectAs("1");
220
+ expect("x^1").toCollectAs("x");
221
+ expect("x^(log_x y)").toCollectAs("y");
222
+ expect("(x^y)^z").toCollectAs("x^(yz)");
223
+ expect("0^0").toCollectAs("1");
224
+ expect("4^1.5").toCollectAs("8");
225
+ expect("(2/3)^2").toCollectAs("4/9");
226
+ expect("(2/3)^-2").toCollectAs("9/4");
227
+ });
228
+
229
+ test("collect over roots", () => {
230
+ expect("sqrt(2)^2").toCollectAs("2");
231
+ expect("sqrt[3]{3}^3").toCollectAs("3");
232
+ expect("(2^(1/3))^3").toCollectAs("2");
233
+ });
234
+
235
+ test("collect over absolute value", () => {
236
+ expect("|x|").toCollectAs("|x|");
237
+ expect("|2|").toCollectAs("2");
238
+ expect("|0|").toCollectAs("0");
239
+ expect("|-2|").toCollectAs("2");
240
+ expect("|pi|").toCollectAs("pi");
241
+ expect("|2^x|").toCollectAs("2^x");
242
+ expect("|x^2|").toCollectAs("x^2");
243
+ expect("|-2pix^2y^3|").toCollectAs("2pix^2*|y^3|");
244
+ });
245
+
246
+ test("collect over logarithms", () => {
247
+ expect("log(1)").toCollectAs("0");
248
+ expect("log_x(x)").toCollectAs("1");
249
+ expect("log_b(b^x)").toCollectAs("x");
250
+
251
+ expect("b^(2*y*log_b x)").toCollectAs("x^(2y)");
252
+ expect("b^(log_b a) b^(-log_b c)").toCollectAs("a/c");
253
+
254
+ expect("ln(x)/ln(b)").toCollectAs("log_b(x)");
255
+ expect("ln(x)/ln(b)*ln(y)/ln(x)").toCollectAs("log_b(y)");
256
+ expect("ln(x)/ln(b)*ln(y)/ln(x)*ln(z)/ln(y)").toCollectAs("log_b(z)");
257
+ });
258
+
259
+ test("collect trig functions", () => {
260
+ expect("sin(x)cos(x)").toCollectAs("sin(x)cos(x)");
261
+ expect("sin(x)/cos(x)").toCollectAs("tan(x)");
262
+
263
+ expect("sin^2(x)/cos^2(x)").toCollectAs("tan^2(x)");
264
+ expect("cos^2(x)/sin^2(x)").toCollectAs("cot^2(x)");
265
+
266
+ expect("sin^-2(x)/cos^-2(x)").toCollectAs("cot^2(x)");
267
+ expect("cos^-2(x)/sin^-2(x)").toCollectAs("tan^2(x)");
268
+
269
+ expect("sin^--2(x)/cos^--2(x)").toCollectAs("tan^2(x)");
270
+ expect("cos^--2(x)/sin^--2(x)").toCollectAs("cot^2(x)");
271
+
272
+ expect("sin^2(x)/cos(x)").toCollectAs("sin^2(x)/cos(x)");
273
+ expect("sin(x)/cos^2(x)").toCollectAs("sin(x)/cos^2(x)");
274
+
275
+ expect("sin(-x)").toCollectAs("-sin(x)");
276
+ expect("cos(-x)").toCollectAs("cos(x)");
277
+ expect("tan(-x)").toCollectAs("-tan(x)");
278
+ expect("csc(-x)").toCollectAs("-csc(x)");
279
+ expect("sec(-x)").toCollectAs("sec(x)");
280
+ expect("cot(-x)").toCollectAs("-cot(x)");
281
+
282
+ expect("sin(--x)").toCollectAs("sin(x)");
283
+ expect("arcsin(-x)").toCollectAs("arcsin(-x)");
284
+
285
+ expect("sin(-x)cos(-x)").toCollectAs("-sin(x)cos(x)");
286
+ expect("sin(-x)/cos(-x)").toCollectAs("-tan(x)");
287
+ });
288
+
289
+ test("collect then output tex", () => {
290
+ // user-friendly tex representation is not guaranteed after collect(assert, )
291
+ expect("-x").toCollectAs("-1x");
292
+ expect("-x").toCollectAsTex("-1x");
293
+ expect("a-b").toCollectAs("a+-1b");
294
+ expect("a-b").toCollectAsTex("a+-1b");
295
+ expect("a/b").toCollectAs("ab^-1");
296
+ expect("a/b").toCollectAsTex("ab^{-1}");
297
+ });
298
+
299
+ test("collect over an equation", () => {
300
+ // collect does not try to collect across both sides of an equation
301
+ expect("y+1-1=x*x").toCollectAs("y=x^(2)");
302
+ expect("1+y=1+x^2").toCollectAs("1+y=1+x^(2)");
303
+ });
304
+
305
+ test("simplify", () => {
306
+ expect("(a+b)^2").toSimplifyAs("(a+b)^2");
307
+ expect("(a+b)(a+b)").toSimplifyAs("(a+b)^2");
308
+
309
+ // (ab)^2 ->[factor]-> a^2 * b^2 ->[collect]-> a^2 * b^2
310
+ // (no change during collect, therefore factoring is rolled back)
311
+ expect("(ab)^2").toSimplifyAs("(ab)^2");
312
+
313
+ // (3x)^2 ->[factor]-> 3^2 * x^2 ->[collect]-> 9x^2
314
+ // (changed during collect, therefore factoring persists)
315
+ expect("(3x)^2").toSimplifyAs("9x^2");
316
+
317
+ expect("(2sqrt(2))^4").toSimplifyAs("64");
318
+ expect("(3sqrt[3]{3})^9").toSimplifyAs("531441");
319
+
320
+ // from "Simplifying expressions with exponents"
321
+ expect("((nx^5)^5)").toSimplifyAs("n^5 x^25");
322
+ expect("((nx^5)^5)/2").toSimplifyAs("1/2 n^5 x^25");
323
+ expect("((nx^5)^5)/(n^-2x^2)^-3").toSimplifyAs("n^-1 x^31");
324
+
325
+ expect("1/(xya)+1/(xyb)").toSimplifyAs("1/(xya)+1/(xyb)");
326
+
327
+ // Simplify rationals correctly
328
+ expect("2*(x+1/3)").toSimplifyAs("2*x+2/3");
329
+ expect("-1*(1/3+x)").toSimplifyAs("-1*x+-1/3");
330
+ });
331
+ });