@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.
- package/CHANGELOG.md +7 -0
- package/LICENSE.txt +21 -0
- package/README.md +94 -0
- package/dist/es/index.js +2 -0
- package/dist/es/index.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/index.js.flow +2 -0
- package/dist/index.js.map +1 -0
- package/experimenter.html +75 -0
- package/package.json +38 -0
- package/src/__genfiles__/parser.js +840 -0
- package/src/__genfiles__/unitparser.js +679 -0
- package/src/__tests__/checking-form_test.js +76 -0
- package/src/__tests__/comparing_test.js +322 -0
- package/src/__tests__/compilation_test.js +97 -0
- package/src/__tests__/evaluating_test.js +73 -0
- package/src/__tests__/index_test.js +364 -0
- package/src/__tests__/parsing_test.js +480 -0
- package/src/__tests__/rendering_test.js +272 -0
- package/src/__tests__/transforming_test.js +331 -0
- package/src/__tests__/units_test.js +188 -0
- package/src/compare.js +69 -0
- package/src/index.js +2 -0
- package/src/nodes.js +3504 -0
- package/src/parser-generator.js +212 -0
- package/src/unitvalue.jison +161 -0
|
@@ -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
|
+
});
|