@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,188 @@
1
+ import _ from "underscore";
2
+
3
+ import * as KAS from "../index.js";
4
+
5
+ expect.extend({
6
+ toHaveEqualUnits([x, y]: [$FlowFixMe, $FlowFixMe], msg: string) {
7
+ let actual = KAS.compare(x.simplify(), y.simplify()).equal;
8
+
9
+ if (this.isNot) {
10
+ return {
11
+ pass: actual,
12
+ message: () => msg,
13
+ };
14
+ }
15
+
16
+ return {
17
+ pass: actual,
18
+ message: () => msg,
19
+ };
20
+ },
21
+ toParseUnitsAsEqual([x, y]: [string, string], msg: string) {
22
+ if (this.isNot) {
23
+ expect([
24
+ KAS.unitParse(x).expr,
25
+ KAS.unitParse(y).expr,
26
+ ]).not.toHaveEqualUnits(msg);
27
+ } else {
28
+ expect([
29
+ KAS.unitParse(x).expr,
30
+ KAS.unitParse(y).expr,
31
+ ]).toHaveEqualUnits(msg);
32
+ }
33
+
34
+ return {pass: !this.isNot};
35
+ },
36
+ toHaveUnitVariable(
37
+ [original, newUnit]: [string, string],
38
+ expected: $FlowFixMe,
39
+ ) {
40
+ const originalParsed = KAS.unitParse(original).expr;
41
+ const newUnitParsed = KAS.unitParse(newUnit).unit;
42
+ const x = new KAS.Var("x");
43
+ const equality = new KAS.Eq(
44
+ originalParsed,
45
+ "=",
46
+ new KAS.Mul(x, newUnitParsed),
47
+ );
48
+ const answer = equality.solveLinearEquationForVariable(x);
49
+ return Math.round(answer.eval()) == Math.round(expected.eval())
50
+ ? {pass: true}
51
+ : {
52
+ pass: false,
53
+ message: () =>
54
+ `${original} = ["${expected.print()}"] ${newUnit}`,
55
+ };
56
+ },
57
+ toHaveTheSameForm([x, y]: [string, string], msg: string) {
58
+ const equal = KAS.compare(
59
+ KAS.unitParse(x).unit,
60
+ KAS.unitParse(y).unit,
61
+ ).equal;
62
+
63
+ return equal ? {pass: true} : {pass: false, message: () => msg};
64
+ },
65
+ toHaveMagnitude(input: string, expected: number) {
66
+ const parsed = KAS.unitParse(input).coefficient;
67
+
68
+ return +parsed === expected
69
+ ? {pass: true}
70
+ : {
71
+ pass: false,
72
+ message: () => `magnitude of ${input} is not ${expected}`,
73
+ };
74
+ },
75
+ });
76
+
77
+ describe("units", () => {
78
+ test.only("simplify expressions with units", () => {
79
+ expect([
80
+ new KAS.Mul(new KAS.Rational(1, 100), new KAS.Unit("cup")),
81
+ new KAS.Mul(new KAS.Rational(1, 400), new KAS.Unit("qt")),
82
+ ]).toHaveEqualUnits("1/100 cup = 1 / 400 quart");
83
+
84
+ expect(["10 g", "0.01 kg"]).toParseUnitsAsEqual("10 g = 1 / 100 kg");
85
+
86
+ expect(["9.8 kg m / s^2", "9.8 N"]).toParseUnitsAsEqual(
87
+ "9.8 kg m / s^2 = 9.8 N",
88
+ );
89
+
90
+ expect(["9.8 m / s^2", "9.8 N / kg"]).toParseUnitsAsEqual(
91
+ "9.8 m / s^2 = 9.8 N / kg",
92
+ );
93
+
94
+ expect([
95
+ new KAS.Mul(
96
+ new KAS.Float(9.8),
97
+ new KAS.Unit("m"),
98
+ new KAS.Pow(new KAS.Unit("s"), new KAS.Int(-2)),
99
+ ),
100
+ new KAS.Mul(
101
+ new KAS.Float(9.8),
102
+ new KAS.Pow(new KAS.Unit("s"), new KAS.Int(-2)),
103
+ new KAS.Unit("m"),
104
+ ),
105
+ ]).toHaveEqualUnits("9.8 m / s^2 = 9.8 s^-2 m");
106
+
107
+ expect([
108
+ new KAS.Mul(
109
+ new KAS.Int(50),
110
+ new KAS.Unit("m"),
111
+ new KAS.Pow(new KAS.Unit("m"), new KAS.Int(-1)),
112
+ ),
113
+ new KAS.Int(50),
114
+ ]).toHaveEqualUnits("50 m / m = 50");
115
+
116
+ // There's a long chain of conversions before this is fully simplified.
117
+ // tsp -> tbsp -> cup -> gal -> L -> m^3
118
+ //
119
+ // 1 tbsp 1 cup 1 gal 3.785 L
120
+ // 1 tsp * ------ * ------- * ------ * -------
121
+ // 3 tsp 16 tbsp 16 cup gal
122
+ expect([
123
+ new KAS.Unit("tsp"),
124
+ new KAS.Mul(
125
+ new KAS.Rational(1, 3),
126
+ new KAS.Rational(1, 16),
127
+ new KAS.Rational(1, 16),
128
+ new KAS.Float(3.785),
129
+ new KAS.Unit("L"),
130
+ ),
131
+ ]).toHaveEqualUnits("tsp reduces");
132
+
133
+ expect(
134
+ KAS.compare(
135
+ new KAS.Mul(new KAS.Int(50), new KAS.Unit("m")),
136
+ new KAS.Int(50),
137
+ ).equal,
138
+ ).toBeFalse();
139
+
140
+ expect(["50 m", "50 A"]).not.toParseUnitsAsEqual("50 m != 50 A");
141
+ expect(["5000 mA", "5 A"]).toParseUnitsAsEqual("5000 mA = 5 A");
142
+ expect(["5 mA", "5 A"]).not.toParseUnitsAsEqual("5 mA != 5 A");
143
+
144
+ expect(["9.8 kg m / s^2", "9.8 J"]).not.toParseUnitsAsEqual(
145
+ "9.8 kg m / s^2 != 9.8 J",
146
+ );
147
+ expect(["9.8 kg m / s^2", "9.8 J/m"]).toParseUnitsAsEqual(
148
+ "9.8 kg m / s^2 = 9.8 J/m",
149
+ );
150
+
151
+ expect(["mA", "A"]).not.toHaveTheSameForm("mA !== A");
152
+ expect(["g", "kg"]).not.toHaveTheSameForm("g !== kg");
153
+ expect(["kg m / s^2", "N"]).not.toHaveTheSameForm("kg m / s^2 !== N");
154
+ expect(["m / s^2", "N / kg"]).not.toHaveTheSameForm("m / s^2 !== N / kg");
155
+
156
+ expect(["A", "A"]).toHaveTheSameForm("A === A");
157
+ expect(["kg", "kg"]).toHaveTheSameForm("kg = kg");
158
+ expect(
159
+ ["kg m / s^2", "m kg / s^2"],
160
+ ).toHaveTheSameForm(
161
+ "kg m / s^2 === m kg / s^2",
162
+ );
163
+ expect(["m / s^2", "m / s^2"]).toHaveTheSameForm("m / s^2 === m / s^2");
164
+
165
+ expect(["2.5 gal", "cup"]).toHaveUnitVariable(new KAS.Int(40));
166
+
167
+ // 1 tbsp 1 cup 1 gal 3785 L 1 m^3 ( 100 cm ) 3
168
+ // 1 tsp * ------ * ------- * ------ * -------- * ------ * (--------)
169
+ // 3 tsp 16 tbsp 16 cup 1000 gal 1000 L ( m )
170
+ expect(["1 tsp", "cm^3"]).toHaveUnitVariable(
171
+ new KAS.Rational(3785, 768),
172
+ );
173
+
174
+ expect(["1 dozen qt", "gal"]).toHaveUnitVariable(new KAS.Int(3));
175
+
176
+ expect(["1 day", "sec"]).toHaveUnitVariable(new KAS.Int(60 * 60 * 24));
177
+
178
+ expect(["5 kg m / s^2", "N"]).toHaveUnitVariable(new KAS.Int(5));
179
+
180
+ // This was originally broken due to a scientific notation parsing
181
+ // problem. Leaving it around because it doesn't hurt anything.
182
+ expect(["5 x 10^5 N", "lb"]).toHaveUnitVariable(new KAS.Int(112404));
183
+
184
+ expect("5 x 10^5 N").toHaveMagnitude(500000);
185
+ expect("1.23456 x 10^5 N").toHaveMagnitude(123456);
186
+ expect("3.14 x 10^-2 N").toHaveMagnitude(0.0314);
187
+ });
188
+ });
package/src/compare.js ADDED
@@ -0,0 +1,69 @@
1
+ import _ from "underscore";
2
+
3
+ // Assumes that both expressions have already been parsed
4
+ // TODO(alex): be able to pass a random() function to compare()
5
+ export const compare = function(expr1, expr2, options) {
6
+ var defaults = {
7
+ form: false, // Check that the two expressions have the same form
8
+ simplify: false, // Check that the second expression is simplified
9
+ };
10
+
11
+ /* Options that could be added in the future:
12
+ * - Allow ratios: e.g. 3/1 and 3 should both be accepted for something
13
+ * like slope
14
+ * - Allow student to choose their own variable names
15
+ */
16
+
17
+ if (options !== undefined) {
18
+ // eslint-disable-next-line no-undef
19
+ options = _.extend(defaults, options);
20
+ } else {
21
+ options = defaults;
22
+ }
23
+
24
+ // TODO(CP-1614): Figure out how to make these messages translatable
25
+
26
+ // Variable check
27
+ var vars = expr1.sameVars(expr2);
28
+ if (!vars.equal) {
29
+ var message;
30
+ if (vars.equalIgnoringCase) {
31
+ message =
32
+ "Check your variables; one or more are using " +
33
+ "the wrong case (upper or lower).";
34
+ } else {
35
+ message =
36
+ "Check your variables; you may have used the wrong " +
37
+ "letter for one or more of them.";
38
+ }
39
+ return {
40
+ equal: false,
41
+ wrongVariableCase: vars.equalIgnoringCase,
42
+ wrongVariableNames: !vars.equalIgnoringCase,
43
+ message: message,
44
+ };
45
+ }
46
+
47
+ // Semantic check
48
+ if (!expr1.compare(expr2)) {
49
+ return {equal: false, message: null};
50
+ }
51
+
52
+ // Syntactic check
53
+ if (options.form && !expr1.sameForm(expr2)) {
54
+ return {
55
+ equal: false,
56
+ message: "Your answer is not in the correct form.",
57
+ };
58
+ }
59
+
60
+ // Syntactic check
61
+ if (options.simplify && !expr1.isSimplified()) {
62
+ return {
63
+ equal: false,
64
+ message: "Your answer is not fully expanded and simplified.",
65
+ };
66
+ }
67
+
68
+ return {equal: true, message: null};
69
+ };
package/src/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export * from "./nodes.js";
2
+ export {compare} from "./compare.js";