@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
package/src/nodes.js
ADDED
|
@@ -0,0 +1,3504 @@
|
|
|
1
|
+
/* TODO(charlie): fix these lint errors (http://eslint.org/docs/rules): */
|
|
2
|
+
/* eslint-disable indent, no-undef, no-var, one-var, no-dupe-keys, no-new-func, no-redeclare, no-unused-vars, comma-dangle, max-len, prefer-spread, space-infix-ops, space-unary-ops */
|
|
3
|
+
import _ from "underscore";
|
|
4
|
+
|
|
5
|
+
import {unitParser} from "./__genfiles__/unitparser.js";
|
|
6
|
+
import {parser} from "./__genfiles__/parser.js";
|
|
7
|
+
|
|
8
|
+
/* The node hierarcy is as follows:
|
|
9
|
+
|
|
10
|
+
(Expr)
|
|
11
|
+
(Seq) 2+ children
|
|
12
|
+
Add
|
|
13
|
+
Mul
|
|
14
|
+
Pow 2 children
|
|
15
|
+
Log 2 children
|
|
16
|
+
Eq 2 children
|
|
17
|
+
Trig 1 child
|
|
18
|
+
Abs 1 child
|
|
19
|
+
(Symbol)
|
|
20
|
+
Func 1 child e.g. f(x)
|
|
21
|
+
Var leaf node e.g. x, x_n
|
|
22
|
+
Const leaf node e.g. pi, e, <i>
|
|
23
|
+
Unit leaf node e.g. kg
|
|
24
|
+
(Num) leaf node
|
|
25
|
+
Rational e.g. 2/3
|
|
26
|
+
Int
|
|
27
|
+
Float
|
|
28
|
+
|
|
29
|
+
(abstract, not meant to be instantiated)
|
|
30
|
+
|
|
31
|
+
== Key design concepts ==
|
|
32
|
+
Functional: All methods return new nodes - nodes are never mutated.
|
|
33
|
+
Ignore commutativity: Commutative inputs should be parsed equivalently.
|
|
34
|
+
Exploit commutativity: Output should take advantage of ordering.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/* non user-facing functions */
|
|
38
|
+
|
|
39
|
+
// assert that all abstract methods have been overridden
|
|
40
|
+
var abstract = function() {
|
|
41
|
+
// Try to give people a bit of information when this happens
|
|
42
|
+
throw new Error("Abstract method - must override for expr: " +
|
|
43
|
+
this.print());
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// throw an error that is meant to be caught by the test suite (not user facing)
|
|
47
|
+
var error = function(message) { throw new Error(message); };
|
|
48
|
+
|
|
49
|
+
// reliably detect NaN
|
|
50
|
+
var isNaN = function(object) { return object !== object; };
|
|
51
|
+
|
|
52
|
+
// return a random float between min (inclusive) and max (exclusive),
|
|
53
|
+
// not that inclusivity means much, probabilistically, on floats
|
|
54
|
+
var randomFloat = function(min, max) {
|
|
55
|
+
var extent = max - min;
|
|
56
|
+
return Math.random() * extent + min;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/* constants */
|
|
60
|
+
var ITERATIONS = 12;
|
|
61
|
+
var TOLERANCE = 9; // decimal places
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
/* abstract base expression node */
|
|
65
|
+
function Expr() {}
|
|
66
|
+
|
|
67
|
+
_.extend(Expr.prototype, {
|
|
68
|
+
|
|
69
|
+
// this node's immediate constructor
|
|
70
|
+
func: abstract,
|
|
71
|
+
|
|
72
|
+
// an array of the arguments to this node's immediate constructor
|
|
73
|
+
args: abstract,
|
|
74
|
+
|
|
75
|
+
// make a new node with the given arguments
|
|
76
|
+
construct: function(args) {
|
|
77
|
+
var instance = new this.func();
|
|
78
|
+
this.func.apply(instance, args);
|
|
79
|
+
return instance;
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
// an abstraction for chainable, bottom-up recursion
|
|
83
|
+
recurse: function(method) {
|
|
84
|
+
var passed = Array.prototype.slice.call(arguments, 1);
|
|
85
|
+
var args = _.map(this.args(), function(arg) {
|
|
86
|
+
return _.isString(arg) ? arg : arg[method].apply(arg, passed);
|
|
87
|
+
});
|
|
88
|
+
return this.construct(args);
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
// evaluate numerically with given variable mapping
|
|
92
|
+
eval: abstract,
|
|
93
|
+
|
|
94
|
+
codegen: abstract,
|
|
95
|
+
|
|
96
|
+
compile: function() {
|
|
97
|
+
var code = this.codegen();
|
|
98
|
+
try {
|
|
99
|
+
return new Function("vars", "return " + code + ";");
|
|
100
|
+
} catch (e) {
|
|
101
|
+
throw new Error("Function did not compile: " + code);
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
// returns a string unambiguously representing the expression
|
|
106
|
+
// should be valid as input
|
|
107
|
+
// e.g. this.equals(parse(this.print())) === true
|
|
108
|
+
print: abstract,
|
|
109
|
+
|
|
110
|
+
// returns a TeX string representing the expression
|
|
111
|
+
tex: abstract,
|
|
112
|
+
|
|
113
|
+
// returns a TeX string, modified by the given options
|
|
114
|
+
asTex: function(options) {
|
|
115
|
+
|
|
116
|
+
options = options || {};
|
|
117
|
+
_.defaults(options, {
|
|
118
|
+
display: true,
|
|
119
|
+
dynamic: true,
|
|
120
|
+
times: false
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
var tex = this.tex();
|
|
124
|
+
|
|
125
|
+
if (options.display) {
|
|
126
|
+
tex = "\\displaystyle " + tex;
|
|
127
|
+
}
|
|
128
|
+
if (options.dynamic) {
|
|
129
|
+
tex = tex.replace(/\(/g, "\\left(");
|
|
130
|
+
tex = tex.replace(/\)/g, "\\right)");
|
|
131
|
+
}
|
|
132
|
+
if (options.times) {
|
|
133
|
+
tex = tex.replace(/\\cdot/g, "\\times");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return tex;
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
// returns the name of this expression's constructor as a string
|
|
140
|
+
// only used for testing and debugging (the ugly regex is for IE8)
|
|
141
|
+
name: function() {
|
|
142
|
+
if (this.func.name) {
|
|
143
|
+
return this.func.name;
|
|
144
|
+
} else {
|
|
145
|
+
return this.func.toString().match(/^function\s*([^\s(]+)/)[1];
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
// returns a string representing current node structure
|
|
150
|
+
repr: function() {
|
|
151
|
+
return this.name() + "(" + _.map(this.args(), function(arg) {
|
|
152
|
+
return _.isString(arg) ? arg : arg.repr();
|
|
153
|
+
}).join(",") + ")";
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
// removes all negative signs
|
|
157
|
+
strip: function() { return this.recurse("strip"); },
|
|
158
|
+
|
|
159
|
+
// canonically reorders all commutative elements
|
|
160
|
+
normalize: function() { return this.recurse("normalize"); },
|
|
161
|
+
|
|
162
|
+
// expands the expression
|
|
163
|
+
expand: function() { return this.recurse("expand"); },
|
|
164
|
+
|
|
165
|
+
// naively factors out like terms
|
|
166
|
+
factor: function(options) { return this.recurse("factor", options); },
|
|
167
|
+
|
|
168
|
+
// collect all like terms
|
|
169
|
+
collect: function(options) { return this.recurse("collect", options); },
|
|
170
|
+
|
|
171
|
+
// strict syntactic equality check
|
|
172
|
+
equals: function(other) {
|
|
173
|
+
return this.normalize().print() === other.normalize().print();
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
// expand and collect until the expression no longer changes
|
|
177
|
+
simplify: function(options) {
|
|
178
|
+
options = _.extend({
|
|
179
|
+
once: false
|
|
180
|
+
}, options);
|
|
181
|
+
|
|
182
|
+
// Attempt to factor and collect
|
|
183
|
+
var step1 = this.factor(options);
|
|
184
|
+
var step2 = step1.collect(options);
|
|
185
|
+
|
|
186
|
+
// Rollback if collect didn't do anything
|
|
187
|
+
if (step1.equals(step2)) {
|
|
188
|
+
step2 = this.collect(options);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Attempt to expand and collect
|
|
192
|
+
var step3 = step2.expand(options);
|
|
193
|
+
var step4 = step3.collect(options);
|
|
194
|
+
|
|
195
|
+
// Rollback if collect didn't do anything
|
|
196
|
+
if (step3.equals(step4)) {
|
|
197
|
+
step4 = step2.collect(options);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// One round of simplification complete
|
|
201
|
+
var simplified = step4;
|
|
202
|
+
|
|
203
|
+
if (options.once || this.equals(simplified)) {
|
|
204
|
+
return simplified;
|
|
205
|
+
} else {
|
|
206
|
+
return simplified.simplify(options);
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
|
|
210
|
+
// check whether this expression is simplified
|
|
211
|
+
isSimplified: function() {
|
|
212
|
+
return this.equals(this.simplify());
|
|
213
|
+
},
|
|
214
|
+
|
|
215
|
+
// return the child nodes of this node
|
|
216
|
+
exprArgs: function() {
|
|
217
|
+
return _.filter(this.args(), function(arg) {
|
|
218
|
+
return arg instanceof Expr;
|
|
219
|
+
});
|
|
220
|
+
},
|
|
221
|
+
|
|
222
|
+
// return the variables (function and non) within the expression
|
|
223
|
+
getVars: function(excludeFunc) {
|
|
224
|
+
return _.uniq(_.flatten(_.invoke(this.exprArgs(), "getVars", excludeFunc))).sort();
|
|
225
|
+
},
|
|
226
|
+
|
|
227
|
+
getConsts: function() {
|
|
228
|
+
return _.uniq(_.flatten(_.invoke(this.exprArgs(), "getConsts"))).sort();
|
|
229
|
+
},
|
|
230
|
+
|
|
231
|
+
getUnits: function() {
|
|
232
|
+
return _.flatten(_.invoke(this.exprArgs(), "getUnits"));
|
|
233
|
+
},
|
|
234
|
+
|
|
235
|
+
// check whether this expression node is of a particular type
|
|
236
|
+
is: function(func) {
|
|
237
|
+
return this instanceof func;
|
|
238
|
+
},
|
|
239
|
+
|
|
240
|
+
// check whether this expression has a particular node type
|
|
241
|
+
has: function(func) {
|
|
242
|
+
if (this instanceof func) {
|
|
243
|
+
return true;
|
|
244
|
+
}
|
|
245
|
+
return _.any(this.exprArgs(), function(arg) { return arg.has(func); });
|
|
246
|
+
},
|
|
247
|
+
|
|
248
|
+
// raise this expression to a given exponent
|
|
249
|
+
// most useful for eventually implementing i^3 = -i, etc.
|
|
250
|
+
raiseToThe: function(exp) {
|
|
251
|
+
return new Pow(this, exp);
|
|
252
|
+
},
|
|
253
|
+
|
|
254
|
+
// does this expression have a specific rendering hint?
|
|
255
|
+
// rendering hints are picked up while parsing, but are lost during transformations
|
|
256
|
+
isSubtract: function() { return false; },
|
|
257
|
+
isDivide: function() { return false; },
|
|
258
|
+
isRoot: function() { return false; },
|
|
259
|
+
|
|
260
|
+
// whether this node needs an explicit multiplication sign if following a Num
|
|
261
|
+
needsExplicitMul: function() {
|
|
262
|
+
return this.args()[0].needsExplicitMul();
|
|
263
|
+
},
|
|
264
|
+
|
|
265
|
+
// check that the variables in both expressions are the same
|
|
266
|
+
sameVars: function(other) {
|
|
267
|
+
var vars1 = this.getVars();
|
|
268
|
+
var vars2 = other.getVars();
|
|
269
|
+
|
|
270
|
+
// the other Expr can have more variables than this one
|
|
271
|
+
// this lets you multiply equations by other variables
|
|
272
|
+
var same = function(array1, array2) {
|
|
273
|
+
return !_.difference(array1, array2).length;
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
var lower = function(array) {
|
|
277
|
+
return _.uniq(_.invoke(array, "toLowerCase")).sort();
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
var equal = same(vars1, vars2);
|
|
281
|
+
var equalIgnoringCase = same(lower(vars1), lower(vars2));
|
|
282
|
+
|
|
283
|
+
return {equal: equal, equalIgnoringCase: equalIgnoringCase};
|
|
284
|
+
},
|
|
285
|
+
|
|
286
|
+
// semantic equality check, call after sameVars() to avoid potential false positives
|
|
287
|
+
// plug in random numbers for the variables in both expressions
|
|
288
|
+
// if they both consistently evaluate the same, then they're the same
|
|
289
|
+
compare: function(other) {
|
|
290
|
+
// equation comparisons are handled by Eq.compare()
|
|
291
|
+
if (other instanceof Eq) {
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
var varList = _.union(
|
|
296
|
+
this.getVars(/* excludeFunc */ true),
|
|
297
|
+
other.getVars(/* excludeFunc */ true));
|
|
298
|
+
|
|
299
|
+
// If the numbers are large we would like to do a relative comparison
|
|
300
|
+
// rather than an absolute one, but if they're small enough then an
|
|
301
|
+
// absolute comparison makes more sense
|
|
302
|
+
var getDelta = function(num1, num2) {
|
|
303
|
+
if (Math.abs(num1) < 1 || Math.abs(num2) < 1) {
|
|
304
|
+
return Math.abs(num1 - num2);
|
|
305
|
+
} else {
|
|
306
|
+
return Math.abs(1 - num1 / num2);
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
var equalNumbers = function(num1, num2) {
|
|
311
|
+
var delta = getDelta(num1, num2);
|
|
312
|
+
return ((num1 === num2) || /* needed if either is +/- Infinity */
|
|
313
|
+
(isNaN(num1) && isNaN(num2)) ||
|
|
314
|
+
(delta < Math.pow(10, -TOLERANCE)));
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
// if no variables, only need to evaluate once
|
|
318
|
+
if (!varList.length && !this.has(Unit) && !other.has(Unit)) {
|
|
319
|
+
return equalNumbers(this.eval(), other.eval());
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// collect here to avoid sometimes dividing by zero, and sometimes not
|
|
323
|
+
// it is better to be deterministic, e.g. x/x -> 1
|
|
324
|
+
// TODO(alex): may want to keep track of assumptions as they're made
|
|
325
|
+
var expr1 = this.collect();
|
|
326
|
+
var expr2 = other.collect();
|
|
327
|
+
|
|
328
|
+
var unitList1 = this.getUnits();
|
|
329
|
+
var unitList2 = other.getUnits();
|
|
330
|
+
|
|
331
|
+
if (!_.isEqual(unitList1, unitList2)) {
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Compare at a set number (currently 12) of points to determine
|
|
336
|
+
// equality.
|
|
337
|
+
//
|
|
338
|
+
// `range` (and `vars`) is the only variable that varies through the
|
|
339
|
+
// iterations. For each of range = 10, 100, and 1000, each random
|
|
340
|
+
// variable is picked from (-range, range).
|
|
341
|
+
//
|
|
342
|
+
// Note that because there are 12 iterations and three ranges, each
|
|
343
|
+
// range is checked four times.
|
|
344
|
+
for (var i = 0; i < ITERATIONS; i++) {
|
|
345
|
+
|
|
346
|
+
var vars = {};
|
|
347
|
+
|
|
348
|
+
// One third total iterations each with range 10, 100, and 1000
|
|
349
|
+
var range = Math.pow(10, 1 + Math.floor(3 * i / ITERATIONS));
|
|
350
|
+
|
|
351
|
+
// Half of the iterations should only use integer values.
|
|
352
|
+
// This is because expressions like (-2)^x are common but result
|
|
353
|
+
// in NaN when evaluated in JS with non-integer values of x.
|
|
354
|
+
// Without this, (-2)^x and (-2)^(x+1) both end up always being NaN
|
|
355
|
+
// and thus equivalent. With this, the most common failure case is
|
|
356
|
+
// avoided. However, less common cases such as (-2)^(x+0.1) and
|
|
357
|
+
// (-2)^(x+1.1) will still both evaluate to NaN and result in a
|
|
358
|
+
// false positive.
|
|
359
|
+
//
|
|
360
|
+
// Note that the above is only true in vanilla JS Number-land,
|
|
361
|
+
// which has no concept of complex numbers. The solution is simple:
|
|
362
|
+
// Integrate a library for handling complex numbers.
|
|
363
|
+
//
|
|
364
|
+
// TODO(alex): Add support for complex numbers, then remove this.
|
|
365
|
+
var useFloats = i % 2 === 0;
|
|
366
|
+
|
|
367
|
+
_.each(varList, function(v) {
|
|
368
|
+
vars[v] = useFloats ? randomFloat(-range, range)
|
|
369
|
+
: _.random(-range, range);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
var equal;
|
|
373
|
+
if (expr1.has(Func) || expr2.has(Func) ||
|
|
374
|
+
expr1.has(Unit) || expr2.has(Unit)) {
|
|
375
|
+
|
|
376
|
+
var result1 = expr1.partialEval(vars);
|
|
377
|
+
var result2 = expr2.partialEval(vars);
|
|
378
|
+
|
|
379
|
+
equal = result1.simplify().equals(result2.simplify());
|
|
380
|
+
} else {
|
|
381
|
+
var result1 = expr1.eval(vars);
|
|
382
|
+
var result2 = expr2.eval(vars);
|
|
383
|
+
|
|
384
|
+
equal = equalNumbers(result1, result2);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (!equal) {
|
|
388
|
+
return false;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return true;
|
|
393
|
+
},
|
|
394
|
+
|
|
395
|
+
// evaluate as much of the expression as possible
|
|
396
|
+
partialEval: function(vars) {
|
|
397
|
+
if (this instanceof Unit) {
|
|
398
|
+
return this;
|
|
399
|
+
} else if (!this.has(Func)) {
|
|
400
|
+
return new Float(this.eval(vars).toFixed(TOLERANCE)).collect();
|
|
401
|
+
} else if (this instanceof Func) {
|
|
402
|
+
return new Func(this.symbol, this.arg.partialEval(vars));
|
|
403
|
+
} else {
|
|
404
|
+
return this.recurse("partialEval", vars);
|
|
405
|
+
}
|
|
406
|
+
},
|
|
407
|
+
|
|
408
|
+
// check that the structure of both expressions is the same
|
|
409
|
+
// all negative signs are stripped and the expressions are converted to
|
|
410
|
+
// a canonical commutative form
|
|
411
|
+
// should only be done after compare() returns true to avoid false positives
|
|
412
|
+
sameForm: function(other) {
|
|
413
|
+
return this.strip().equals(other.strip());
|
|
414
|
+
},
|
|
415
|
+
|
|
416
|
+
// returns the GCD of this expression and the given factor
|
|
417
|
+
findGCD: function(factor) {
|
|
418
|
+
return this.equals(factor) ? factor : Num.One;
|
|
419
|
+
},
|
|
420
|
+
|
|
421
|
+
// return this expression's denominator
|
|
422
|
+
getDenominator: function() {
|
|
423
|
+
return Num.One;
|
|
424
|
+
},
|
|
425
|
+
|
|
426
|
+
// return this expression as a Mul
|
|
427
|
+
asMul: function() {
|
|
428
|
+
return new Mul(Num.One, this);
|
|
429
|
+
},
|
|
430
|
+
|
|
431
|
+
// TODO(alex): rename to isDefinitePositive or similar?
|
|
432
|
+
// return whether this expression is 100% positive
|
|
433
|
+
isPositive: abstract,
|
|
434
|
+
|
|
435
|
+
// TODO(alex): rename to hasNegativeSign or similar?
|
|
436
|
+
// return whether this expression has a negative sign
|
|
437
|
+
isNegative: function() { return false; },
|
|
438
|
+
|
|
439
|
+
// return a factor of this expression that is 100% positive
|
|
440
|
+
asPositiveFactor: function() {
|
|
441
|
+
return this.isPositive() ? this : Num.One;
|
|
442
|
+
},
|
|
443
|
+
|
|
444
|
+
// return a copy of the expression with a new hint set (preserves hints)
|
|
445
|
+
addHint: function(hint) {
|
|
446
|
+
if (!hint) {
|
|
447
|
+
return this;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
var expr = this.construct(this.args());
|
|
451
|
+
expr.hints = _.clone(this.hints);
|
|
452
|
+
expr.hints[hint] = true;
|
|
453
|
+
return expr;
|
|
454
|
+
},
|
|
455
|
+
|
|
456
|
+
hints: {
|
|
457
|
+
parens: false
|
|
458
|
+
},
|
|
459
|
+
|
|
460
|
+
// currently unused!
|
|
461
|
+
asExpr: function() { return this; },
|
|
462
|
+
|
|
463
|
+
// complete parse by performing a few necessary transformations
|
|
464
|
+
completeParse: function() { return this.recurse("completeParse"); },
|
|
465
|
+
|
|
466
|
+
abs: abstract,
|
|
467
|
+
|
|
468
|
+
negate: function() {
|
|
469
|
+
return new Mul(Num.Neg, this);
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
/* abstract sequence node */
|
|
475
|
+
function Seq() {}
|
|
476
|
+
Seq.prototype = new Expr();
|
|
477
|
+
|
|
478
|
+
_.extend(Seq.prototype, {
|
|
479
|
+
args: function() { return this.terms; },
|
|
480
|
+
|
|
481
|
+
normalize: function() {
|
|
482
|
+
var terms = _.sortBy(_.invoke(this.terms, "normalize"), function(term) {
|
|
483
|
+
return term.print();
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
return new this.func(terms);
|
|
487
|
+
},
|
|
488
|
+
|
|
489
|
+
expand: function() {
|
|
490
|
+
return this.recurse("expand").flatten();
|
|
491
|
+
},
|
|
492
|
+
|
|
493
|
+
// partition the sequence into its numeric and non-numeric parts
|
|
494
|
+
// makes no guarantees about the validity of either part!
|
|
495
|
+
partition: function() {
|
|
496
|
+
var terms = _.groupBy(this.terms, function(term) {
|
|
497
|
+
return term instanceof Num;
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
// XXX using a boolean as a key just converts it to a string. I don't
|
|
501
|
+
// think this code was written with that in mind. Probably doesn't
|
|
502
|
+
// matter except for readability.
|
|
503
|
+
var numbers = terms[true] || [];
|
|
504
|
+
var others = terms[false] || [];
|
|
505
|
+
|
|
506
|
+
return [new this.func(numbers), new this.func(others)];
|
|
507
|
+
},
|
|
508
|
+
|
|
509
|
+
// ensure that sequences have 2+ terms and no nested sequences of the same type
|
|
510
|
+
// this is a shallow flattening and will return a non-Seq if terms.length <= 1
|
|
511
|
+
flatten: function() {
|
|
512
|
+
var type = this;
|
|
513
|
+
var terms = _.reject(this.terms, function(term) {
|
|
514
|
+
return term.equals(type.identity);
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
if (terms.length === 0) {
|
|
518
|
+
return type.identity;
|
|
519
|
+
}
|
|
520
|
+
if (terms.length === 1) {
|
|
521
|
+
return terms[0];
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
var grouped = _.groupBy(terms, function(term) {
|
|
525
|
+
return term instanceof type.func;
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
// same contains the children which are Seqs of the same type as this Seq
|
|
529
|
+
var same = grouped[true] || [];
|
|
530
|
+
var others = grouped[false] || [];
|
|
531
|
+
|
|
532
|
+
var flattened = others.concat(_.flatten(_.pluck(same, "terms"), /* shallow: */ true));
|
|
533
|
+
return new type.func(flattened);
|
|
534
|
+
},
|
|
535
|
+
|
|
536
|
+
// the identity associated with the sequence
|
|
537
|
+
identity: undefined,
|
|
538
|
+
|
|
539
|
+
// reduce a numeric sequence to a Num
|
|
540
|
+
reduce: abstract,
|
|
541
|
+
|
|
542
|
+
isPositive: function() {
|
|
543
|
+
var terms = _.invoke(this.terms, "collect");
|
|
544
|
+
return _.all(_.invoke(terms, "isPositive"));
|
|
545
|
+
},
|
|
546
|
+
|
|
547
|
+
// return a new Seq with a given term replaced by a different term
|
|
548
|
+
// (or array of terms). given term can be passed directly, or by index
|
|
549
|
+
// if no new term is provided, the old one is simply removed
|
|
550
|
+
replace: function(oldTerm, newTerm) {
|
|
551
|
+
var index;
|
|
552
|
+
|
|
553
|
+
if (oldTerm instanceof Expr) {
|
|
554
|
+
index = _.indexOf(this.terms, oldTerm);
|
|
555
|
+
} else {
|
|
556
|
+
index = oldTerm;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
var newTerms = [];
|
|
560
|
+
if (_.isArray(newTerm)) {
|
|
561
|
+
newTerms = newTerm;
|
|
562
|
+
} else if (newTerm) {
|
|
563
|
+
newTerms = [newTerm];
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
var terms = this.terms.slice(0, index)
|
|
567
|
+
.concat(newTerms)
|
|
568
|
+
.concat(this.terms.slice(index + 1));
|
|
569
|
+
|
|
570
|
+
return new this.func(terms);
|
|
571
|
+
},
|
|
572
|
+
|
|
573
|
+
// syntactic sugar for replace()
|
|
574
|
+
remove: function(term) {
|
|
575
|
+
return this.replace(term);
|
|
576
|
+
},
|
|
577
|
+
|
|
578
|
+
getDenominator: function() {
|
|
579
|
+
// TODO(alex): find and return LCM
|
|
580
|
+
return new Mul(_.invoke(this.terms, "getDenominator")).flatten();
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
/* sequence of additive terms */
|
|
586
|
+
export function Add() {
|
|
587
|
+
if (arguments.length === 1) {
|
|
588
|
+
this.terms = arguments[0];
|
|
589
|
+
} else {
|
|
590
|
+
this.terms = _.toArray(arguments);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
Add.prototype = new Seq();
|
|
594
|
+
|
|
595
|
+
_.extend(Add.prototype, {
|
|
596
|
+
func: Add,
|
|
597
|
+
|
|
598
|
+
eval: function(vars, options) {
|
|
599
|
+
return _.reduce(this.terms, function(memo, term) { return memo + term.eval(vars, options); }, 0);
|
|
600
|
+
},
|
|
601
|
+
|
|
602
|
+
codegen: function() {
|
|
603
|
+
return _.map(this.terms, function(term) {
|
|
604
|
+
return "(" + term.codegen() + ")";
|
|
605
|
+
}).join(" + ") || "0";
|
|
606
|
+
},
|
|
607
|
+
|
|
608
|
+
print: function() {
|
|
609
|
+
return _.invoke(this.terms, "print").join("+");
|
|
610
|
+
},
|
|
611
|
+
|
|
612
|
+
tex: function() {
|
|
613
|
+
var tex = "";
|
|
614
|
+
|
|
615
|
+
_.each(this.terms, function(term) {
|
|
616
|
+
if (!tex || term.isSubtract()) {
|
|
617
|
+
tex += term.tex();
|
|
618
|
+
} else {
|
|
619
|
+
tex += "+" + term.tex();
|
|
620
|
+
}
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
return tex;
|
|
624
|
+
},
|
|
625
|
+
|
|
626
|
+
collect: function(options) {
|
|
627
|
+
var terms = _.invoke(this.terms, "collect", options);
|
|
628
|
+
|
|
629
|
+
// [Expr expr, Num coefficient]
|
|
630
|
+
var pairs = [];
|
|
631
|
+
|
|
632
|
+
_.each(terms, function(term) {
|
|
633
|
+
if (term instanceof Mul) {
|
|
634
|
+
var muls = term.partition();
|
|
635
|
+
pairs.push([muls[1].flatten(), muls[0].reduce(options)]);
|
|
636
|
+
} else if (term instanceof Num) {
|
|
637
|
+
pairs.push([Num.One, term]);
|
|
638
|
+
} else {
|
|
639
|
+
pairs.push([term, Num.One]);
|
|
640
|
+
}
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
// { (Expr expr).print(): [[Expr expr, Num coefficient]] }
|
|
644
|
+
var grouped = _.groupBy(pairs, function(pair) {
|
|
645
|
+
return pair[0].normalize().print();
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
var collected = _.compact(_.map(grouped, function(pairs) {
|
|
649
|
+
var expr = pairs[0][0];
|
|
650
|
+
var sum = new Add(_.zip.apply(_, pairs)[1]);
|
|
651
|
+
var coefficient = sum.reduce(options);
|
|
652
|
+
return new Mul(coefficient, expr).collect(options);
|
|
653
|
+
}));
|
|
654
|
+
|
|
655
|
+
// TODO(alex): use the Pythagorean identity here
|
|
656
|
+
// e.g. x*sin^2(y) + x*cos^2(y) -> x
|
|
657
|
+
|
|
658
|
+
return new Add(collected).flatten();
|
|
659
|
+
},
|
|
660
|
+
|
|
661
|
+
// naively factor out anything that is common to all terms
|
|
662
|
+
// if options.keepNegative is specified, won't factor out a common -1
|
|
663
|
+
factor: function(options) {
|
|
664
|
+
options = _.extend({
|
|
665
|
+
keepNegative: false
|
|
666
|
+
}, options);
|
|
667
|
+
|
|
668
|
+
var terms = _.invoke(this.terms, "collect");
|
|
669
|
+
var factors;
|
|
670
|
+
|
|
671
|
+
if (terms[0] instanceof Mul) {
|
|
672
|
+
factors = terms[0].terms;
|
|
673
|
+
} else {
|
|
674
|
+
factors = [terms[0]];
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
_.each(_.rest(this.terms), function(term) {
|
|
678
|
+
factors = _.map(factors, function(factor) {
|
|
679
|
+
return term.findGCD(factor);
|
|
680
|
+
});
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
if (!options.keepNegative && this.isNegative()) {
|
|
684
|
+
factors.push(Num.Neg);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
factors = new Mul(factors).flatten().collect();
|
|
688
|
+
|
|
689
|
+
var remainder = _.map(terms, function(term) {
|
|
690
|
+
return Mul.handleDivide(term, factors).simplify();
|
|
691
|
+
});
|
|
692
|
+
remainder = new Add(remainder).flatten();
|
|
693
|
+
|
|
694
|
+
return Mul.createOrAppend(factors, remainder).flatten();
|
|
695
|
+
},
|
|
696
|
+
|
|
697
|
+
reduce: function(options) {
|
|
698
|
+
return _.reduce(this.terms, function(memo, term) {
|
|
699
|
+
return memo.add(term, options);
|
|
700
|
+
}, this.identity);
|
|
701
|
+
},
|
|
702
|
+
|
|
703
|
+
needsExplicitMul: function() { return false; },
|
|
704
|
+
|
|
705
|
+
isNegative: function() {
|
|
706
|
+
var terms = _.invoke(this.terms, "collect");
|
|
707
|
+
return _.all(_.invoke(terms, "isNegative"));
|
|
708
|
+
},
|
|
709
|
+
|
|
710
|
+
negate: function() {
|
|
711
|
+
return new Add(_.invoke(this.terms, "negate"));
|
|
712
|
+
}
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
|
|
716
|
+
/* sequence of multiplicative terms */
|
|
717
|
+
export function Mul() {
|
|
718
|
+
if (arguments.length === 1) {
|
|
719
|
+
this.terms = arguments[0];
|
|
720
|
+
} else {
|
|
721
|
+
this.terms = _.toArray(arguments);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
Mul.prototype = new Seq();
|
|
725
|
+
|
|
726
|
+
_.extend(Mul.prototype, {
|
|
727
|
+
func: Mul,
|
|
728
|
+
|
|
729
|
+
eval: function(vars, options) {
|
|
730
|
+
return _.reduce(this.terms, function(memo, term) { return memo * term.eval(vars, options); }, 1);
|
|
731
|
+
},
|
|
732
|
+
|
|
733
|
+
codegen: function() {
|
|
734
|
+
return _.map(this.terms, function(term) {
|
|
735
|
+
return "(" + term.codegen() + ")";
|
|
736
|
+
}).join(" * ") || "0";
|
|
737
|
+
},
|
|
738
|
+
|
|
739
|
+
print: function() {
|
|
740
|
+
return _.map(this.terms, function(term) {
|
|
741
|
+
return (term instanceof Add) ? "(" + term.print() + ")" : term.print();
|
|
742
|
+
}).join("*");
|
|
743
|
+
},
|
|
744
|
+
|
|
745
|
+
getUnits: function() {
|
|
746
|
+
var tmUnits = _(this.terms)
|
|
747
|
+
.chain()
|
|
748
|
+
.map(function(term) {
|
|
749
|
+
return term.getUnits();
|
|
750
|
+
})
|
|
751
|
+
.flatten()
|
|
752
|
+
.value();
|
|
753
|
+
|
|
754
|
+
tmUnits.sort((a, b) => a.unit.localeCompare(b.unit));
|
|
755
|
+
|
|
756
|
+
return tmUnits;
|
|
757
|
+
},
|
|
758
|
+
|
|
759
|
+
// since we don't care about commutativity, we can render a Mul any way we choose
|
|
760
|
+
// so we follow convention: first any negatives, then any numbers, then everything else
|
|
761
|
+
tex: function() {
|
|
762
|
+
var cdot = " \\cdot ";
|
|
763
|
+
|
|
764
|
+
var terms = _.groupBy(this.terms, function(term) {
|
|
765
|
+
if (term.isDivide()) {
|
|
766
|
+
return "inverse";
|
|
767
|
+
} else if (term instanceof Num) {
|
|
768
|
+
return "number";
|
|
769
|
+
} else {
|
|
770
|
+
return "other";
|
|
771
|
+
}
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
var inverses = terms.inverse || [];
|
|
775
|
+
var numbers = terms.number || [];
|
|
776
|
+
var others = terms.other || [];
|
|
777
|
+
|
|
778
|
+
var negatives = "";
|
|
779
|
+
var numerator;
|
|
780
|
+
|
|
781
|
+
// check all the numbers to see if there is a rational we can extract,
|
|
782
|
+
// since we would like 1/2x/y to come out as \frac{1}{2}\frac{x}{y},
|
|
783
|
+
// and not \frac{1x}{2y}.
|
|
784
|
+
for (var i = 0; i < numbers.length; i++) {
|
|
785
|
+
var isRational = numbers[i] instanceof Rational &&
|
|
786
|
+
!(numbers[i] instanceof Int);
|
|
787
|
+
if (isRational && others.length > 0 && inverses.length > 0) {
|
|
788
|
+
var withThisRemoved = numbers.slice();
|
|
789
|
+
withThisRemoved.splice(i, 1);
|
|
790
|
+
var newTerms = withThisRemoved.concat(inverses).concat(others);
|
|
791
|
+
return numbers[i].tex() + new Mul(newTerms).tex();
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
numbers = _.compact(_.map(numbers, function(term) {
|
|
796
|
+
var hasDenom = (term instanceof Rational) && !(term instanceof Int);
|
|
797
|
+
var shouldPushDown = !term.hints.fraction || inverses.length > 0;
|
|
798
|
+
if (hasDenom && shouldPushDown) {
|
|
799
|
+
// e.g. 3x/4 -> 3/4*x (internally) -> 3x/4 (rendered)
|
|
800
|
+
inverses.push(new Pow(new Int(term.d), Num.Div));
|
|
801
|
+
var number = new Int(term.n);
|
|
802
|
+
number.hints = term.hints;
|
|
803
|
+
return _.any(term.hints) ? number : null;
|
|
804
|
+
} else {
|
|
805
|
+
return term;
|
|
806
|
+
}
|
|
807
|
+
}));
|
|
808
|
+
|
|
809
|
+
if (numbers.length === 0 && others.length === 1) {
|
|
810
|
+
// e.g. (x+y)/z -> \frac{x+y}{z}
|
|
811
|
+
numerator = others[0].tex();
|
|
812
|
+
} else {
|
|
813
|
+
var tex = "";
|
|
814
|
+
|
|
815
|
+
_.each(numbers, function(term) {
|
|
816
|
+
if (term.hints.subtract && term.hints.entered) {
|
|
817
|
+
negatives += "-";
|
|
818
|
+
tex += (tex ? cdot : "") + term.abs().tex();
|
|
819
|
+
} else if ((term instanceof Int) && (term.n === -1) &&
|
|
820
|
+
(term.hints.negate || term.hints.subtract)) {
|
|
821
|
+
// e.g. -1*-1 -> --1
|
|
822
|
+
// e.g. -1*x -> -x
|
|
823
|
+
negatives += "-";
|
|
824
|
+
} else {
|
|
825
|
+
// e.g. 2*3 -> 2(dot)3
|
|
826
|
+
tex += (tex ? cdot : "") + term.tex();
|
|
827
|
+
}
|
|
828
|
+
});
|
|
829
|
+
|
|
830
|
+
_.each(others, function(term) {
|
|
831
|
+
if (term.needsExplicitMul()) {
|
|
832
|
+
// e.g. 2*2^3 -> 2(dot)2^3
|
|
833
|
+
tex += (tex ? cdot : "") + term.tex();
|
|
834
|
+
} else if (term instanceof Add) {
|
|
835
|
+
// e.g. (a+b)*c -> (a+b)c
|
|
836
|
+
tex += "(" + term.tex() + ")";
|
|
837
|
+
} else {
|
|
838
|
+
// e.g. a*b*c -> abc
|
|
839
|
+
tex += term.tex();
|
|
840
|
+
}
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
numerator = tex ? tex : "1";
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
if (!inverses.length) {
|
|
847
|
+
return negatives + numerator;
|
|
848
|
+
} else {
|
|
849
|
+
var denominator = new Mul(_.invoke(inverses, "asDivide")).flatten().tex();
|
|
850
|
+
return negatives + "\\frac{" + numerator + "}{" + denominator + "}";
|
|
851
|
+
}
|
|
852
|
+
},
|
|
853
|
+
|
|
854
|
+
strip: function() {
|
|
855
|
+
var terms = _.map(this.terms, function(term) {
|
|
856
|
+
return term instanceof Num ? term.abs() : term.strip();
|
|
857
|
+
});
|
|
858
|
+
return new Mul(terms).flatten();
|
|
859
|
+
},
|
|
860
|
+
|
|
861
|
+
// expand numerator and denominator separately
|
|
862
|
+
expand: function() {
|
|
863
|
+
|
|
864
|
+
var isAdd = function(term) {
|
|
865
|
+
return term instanceof Add;
|
|
866
|
+
};
|
|
867
|
+
|
|
868
|
+
var isInverse = function(term) {
|
|
869
|
+
return term instanceof Pow && term.exp.isNegative();
|
|
870
|
+
};
|
|
871
|
+
|
|
872
|
+
var isInverseAdd = function(term) {
|
|
873
|
+
return isInverse(term) && isAdd(term.base);
|
|
874
|
+
};
|
|
875
|
+
|
|
876
|
+
var mul = this.recurse("expand").flatten();
|
|
877
|
+
|
|
878
|
+
var hasAdd = _.any(mul.terms, isAdd);
|
|
879
|
+
var hasInverseAdd = _.any(mul.terms, isInverseAdd);
|
|
880
|
+
|
|
881
|
+
if (!(hasAdd || hasInverseAdd)) {
|
|
882
|
+
return mul;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
var terms = _.groupBy(mul.terms, isInverse);
|
|
886
|
+
var normals = terms[false] || [];
|
|
887
|
+
var inverses = terms[true] || [];
|
|
888
|
+
|
|
889
|
+
if (hasAdd) {
|
|
890
|
+
var grouped = _.groupBy(normals, isAdd);
|
|
891
|
+
var adds = grouped[true] || [];
|
|
892
|
+
var others = grouped[false] || [];
|
|
893
|
+
|
|
894
|
+
// loop over each additive sequence
|
|
895
|
+
var expanded = _.reduce(adds, function(expanded, add) {
|
|
896
|
+
// loop over each expanded array of terms
|
|
897
|
+
return _.reduce(expanded, function(temp, array) {
|
|
898
|
+
// loop over each additive sequence's terms
|
|
899
|
+
return temp.concat(_.map(add.terms, function(term) {
|
|
900
|
+
return array.concat(term);
|
|
901
|
+
}));
|
|
902
|
+
}, []);
|
|
903
|
+
}, [[]]);
|
|
904
|
+
|
|
905
|
+
// join each fully expanded array of factors with remaining multiplicative factors
|
|
906
|
+
var muls = _.map(expanded, function(array) {
|
|
907
|
+
return new Mul(others.concat(array)).flatten();
|
|
908
|
+
});
|
|
909
|
+
|
|
910
|
+
normals = [new Add(muls)];
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
if (hasInverseAdd) {
|
|
914
|
+
var denominator = new Mul(_.invoke(inverses, "getDenominator")).flatten();
|
|
915
|
+
inverses = [new Pow(denominator.expand(), Num.Div)];
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
return new Mul(normals.concat(inverses)).flatten();
|
|
919
|
+
},
|
|
920
|
+
|
|
921
|
+
factor: function(options) {
|
|
922
|
+
var factored = this.recurse("factor", options).flatten();
|
|
923
|
+
if (! (factored instanceof Mul)) {
|
|
924
|
+
return factored;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
// Combine any factored out Rationals into one, but don't collect
|
|
928
|
+
var grouped = _.groupBy(factored.terms, function(term) {
|
|
929
|
+
return term instanceof Rational;
|
|
930
|
+
});
|
|
931
|
+
|
|
932
|
+
// Could also accomplish this by passing a new option
|
|
933
|
+
// e.g. return memo.mul(term, {autocollect: false});
|
|
934
|
+
// TODO(alex): Decide whether this is a good use of options or not
|
|
935
|
+
var rational = _.reduce(grouped[true], function(memo, term) {
|
|
936
|
+
return {n: memo.n * term.n, d: memo.d * term.d};
|
|
937
|
+
}, {n: 1, d: 1});
|
|
938
|
+
|
|
939
|
+
if (rational.d === 1) {
|
|
940
|
+
rational = new Int(rational.n);
|
|
941
|
+
} else {
|
|
942
|
+
rational = new Rational(rational.n, rational.d);
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
return new Mul((grouped[false] || []).concat(rational)).flatten();
|
|
946
|
+
},
|
|
947
|
+
|
|
948
|
+
collect: function(options) {
|
|
949
|
+
var partitioned = this.recurse("collect", options).partition();
|
|
950
|
+
var number = partitioned[0].reduce(options);
|
|
951
|
+
|
|
952
|
+
// e.g. 0*x -> 0
|
|
953
|
+
if (number.eval() === 0) {
|
|
954
|
+
return Num.Zero;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
var others = partitioned[1].flatten();
|
|
958
|
+
|
|
959
|
+
// e.g. 2*2 -> 4
|
|
960
|
+
// e.g. 2*2*x -> 4*x
|
|
961
|
+
if (!(others instanceof Mul)) {
|
|
962
|
+
return new Mul(number, others).flatten();
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
others = others.terms;
|
|
966
|
+
|
|
967
|
+
// [Expr base, Expr exp]
|
|
968
|
+
var pairs = [];
|
|
969
|
+
|
|
970
|
+
_.each(others, function(term) {
|
|
971
|
+
if (term instanceof Pow) {
|
|
972
|
+
pairs.push([term.base, term.exp]);
|
|
973
|
+
} else {
|
|
974
|
+
pairs.push([term, Num.One]);
|
|
975
|
+
}
|
|
976
|
+
});
|
|
977
|
+
|
|
978
|
+
// {(Expr base).print(): [[Expr base, Expr exp]]}
|
|
979
|
+
var grouped = _.groupBy(pairs, function(pair) {
|
|
980
|
+
return pair[0].normalize().print();
|
|
981
|
+
});
|
|
982
|
+
|
|
983
|
+
// [[Expr base, Expr exp]]
|
|
984
|
+
var summed = _.compact(_.map(grouped, function(pairs) {
|
|
985
|
+
var base = pairs[0][0];
|
|
986
|
+
var sum = new Add(_.zip.apply(_, pairs)[1]);
|
|
987
|
+
var exp = sum.collect(options);
|
|
988
|
+
|
|
989
|
+
if (exp instanceof Num && exp.eval() === 0) {
|
|
990
|
+
return null;
|
|
991
|
+
} else {
|
|
992
|
+
return [base, exp];
|
|
993
|
+
}
|
|
994
|
+
}));
|
|
995
|
+
|
|
996
|
+
// XXX `pairs` is shadowed four or five times in this function
|
|
997
|
+
var pairs = _.groupBy(summed, function(pair) {
|
|
998
|
+
if (pair[0] instanceof Trig && pair[0].isBasic()) {
|
|
999
|
+
return "trig";
|
|
1000
|
+
} else if (pair[0] instanceof Log) {
|
|
1001
|
+
return "log";
|
|
1002
|
+
} else {
|
|
1003
|
+
return "expr";
|
|
1004
|
+
}
|
|
1005
|
+
});
|
|
1006
|
+
var trigs = pairs.trig || [];
|
|
1007
|
+
var logs = pairs.log || [];
|
|
1008
|
+
var exprs = pairs.expr || [];
|
|
1009
|
+
|
|
1010
|
+
if (trigs.length > 1) {
|
|
1011
|
+
// combine sines and cosines into other trig functions
|
|
1012
|
+
|
|
1013
|
+
// {Trig.arg.print(): [[Trig base, Expr exp]]}
|
|
1014
|
+
var byArg = _.groupBy(trigs, function(pair) {
|
|
1015
|
+
return pair[0].arg.normalize().print();
|
|
1016
|
+
});
|
|
1017
|
+
|
|
1018
|
+
trigs = [];
|
|
1019
|
+
_.each(byArg, function(pairs) {
|
|
1020
|
+
var arg = pairs[0][0].arg;
|
|
1021
|
+
|
|
1022
|
+
// {Trig.type: Expr exp}
|
|
1023
|
+
var funcs = {sin: Num.Zero, cos: Num.Zero};
|
|
1024
|
+
_.each(pairs, function(pair) {
|
|
1025
|
+
funcs[pair[0].type] = pair[1];
|
|
1026
|
+
});
|
|
1027
|
+
|
|
1028
|
+
if (Mul.handleNegative(funcs.sin).collect(options).equals(funcs.cos)) {
|
|
1029
|
+
// e.g. sin^x(y)/cos^x(y) -> tan^x(y)
|
|
1030
|
+
if (funcs.cos.isNegative()) {
|
|
1031
|
+
funcs = {tan: funcs.sin};
|
|
1032
|
+
} else {
|
|
1033
|
+
funcs = {cot: funcs.cos};
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
// TODO(alex): combine even if exponents not a perfect match
|
|
1038
|
+
// TODO(alex): transform 1/sin and 1/cos into csc and sec
|
|
1039
|
+
|
|
1040
|
+
_.each(funcs, function(exp, type) {
|
|
1041
|
+
trigs.push([new Trig(type, arg), exp]);
|
|
1042
|
+
});
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
if (logs.length > 1) {
|
|
1047
|
+
// combine logs with the same base
|
|
1048
|
+
|
|
1049
|
+
// {Log.base.print(): [[Log base, Expr exp]]}
|
|
1050
|
+
var byBase = _.groupBy(logs, function(pair) {
|
|
1051
|
+
return pair[0].base.normalize().print();
|
|
1052
|
+
});
|
|
1053
|
+
|
|
1054
|
+
logs = [];
|
|
1055
|
+
|
|
1056
|
+
_.each(byBase, function(pairs) {
|
|
1057
|
+
// only combine two logs of the same base, otherwise commutative
|
|
1058
|
+
// differences result in different equally valid output
|
|
1059
|
+
// e.g. ln(x)/ln(z)*ln(y) -> log_z(x)*ln(y)
|
|
1060
|
+
// e.g. ln(x)*ln(y)/ln(z) -> ln(x)*log_z(y)
|
|
1061
|
+
if (pairs.length === 2 &&
|
|
1062
|
+
Mul.handleNegative(pairs[0][1]).collect(options).equals(pairs[1][1])) {
|
|
1063
|
+
// e.g. ln(x)^y/ln(b)^y -> log_b(x)^y
|
|
1064
|
+
if (pairs[0][1].isNegative()) {
|
|
1065
|
+
logs.push([new Log(pairs[0][0].power, pairs[1][0].power), pairs[1][1]]);
|
|
1066
|
+
} else {
|
|
1067
|
+
logs.push([new Log(pairs[1][0].power, pairs[0][0].power), pairs[0][1]]);
|
|
1068
|
+
}
|
|
1069
|
+
} else {
|
|
1070
|
+
logs = logs.concat(pairs);
|
|
1071
|
+
}
|
|
1072
|
+
});
|
|
1073
|
+
|
|
1074
|
+
// TODO(alex): combine if all inverses are the same e.g. ln(y)*ln(z)/ln(x)/ln(x)
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
pairs = trigs.concat(logs).concat(exprs);
|
|
1078
|
+
|
|
1079
|
+
var collected = _.map(pairs, function(pair) {
|
|
1080
|
+
return new Pow(pair[0], pair[1]).collect(options);
|
|
1081
|
+
});
|
|
1082
|
+
|
|
1083
|
+
return new Mul([number].concat(collected)).flatten();
|
|
1084
|
+
},
|
|
1085
|
+
|
|
1086
|
+
isSubtract: function() {
|
|
1087
|
+
return _.any(this.terms, function(term) {
|
|
1088
|
+
return term instanceof Num && term.hints.subtract;
|
|
1089
|
+
});
|
|
1090
|
+
},
|
|
1091
|
+
|
|
1092
|
+
// factor a single -1 in to the Mul
|
|
1093
|
+
// combine with a Num if all Nums are positive, else add as a term
|
|
1094
|
+
factorIn: function(hint) {
|
|
1095
|
+
var partitioned = this.partition();
|
|
1096
|
+
var numbers = partitioned[0].terms;
|
|
1097
|
+
var fold = numbers.length && _.all(numbers, function(num) {
|
|
1098
|
+
return num.n > 0;
|
|
1099
|
+
});
|
|
1100
|
+
|
|
1101
|
+
if (fold) {
|
|
1102
|
+
// e.g. - x*2*3 -> x*-2*3
|
|
1103
|
+
var num = numbers[0].negate();
|
|
1104
|
+
num.hints = numbers[0].hints;
|
|
1105
|
+
return this.replace(numbers[0], num.addHint(hint));
|
|
1106
|
+
} else {
|
|
1107
|
+
// e.g. - x*y -> -1*x*y
|
|
1108
|
+
// e.g. - x*-2 -> -1*x*-2
|
|
1109
|
+
return new Mul([Num.negativeOne(hint)].concat(this.terms));
|
|
1110
|
+
}
|
|
1111
|
+
},
|
|
1112
|
+
|
|
1113
|
+
// factor out a single hinted -1 (assume it is the division hint)
|
|
1114
|
+
// TODO(alex): make more general or rename to be more specific
|
|
1115
|
+
factorOut: function() {
|
|
1116
|
+
var factored = false;
|
|
1117
|
+
var terms = _.compact(_.map(this.terms, function(term, i, list) {
|
|
1118
|
+
if (!factored && term instanceof Num && term.hints.divide) {
|
|
1119
|
+
factored = true;
|
|
1120
|
+
return term.n !== -1 ? term.negate() : null;
|
|
1121
|
+
} else {
|
|
1122
|
+
return term;
|
|
1123
|
+
}
|
|
1124
|
+
}));
|
|
1125
|
+
|
|
1126
|
+
if (terms.length === 1) {
|
|
1127
|
+
return terms[0];
|
|
1128
|
+
} else {
|
|
1129
|
+
return new Mul(terms);
|
|
1130
|
+
}
|
|
1131
|
+
},
|
|
1132
|
+
|
|
1133
|
+
reduce: function(options) {
|
|
1134
|
+
return _.reduce(this.terms, function(memo, term) {
|
|
1135
|
+
return memo.mul(term, options);
|
|
1136
|
+
}, this.identity);
|
|
1137
|
+
},
|
|
1138
|
+
|
|
1139
|
+
findGCD: function(factor) {
|
|
1140
|
+
return new Mul(_.invoke(this.terms, "findGCD", factor)).flatten();
|
|
1141
|
+
},
|
|
1142
|
+
|
|
1143
|
+
asMul: function() {
|
|
1144
|
+
return this;
|
|
1145
|
+
},
|
|
1146
|
+
|
|
1147
|
+
asPositiveFactor: function() {
|
|
1148
|
+
if (this.isPositive()) {
|
|
1149
|
+
return this;
|
|
1150
|
+
} else {
|
|
1151
|
+
var terms = _.invoke(this.collect().terms, "asPositiveFactor");
|
|
1152
|
+
return new Mul(terms).flatten();
|
|
1153
|
+
}
|
|
1154
|
+
},
|
|
1155
|
+
|
|
1156
|
+
isNegative: function() {
|
|
1157
|
+
return _.any(_.invoke(this.collect().terms, "isNegative"));
|
|
1158
|
+
},
|
|
1159
|
+
|
|
1160
|
+
fold: function() {
|
|
1161
|
+
return Mul.fold(this);
|
|
1162
|
+
},
|
|
1163
|
+
|
|
1164
|
+
negate: function() {
|
|
1165
|
+
var isNum = function(expr) { return expr instanceof Num; };
|
|
1166
|
+
if (_.any(this.terms, isNum)) {
|
|
1167
|
+
var num = _.find(this.terms, isNum);
|
|
1168
|
+
return this.replace(num, num.negate());
|
|
1169
|
+
} else {
|
|
1170
|
+
return new Mul([Num.Neg].concat(this.terms));
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
});
|
|
1174
|
+
|
|
1175
|
+
// static methods for the sequence types
|
|
1176
|
+
_.each([Add, Mul], function(type) {
|
|
1177
|
+
_.extend(type, {
|
|
1178
|
+
// create a new sequence unless left is already one (returns a copy)
|
|
1179
|
+
createOrAppend: function(left, right) {
|
|
1180
|
+
if (left instanceof type) {
|
|
1181
|
+
return new type(left.terms.concat(right));
|
|
1182
|
+
} else {
|
|
1183
|
+
return new type(left, right);
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
});
|
|
1187
|
+
});
|
|
1188
|
+
|
|
1189
|
+
_.extend(Mul, {
|
|
1190
|
+
// negative signs should be folded into numbers whenever possible
|
|
1191
|
+
// never fold into a Num that's already negative or a Mul that has a negative Num
|
|
1192
|
+
// an optional hint is kept track of to properly render user input
|
|
1193
|
+
// an empty hint means negation
|
|
1194
|
+
handleNegative: function(expr, hint) {
|
|
1195
|
+
if (expr instanceof Num && expr.n > 0) {
|
|
1196
|
+
// e.g. - 2 -> -2
|
|
1197
|
+
var negated = expr.negate();
|
|
1198
|
+
// TODO(alex): rework hint system so that this isn't necessary
|
|
1199
|
+
negated.hints = expr.hints;
|
|
1200
|
+
return negated.addHint(hint);
|
|
1201
|
+
} else if (expr instanceof Mul) {
|
|
1202
|
+
// e.g. - x*2*3 -> x*-2*3
|
|
1203
|
+
// e.g. - x*y -> -1*x*y
|
|
1204
|
+
// e.g. - x*-2 -> -1*x*-2
|
|
1205
|
+
return expr.factorIn(hint);
|
|
1206
|
+
} else {
|
|
1207
|
+
// e.g. - x -> -1*x
|
|
1208
|
+
return new Mul(Num.negativeOne(hint), expr);
|
|
1209
|
+
}
|
|
1210
|
+
},
|
|
1211
|
+
|
|
1212
|
+
// division can create either a Rational or a Mul
|
|
1213
|
+
handleDivide: function(left, right) {
|
|
1214
|
+
|
|
1215
|
+
// dividing by a Mul is the same as repeated division by its terms
|
|
1216
|
+
if (right instanceof Mul) {
|
|
1217
|
+
var first = Mul.handleDivide(left, right.terms[0]);
|
|
1218
|
+
var rest = new Mul(_.rest(right.terms)).flatten();
|
|
1219
|
+
return Mul.handleDivide(first, rest);
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
var isInt = function(expr) { return expr instanceof Int; };
|
|
1223
|
+
var isRational = function(expr) { return expr instanceof Rational; };
|
|
1224
|
+
|
|
1225
|
+
// for simplification purposes, fold Ints into Rationals if possible
|
|
1226
|
+
// e.g. 3x / 4 -> 3/4 * x (will still render as 3x/4)
|
|
1227
|
+
if (isInt(right) && left instanceof Mul && _.any(left.terms, isInt)) {
|
|
1228
|
+
|
|
1229
|
+
// search from the right
|
|
1230
|
+
var reversed = left.terms.slice().reverse();
|
|
1231
|
+
var num = _.find(reversed, isRational);
|
|
1232
|
+
|
|
1233
|
+
if (!isInt(num)) {
|
|
1234
|
+
return new Mul(left.terms.concat([new Rational(1, right.n).addHint("fraction")]));
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
var rational = new Rational(num.n, right.n);
|
|
1238
|
+
rational.hints = num.hints;
|
|
1239
|
+
|
|
1240
|
+
// in the case of something like 1/3 * 6/8, we want the
|
|
1241
|
+
// 6/8 to be considered a fraction, not just a division
|
|
1242
|
+
if (num === reversed[0]) {
|
|
1243
|
+
rational = rational.addHint("fraction");
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
var result;
|
|
1247
|
+
if (num.n < 0 && right.n < 0) {
|
|
1248
|
+
rational.d = -rational.d;
|
|
1249
|
+
return left.replace(num, [Num.Neg, rational]);
|
|
1250
|
+
} else {
|
|
1251
|
+
return left.replace(num, rational);
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
var divide = function(a, b) {
|
|
1256
|
+
if (b instanceof Int) {
|
|
1257
|
+
if (a instanceof Int) {
|
|
1258
|
+
if (a.n < 0 && b.n < 0) {
|
|
1259
|
+
// e.g. -2 / -3 -> -1*-2/3
|
|
1260
|
+
return [Num.Neg, new Rational(a.n, -b.n).addHint("fraction")];
|
|
1261
|
+
} else {
|
|
1262
|
+
// e.g. 2 / 3 -> 2/3
|
|
1263
|
+
// e.g. -2 / 3 -> -2/3
|
|
1264
|
+
// e.g. 2 / -3 -> -2/3
|
|
1265
|
+
return [new Rational(a.n, b.n).addHint("fraction")];
|
|
1266
|
+
}
|
|
1267
|
+
} else {
|
|
1268
|
+
// e.g. x / 3 -> x*1/3
|
|
1269
|
+
// e.g. x / -3 -> x*-1/3
|
|
1270
|
+
var inverse = new Rational(1, b.eval());
|
|
1271
|
+
if (b.eval() < 0) {
|
|
1272
|
+
return [a, inverse.addHint("negate")];
|
|
1273
|
+
} else {
|
|
1274
|
+
return [a, inverse];
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
} else {
|
|
1278
|
+
var pow;
|
|
1279
|
+
|
|
1280
|
+
if (b instanceof Trig && b.exp) {
|
|
1281
|
+
// e.g. sin^2(x) -> sin(x)^2
|
|
1282
|
+
var exp = b.exp;
|
|
1283
|
+
b.exp = undefined;
|
|
1284
|
+
b = new Pow(b, exp);
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
if (b instanceof Pow) {
|
|
1288
|
+
// e.g. (x^2) ^ -1 -> x^-2
|
|
1289
|
+
// e.g. (x^y) ^ -1 -> x^(-1*y)
|
|
1290
|
+
// e.g. (x^(yz)) ^ -1 -> x^(-1*y*z)
|
|
1291
|
+
pow = new Pow(b.base, Mul.handleNegative(b.exp, "divide"));
|
|
1292
|
+
} else {
|
|
1293
|
+
// e.g. x ^ -1 -> x^-1
|
|
1294
|
+
pow = new Pow(b, Num.Div);
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
if (a instanceof Int && a.n === 1) {
|
|
1298
|
+
// e.g. 1 / x -> x^-1
|
|
1299
|
+
return [pow];
|
|
1300
|
+
} else {
|
|
1301
|
+
// e.g. 2 / x -> 2*x^-1
|
|
1302
|
+
return [a, pow];
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
};
|
|
1306
|
+
|
|
1307
|
+
if (left instanceof Mul) {
|
|
1308
|
+
var divided = divide(_.last(left.terms), right);
|
|
1309
|
+
return new Mul(_.initial(left.terms).concat(divided));
|
|
1310
|
+
} else {
|
|
1311
|
+
var divided = divide(left, right);
|
|
1312
|
+
return new Mul(divided).flatten();
|
|
1313
|
+
}
|
|
1314
|
+
},
|
|
1315
|
+
|
|
1316
|
+
// fold negative signs into numbers if possible
|
|
1317
|
+
// negative signs are not the same as multiplying by negative one!
|
|
1318
|
+
// e.g. -x -> -1*x simplified
|
|
1319
|
+
// e.g. -2*x -> -2*x simplified
|
|
1320
|
+
// e.g. -x*2 -> -1*x*2 not simplified -> x*-2 simplified
|
|
1321
|
+
// e.g. -1*x*2 -> -1*x*2 not simplified
|
|
1322
|
+
|
|
1323
|
+
// also fold multiplicative terms into open Trig and Log nodes
|
|
1324
|
+
// e.g. (sin x)*x -> sin(x)*x
|
|
1325
|
+
// e.g. sin(x)*x -> sin(x)*x
|
|
1326
|
+
// e.g. sin(x)*(x) -> sin(x)*x
|
|
1327
|
+
// e.g. sin(x)*sin(y) -> sin(x)*sin(y)
|
|
1328
|
+
fold: function(expr) {
|
|
1329
|
+
if (expr instanceof Mul) {
|
|
1330
|
+
// assuming that this will be second to last
|
|
1331
|
+
var trigLog = _.find(_.initial(expr.terms), function(term) {
|
|
1332
|
+
return (term instanceof Trig || term instanceof Log) && term.hints.open;
|
|
1333
|
+
});
|
|
1334
|
+
var index = _.indexOf(expr.terms, trigLog);
|
|
1335
|
+
|
|
1336
|
+
if (trigLog) {
|
|
1337
|
+
var last = _.last(expr.terms);
|
|
1338
|
+
if (trigLog.hints.parens || last.hints.parens ||
|
|
1339
|
+
last.has(Trig) || last.has(Log)) {
|
|
1340
|
+
trigLog.hints.open = false;
|
|
1341
|
+
} else {
|
|
1342
|
+
var newTrigLog;
|
|
1343
|
+
if (trigLog instanceof Trig) {
|
|
1344
|
+
newTrigLog = Trig.create([trigLog.type, trigLog.exp], Mul.createOrAppend(trigLog.arg, last).fold());
|
|
1345
|
+
} else {
|
|
1346
|
+
newTrigLog = Log.create(trigLog.base, Mul.createOrAppend(trigLog.power, last).fold());
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
if (index === 0) {
|
|
1350
|
+
return newTrigLog;
|
|
1351
|
+
} else {
|
|
1352
|
+
return new Mul(expr.terms.slice(0, index).concat(newTrigLog)).fold();
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
var partitioned = expr.partition();
|
|
1358
|
+
var numbers = partitioned[0].terms;
|
|
1359
|
+
|
|
1360
|
+
var pos = function(num) { return num.n > 0; };
|
|
1361
|
+
var neg = function(num) { return num.n === -1 && num.hints.negate; };
|
|
1362
|
+
var posOrNeg = function(num) { return pos(num) || neg(num); };
|
|
1363
|
+
|
|
1364
|
+
if (numbers.length > 1 &&
|
|
1365
|
+
_.some(numbers, neg) &&
|
|
1366
|
+
_.some(numbers, pos) &&
|
|
1367
|
+
_.every(numbers, posOrNeg)) {
|
|
1368
|
+
|
|
1369
|
+
var firstNeg = _.indexOf(expr.terms, _.find(expr.terms, neg));
|
|
1370
|
+
var firstNum = _.indexOf(expr.terms, _.find(expr.terms, pos));
|
|
1371
|
+
|
|
1372
|
+
// e.g. -x*2 -> x*-2
|
|
1373
|
+
if (firstNeg < firstNum) {
|
|
1374
|
+
return expr.replace(firstNum,
|
|
1375
|
+
expr.terms[firstNum].negate())
|
|
1376
|
+
.remove(firstNeg);
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
// in all other cases, make no change
|
|
1382
|
+
return expr;
|
|
1383
|
+
}
|
|
1384
|
+
});
|
|
1385
|
+
|
|
1386
|
+
|
|
1387
|
+
/* exponentiation */
|
|
1388
|
+
export function Pow(base, exp) { this.base = base; this.exp = exp; }
|
|
1389
|
+
Pow.prototype = new Expr();
|
|
1390
|
+
|
|
1391
|
+
_.extend(Pow.prototype, {
|
|
1392
|
+
func: Pow,
|
|
1393
|
+
args: function() { return [this.base, this.exp]; },
|
|
1394
|
+
|
|
1395
|
+
eval: function(vars, options) {
|
|
1396
|
+
var evaledBase = this.base.eval(vars, options);
|
|
1397
|
+
var evaledExp = this.exp.eval(vars, options);
|
|
1398
|
+
|
|
1399
|
+
// Math.pow unequivocally returns NaN when provided with both a
|
|
1400
|
+
// negative base and a fractional exponent. However, in some cases, we
|
|
1401
|
+
// know that our exponent is actually valid for use with negative
|
|
1402
|
+
// bases (e.g., (-5)^(1/3)).
|
|
1403
|
+
//
|
|
1404
|
+
// Here, we explicitly check for such cases. We really only handle a
|
|
1405
|
+
// limited subset (by requiring that the exponent is rational with an
|
|
1406
|
+
// odd denominator), but it's still useful.
|
|
1407
|
+
// See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/pow
|
|
1408
|
+
if (evaledBase < 0) {
|
|
1409
|
+
var simplifiedExp = this.exp.simplify();
|
|
1410
|
+
|
|
1411
|
+
// If Float, convert to a Rational to enable the logic below
|
|
1412
|
+
if (simplifiedExp instanceof Float) {
|
|
1413
|
+
var num = simplifiedExp.n;
|
|
1414
|
+
var decimals = (num - num.toFixed()).toString().length - 2;
|
|
1415
|
+
var denominator = Math.pow(10, decimals);
|
|
1416
|
+
var rationalExp = new Rational(num * denominator, denominator);
|
|
1417
|
+
simplifiedExp = rationalExp.simplify();
|
|
1418
|
+
}
|
|
1419
|
+
if (simplifiedExp instanceof Rational) {
|
|
1420
|
+
var oddDenominator = Math.abs(simplifiedExp.d) % 2 === 1;
|
|
1421
|
+
if (oddDenominator) {
|
|
1422
|
+
var oddNumerator = Math.abs(simplifiedExp.n) % 2 === 1;
|
|
1423
|
+
var sign = (oddNumerator) ? -1 : 1;
|
|
1424
|
+
return sign * Math.pow(-1 * evaledBase, evaledExp);
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
return Math.pow(evaledBase, evaledExp);
|
|
1429
|
+
},
|
|
1430
|
+
|
|
1431
|
+
getUnits: function() {
|
|
1432
|
+
return this.base.getUnits().map(function(unit) {
|
|
1433
|
+
return {
|
|
1434
|
+
unit: unit.unit,
|
|
1435
|
+
pow: unit.pow * this.exp.n
|
|
1436
|
+
};
|
|
1437
|
+
}.bind(this));
|
|
1438
|
+
},
|
|
1439
|
+
|
|
1440
|
+
codegen: function() {
|
|
1441
|
+
return "Math.pow(" + this.base.codegen() +
|
|
1442
|
+
", " + this.exp.codegen() + ")";
|
|
1443
|
+
},
|
|
1444
|
+
|
|
1445
|
+
print: function() {
|
|
1446
|
+
var base = this.base.print();
|
|
1447
|
+
if (this.base instanceof Seq || this.base instanceof Pow) {
|
|
1448
|
+
base = "(" + base + ")";
|
|
1449
|
+
}
|
|
1450
|
+
return base + "^(" + this.exp.print() + ")";
|
|
1451
|
+
},
|
|
1452
|
+
|
|
1453
|
+
tex: function() {
|
|
1454
|
+
if (this.isDivide()) {
|
|
1455
|
+
|
|
1456
|
+
// e.g. x ^ -1 w/hint -> 1/x
|
|
1457
|
+
return "\\frac{1}{" + this.asDivide().tex() + "}";
|
|
1458
|
+
|
|
1459
|
+
} else if (this.isRoot()) {
|
|
1460
|
+
|
|
1461
|
+
if (this.exp.n !== 1) {
|
|
1462
|
+
error("Node marked with hint 'root' does not have exponent " +
|
|
1463
|
+
"of form 1/x.");
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
if (this.exp.d === 2) {
|
|
1467
|
+
// e.g. x ^ 1/2 w/hint -> sqrt{x}
|
|
1468
|
+
return "\\sqrt{" + this.base.tex() + "}";
|
|
1469
|
+
} else {
|
|
1470
|
+
// e.g. x ^ 1/y w/hint -> sqrt[y]{x}
|
|
1471
|
+
return "\\sqrt[" + this.exp.d + "]{" + this.base.tex() + "}";
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
} else if (this.base instanceof Trig && !this.base.isInverse() &&
|
|
1475
|
+
this.exp instanceof Num && this.exp.isSimple() &&
|
|
1476
|
+
this.exp.eval() >= 0) {
|
|
1477
|
+
|
|
1478
|
+
// e.g sin(x) ^ 2 -> sin^2(x)
|
|
1479
|
+
var split = this.base.tex({split: true});
|
|
1480
|
+
return split[0] + "^{" + this.exp.tex() + "}" + split[1];
|
|
1481
|
+
|
|
1482
|
+
} else {
|
|
1483
|
+
|
|
1484
|
+
// e.g. x ^ y -> x^y
|
|
1485
|
+
var base = this.base.tex();
|
|
1486
|
+
if (this.base instanceof Seq || this.base instanceof Pow ||
|
|
1487
|
+
(this.base instanceof Num && !this.base.isSimple())) {
|
|
1488
|
+
// e.g. a+b ^ c -> (a+b)^c
|
|
1489
|
+
base = "(" + base + ")";
|
|
1490
|
+
} else if (this.base instanceof Trig || this.base instanceof Log) {
|
|
1491
|
+
// e.g. ln(x) ^ 2 -> [ln(x)]^2
|
|
1492
|
+
base = "[" + base + "]";
|
|
1493
|
+
}
|
|
1494
|
+
return base + "^{" + this.exp.tex() + "}";
|
|
1495
|
+
}
|
|
1496
|
+
},
|
|
1497
|
+
|
|
1498
|
+
needsExplicitMul: function() {
|
|
1499
|
+
return this.isRoot() ? false : this.base.needsExplicitMul();
|
|
1500
|
+
},
|
|
1501
|
+
|
|
1502
|
+
expand: function() {
|
|
1503
|
+
var pow = this.recurse("expand");
|
|
1504
|
+
|
|
1505
|
+
if (pow.base instanceof Mul) {
|
|
1506
|
+
// e.g. (ab)^c -> a^c*b^c
|
|
1507
|
+
|
|
1508
|
+
var terms = _.map(pow.base.terms, function(term) {
|
|
1509
|
+
return new Pow(term, pow.exp);
|
|
1510
|
+
});
|
|
1511
|
+
|
|
1512
|
+
return new Mul(terms).expand();
|
|
1513
|
+
|
|
1514
|
+
} else if (pow.base instanceof Add && pow.exp instanceof Int && pow.exp.abs().eval() > 1) {
|
|
1515
|
+
// e.g. (a+b)^2 -> a*a+a*b+a*b+b*b
|
|
1516
|
+
// e.g. (a+b)^-2 -> (a*a+a*b+a*b+b*b)^-1
|
|
1517
|
+
|
|
1518
|
+
var positive = pow.exp.eval() > 0;
|
|
1519
|
+
var n = pow.exp.abs().eval();
|
|
1520
|
+
|
|
1521
|
+
var signed = function(mul) {
|
|
1522
|
+
return positive ? mul : new Pow(mul, Num.Div);
|
|
1523
|
+
};
|
|
1524
|
+
|
|
1525
|
+
// compute and cache powers of 2 up to n
|
|
1526
|
+
var cache = { 1: pow.base };
|
|
1527
|
+
for (var i = 2; i <= n; i *= 2) {
|
|
1528
|
+
var mul = new Mul(cache[i / 2], cache[i / 2]);
|
|
1529
|
+
cache[i] = mul.expand().collect();
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
// if n is a power of 2, you're done!
|
|
1533
|
+
if (_.has(cache, n)) {
|
|
1534
|
+
return signed(cache[n]);
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
// otherwise decompose n into powers of 2 ...
|
|
1538
|
+
var indices = _.map(n.toString(2).split(""), function(str, i, list) {
|
|
1539
|
+
return Number(str) * Math.pow(2, list.length - i - 1);
|
|
1540
|
+
});
|
|
1541
|
+
indices = _.without(indices, 0);
|
|
1542
|
+
|
|
1543
|
+
// ... then combine
|
|
1544
|
+
var mul = new Mul(_.pick(cache, indices)).expand().collect();
|
|
1545
|
+
return signed(mul);
|
|
1546
|
+
|
|
1547
|
+
} else if (pow.exp instanceof Add) { // DEFINITELY want behind super-simplify() flag
|
|
1548
|
+
// e.g. x^(a+b) -> x^a*x^b
|
|
1549
|
+
|
|
1550
|
+
var terms = _.map(pow.exp.terms, function(term) {
|
|
1551
|
+
return new Pow(pow.base, term).expand();
|
|
1552
|
+
});
|
|
1553
|
+
|
|
1554
|
+
return new Mul(terms).expand();
|
|
1555
|
+
} else {
|
|
1556
|
+
return pow;
|
|
1557
|
+
}
|
|
1558
|
+
},
|
|
1559
|
+
|
|
1560
|
+
factor: function() {
|
|
1561
|
+
var pow = this.recurse("factor");
|
|
1562
|
+
if (pow.base instanceof Mul) {
|
|
1563
|
+
var terms = _.map(pow.base.terms, function(term) {
|
|
1564
|
+
if (term instanceof Int && pow.exp.equals(Num.Div)) {
|
|
1565
|
+
// Anything that can be a Rational should be a Rational
|
|
1566
|
+
// e.g. 2^(-1) -> 1/2
|
|
1567
|
+
return new Rational(1, term.n);
|
|
1568
|
+
} else {
|
|
1569
|
+
return new Pow(term, pow.exp);
|
|
1570
|
+
}
|
|
1571
|
+
});
|
|
1572
|
+
return new Mul(terms);
|
|
1573
|
+
} else {
|
|
1574
|
+
return pow;
|
|
1575
|
+
}
|
|
1576
|
+
},
|
|
1577
|
+
|
|
1578
|
+
collect: function(options) {
|
|
1579
|
+
|
|
1580
|
+
if (this.base instanceof Pow) {
|
|
1581
|
+
// collect this first to avoid having to deal with float precision
|
|
1582
|
+
// e.g. sqrt(2)^2 -> 2, not 2.0000000000000004
|
|
1583
|
+
// e.g. (x^y)^z -> x^(yz)
|
|
1584
|
+
var base = this.base.base;
|
|
1585
|
+
var exp = Mul.createOrAppend(this.base.exp, this.exp);
|
|
1586
|
+
return new Pow(base, exp).collect(options);
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
var pow = this.recurse("collect", options);
|
|
1590
|
+
|
|
1591
|
+
var isSimilarLog = function(term) {
|
|
1592
|
+
return term instanceof Log && term.base.equals(pow.base);
|
|
1593
|
+
};
|
|
1594
|
+
|
|
1595
|
+
if (pow.exp instanceof Num &&
|
|
1596
|
+
pow.exp.eval() === 0) {
|
|
1597
|
+
|
|
1598
|
+
// e.g. x^0 -> 1
|
|
1599
|
+
return Num.One;
|
|
1600
|
+
|
|
1601
|
+
} else if (pow.exp instanceof Num &&
|
|
1602
|
+
pow.exp.eval() === 1) {
|
|
1603
|
+
|
|
1604
|
+
// e.g. x^1 -> x
|
|
1605
|
+
return pow.base;
|
|
1606
|
+
|
|
1607
|
+
} else if (isSimilarLog(pow.exp)) {
|
|
1608
|
+
|
|
1609
|
+
// e.g. b^(log_b(x)) -> x
|
|
1610
|
+
return pow.exp.power;
|
|
1611
|
+
|
|
1612
|
+
} else if (pow.exp instanceof Mul &&
|
|
1613
|
+
_.any(pow.exp.terms, isSimilarLog)) {
|
|
1614
|
+
|
|
1615
|
+
// e.g. b^(2*y*log_b(x)) -> x^(2*y)
|
|
1616
|
+
var log = _.find(pow.exp.terms, isSimilarLog);
|
|
1617
|
+
var base = log.power;
|
|
1618
|
+
var exp = pow.exp.remove(log).flatten();
|
|
1619
|
+
return new Pow(base, exp).collect(options);
|
|
1620
|
+
|
|
1621
|
+
} else if (pow.base instanceof Num &&
|
|
1622
|
+
pow.exp instanceof Num) {
|
|
1623
|
+
|
|
1624
|
+
// TODO(alex): Consider encapsualting this logic (and similar logic
|
|
1625
|
+
// elsewhere) into a separate Decimal class for user-entered floats
|
|
1626
|
+
if (options && options.preciseFloats) {
|
|
1627
|
+
// Avoid creating an imprecise float
|
|
1628
|
+
// e.g. 23^1.5 -> 12167^0.5, not ~110.304
|
|
1629
|
+
|
|
1630
|
+
// If you take the root as specified by the denominator and
|
|
1631
|
+
// end up with more digits after the decimal point,
|
|
1632
|
+
// the result is imprecise. This works for rationals as well
|
|
1633
|
+
// as floats, but ideally rationals should be pre-processed
|
|
1634
|
+
// e.g. (1/27)^(1/3) -> 1/3 to avoid most cases.
|
|
1635
|
+
// TODO(alex): Catch such cases and avoid converting to floats.
|
|
1636
|
+
var exp = pow.exp.asRational();
|
|
1637
|
+
var decimalsInBase = pow.base.getDecimalPlaces();
|
|
1638
|
+
var root = new Pow(pow.base, new Rational(1, exp.d));
|
|
1639
|
+
var decimalsInRoot = root.collect().getDecimalPlaces();
|
|
1640
|
+
|
|
1641
|
+
if (decimalsInRoot > decimalsInBase) {
|
|
1642
|
+
// Collecting over this denominator would result in an
|
|
1643
|
+
// imprecise float, so avoid doing so.
|
|
1644
|
+
var newBase = new Pow(pow.base, new Int(exp.n)).collect();
|
|
1645
|
+
return new Pow(newBase, new Rational(1, exp.d));
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
// e.g. 4^1.5 -> 8
|
|
1650
|
+
return pow.base.raiseToThe(pow.exp, options);
|
|
1651
|
+
} else {
|
|
1652
|
+
return pow;
|
|
1653
|
+
}
|
|
1654
|
+
},
|
|
1655
|
+
|
|
1656
|
+
// checks whether this Pow represents user-entered division
|
|
1657
|
+
isDivide: function() {
|
|
1658
|
+
var isDiv = function(arg) { return arg instanceof Num && arg.hints.divide; };
|
|
1659
|
+
return isDiv(this.exp) || (this.exp instanceof Mul && _.any(this.exp.terms, isDiv));
|
|
1660
|
+
},
|
|
1661
|
+
|
|
1662
|
+
// assuming this Pow represents user-entered division, returns the denominator
|
|
1663
|
+
asDivide: function() {
|
|
1664
|
+
if (this.exp instanceof Num) {
|
|
1665
|
+
if (this.exp.eval() === -1) {
|
|
1666
|
+
return this.base;
|
|
1667
|
+
} else {
|
|
1668
|
+
var negated = this.exp.negate();
|
|
1669
|
+
negated.hints = _.clone(this.exp.hints);
|
|
1670
|
+
negated.hints.divide = false;
|
|
1671
|
+
return new Pow(this.base, negated);
|
|
1672
|
+
}
|
|
1673
|
+
} else if (this.exp instanceof Mul) {
|
|
1674
|
+
return new Pow(this.base, this.exp.factorOut());
|
|
1675
|
+
} else {
|
|
1676
|
+
error("called asDivide() on an Expr that wasn't a Num or Mul");
|
|
1677
|
+
}
|
|
1678
|
+
},
|
|
1679
|
+
|
|
1680
|
+
isRoot: function() {
|
|
1681
|
+
return this.exp instanceof Rational && this.exp.hints.root;
|
|
1682
|
+
},
|
|
1683
|
+
|
|
1684
|
+
isSquaredTrig: function() {
|
|
1685
|
+
return this.base instanceof Trig && !this.base.isInverse() &&
|
|
1686
|
+
this.exp instanceof Num && this.exp.eval() === 2;
|
|
1687
|
+
},
|
|
1688
|
+
|
|
1689
|
+
// extract whatever denominator makes sense, ignoring hints
|
|
1690
|
+
// if negative exponent, will recursively include the base's denominator as well
|
|
1691
|
+
getDenominator: function() {
|
|
1692
|
+
if (this.exp instanceof Num && this.exp.eval() === -1) {
|
|
1693
|
+
return Mul.createOrAppend(this.base, this.base.getDenominator()).flatten();
|
|
1694
|
+
} else if (this.exp.isNegative()) {
|
|
1695
|
+
var pow = new Pow(this.base, Mul.handleNegative(this.exp).collect());
|
|
1696
|
+
return Mul.createOrAppend(pow, pow.collect().getDenominator()).flatten();
|
|
1697
|
+
} else if (this.base instanceof Num) {
|
|
1698
|
+
return new Pow(this.base.getDenominator(), this.exp).collect();
|
|
1699
|
+
} else {
|
|
1700
|
+
return Num.One;
|
|
1701
|
+
}
|
|
1702
|
+
},
|
|
1703
|
+
|
|
1704
|
+
findGCD: function(factor) {
|
|
1705
|
+
var base, exp;
|
|
1706
|
+
if (factor instanceof Pow) {
|
|
1707
|
+
base = factor.base;
|
|
1708
|
+
exp = factor.exp;
|
|
1709
|
+
} else {
|
|
1710
|
+
base = factor;
|
|
1711
|
+
exp = Num.One;
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
// GCD is only relevant if same base
|
|
1715
|
+
if (this.base.equals(base)) {
|
|
1716
|
+
if (this.exp.equals(exp)) {
|
|
1717
|
+
// exact match
|
|
1718
|
+
// e.g. GCD(x^y^z, x^y^z) -> x^y^z
|
|
1719
|
+
return this;
|
|
1720
|
+
} else if (this.exp instanceof Num && exp instanceof Num) {
|
|
1721
|
+
// two numerical exponents
|
|
1722
|
+
// e.g. GCD(x^3, x^2) -> x^2
|
|
1723
|
+
return new Pow(this.base, Num.min(this.exp, exp)).collect();
|
|
1724
|
+
} else if (this.exp instanceof Num || exp instanceof Num) {
|
|
1725
|
+
// one numerical exponent
|
|
1726
|
+
// e.g. GCD(x^2, x^y) -> 1
|
|
1727
|
+
return Num.One;
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
var expA = this.exp.asMul().partition();
|
|
1731
|
+
var expB = exp.asMul().partition();
|
|
1732
|
+
|
|
1733
|
+
if (expA[1].equals(expB[1])) {
|
|
1734
|
+
// exponents match except for coefficient
|
|
1735
|
+
// e.g. GCD(x^3y, x^y) -> x^y
|
|
1736
|
+
var coefficient = Num.min(expA[0].reduce(), expB[0].reduce());
|
|
1737
|
+
var mul = new Mul(coefficient, expA[1].flatten()).flatten();
|
|
1738
|
+
return new Pow(base, mul).collect();
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
return Num.One;
|
|
1743
|
+
},
|
|
1744
|
+
|
|
1745
|
+
isPositive: function() {
|
|
1746
|
+
if (this.base.isPositive()) {
|
|
1747
|
+
return true;
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
var exp = this.exp.simplify();
|
|
1751
|
+
return exp instanceof Int && exp.eval() % 2 === 0;
|
|
1752
|
+
},
|
|
1753
|
+
|
|
1754
|
+
asPositiveFactor: function() {
|
|
1755
|
+
if (this.isPositive()) {
|
|
1756
|
+
return this;
|
|
1757
|
+
} else {
|
|
1758
|
+
var exp = this.exp.simplify();
|
|
1759
|
+
if (exp instanceof Int) {
|
|
1760
|
+
var n = exp.eval();
|
|
1761
|
+
if (n > 2) {
|
|
1762
|
+
// e.g. x^3 -> x^2
|
|
1763
|
+
return new Pow(this.base, new Int(n-1));
|
|
1764
|
+
} else if (n < -2) {
|
|
1765
|
+
// e.g. x^-3 -> x^-2
|
|
1766
|
+
return new Pow(this.base, new Int(n+1));
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
return Num.One;
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
});
|
|
1773
|
+
|
|
1774
|
+
_.extend(Pow, {
|
|
1775
|
+
sqrt: function(arg) {
|
|
1776
|
+
return new Pow(arg, Num.Sqrt);
|
|
1777
|
+
},
|
|
1778
|
+
|
|
1779
|
+
nthroot: function(radicand, degree) {
|
|
1780
|
+
var exp = Mul.fold(Mul.handleDivide(new Int(1), degree));
|
|
1781
|
+
|
|
1782
|
+
// FIXME(johnsullivan): If oneOverDegree ends up being a pow object,
|
|
1783
|
+
// this "root" hint is lost between here and when tex() is called.
|
|
1784
|
+
return new Pow(radicand, exp.addHint("root"));
|
|
1785
|
+
},
|
|
1786
|
+
});
|
|
1787
|
+
|
|
1788
|
+
|
|
1789
|
+
/* logarithm */
|
|
1790
|
+
export function Log(base, power) { this.base = base; this.power = power; }
|
|
1791
|
+
Log.prototype = new Expr();
|
|
1792
|
+
|
|
1793
|
+
_.extend(Log.prototype, {
|
|
1794
|
+
func: Log,
|
|
1795
|
+
args: function() { return [this.base, this.power]; },
|
|
1796
|
+
|
|
1797
|
+
eval: function(vars, options) {
|
|
1798
|
+
return Math.log(this.power.eval(vars, options)) / Math.log(this.base.eval(vars, options));
|
|
1799
|
+
},
|
|
1800
|
+
|
|
1801
|
+
codegen: function() {
|
|
1802
|
+
return "(Math.log(" + this.power.codegen() +
|
|
1803
|
+
") / Math.log(" + this.base.codegen() + "))";
|
|
1804
|
+
},
|
|
1805
|
+
|
|
1806
|
+
print: function() {
|
|
1807
|
+
var power = "(" + this.power.print() + ")";
|
|
1808
|
+
if (this.isNatural()) {
|
|
1809
|
+
return "ln" + power;
|
|
1810
|
+
} else {
|
|
1811
|
+
return "log_(" + this.base.print() + ") " + power;
|
|
1812
|
+
}
|
|
1813
|
+
},
|
|
1814
|
+
|
|
1815
|
+
tex: function() {
|
|
1816
|
+
var power = "(" + this.power.tex() + ")";
|
|
1817
|
+
if (this.isNatural()) {
|
|
1818
|
+
return "\\ln" + power;
|
|
1819
|
+
} else {
|
|
1820
|
+
return "\\log_{" + this.base.tex() + "}" + power;
|
|
1821
|
+
}
|
|
1822
|
+
},
|
|
1823
|
+
|
|
1824
|
+
collect: function(options) {
|
|
1825
|
+
var log = this.recurse("collect", options);
|
|
1826
|
+
|
|
1827
|
+
if (log.power instanceof Num && log.power.eval() === 1) {
|
|
1828
|
+
|
|
1829
|
+
// e.g. ln(1) -> 0
|
|
1830
|
+
return Num.Zero;
|
|
1831
|
+
|
|
1832
|
+
} else if (log.base.equals(log.power)) {
|
|
1833
|
+
|
|
1834
|
+
// e.g. log_b(b) -> 1
|
|
1835
|
+
return Num.One;
|
|
1836
|
+
|
|
1837
|
+
} else if (log.power instanceof Pow &&
|
|
1838
|
+
log.power.base.equals(log.base)) {
|
|
1839
|
+
|
|
1840
|
+
// e.g. log_b(b^x) -> x
|
|
1841
|
+
return log.power.exp;
|
|
1842
|
+
} else {
|
|
1843
|
+
return log;
|
|
1844
|
+
}
|
|
1845
|
+
},
|
|
1846
|
+
|
|
1847
|
+
expand: function() {
|
|
1848
|
+
var log = this.recurse("expand");
|
|
1849
|
+
|
|
1850
|
+
if (log.power instanceof Mul) { // might want behind super-simplify() flag
|
|
1851
|
+
// e.g. ln(xy) -> ln(x) + ln(y)
|
|
1852
|
+
|
|
1853
|
+
var terms = _.map(log.power.terms, function(term) {
|
|
1854
|
+
// need to expand again in case new log powers are Pows
|
|
1855
|
+
return new Log(log.base, term).expand();
|
|
1856
|
+
});
|
|
1857
|
+
|
|
1858
|
+
return new Add(terms);
|
|
1859
|
+
|
|
1860
|
+
} else if (log.power instanceof Pow) {
|
|
1861
|
+
// e.g. ln(x^y) -> y*ln(x)
|
|
1862
|
+
|
|
1863
|
+
return new Mul(log.power.exp, new Log(log.base, log.power.base).expand()).flatten();
|
|
1864
|
+
} else if (!log.isNatural()) {
|
|
1865
|
+
// e.g. log_b(x) -> ln(x)/ln(b)
|
|
1866
|
+
|
|
1867
|
+
return Mul.handleDivide(new Log(Const.e, log.power), new Log(Const.e, log.base));
|
|
1868
|
+
} else {
|
|
1869
|
+
return log;
|
|
1870
|
+
}
|
|
1871
|
+
},
|
|
1872
|
+
|
|
1873
|
+
hints: _.extend(Log.prototype.hints, {
|
|
1874
|
+
open: false
|
|
1875
|
+
}),
|
|
1876
|
+
|
|
1877
|
+
isPositive: function() {
|
|
1878
|
+
var log = this.collect();
|
|
1879
|
+
|
|
1880
|
+
if (log.base instanceof Num &&
|
|
1881
|
+
log.power instanceof Num) {
|
|
1882
|
+
return this.eval() > 0;
|
|
1883
|
+
} else {
|
|
1884
|
+
return false;
|
|
1885
|
+
}
|
|
1886
|
+
},
|
|
1887
|
+
|
|
1888
|
+
needsExplicitMul: function() { return false; },
|
|
1889
|
+
|
|
1890
|
+
isNatural: function() { return this.base.equals(Const.e); }
|
|
1891
|
+
});
|
|
1892
|
+
|
|
1893
|
+
_.extend(Log, {
|
|
1894
|
+
natural: function() { return Const.e; },
|
|
1895
|
+
common: function() { return Num.Ten; },
|
|
1896
|
+
|
|
1897
|
+
create: function(base, power) {
|
|
1898
|
+
var log = new Log(base, power);
|
|
1899
|
+
if (!power.hints.parens) {
|
|
1900
|
+
log = log.addHint("open");
|
|
1901
|
+
}
|
|
1902
|
+
return log;
|
|
1903
|
+
}
|
|
1904
|
+
});
|
|
1905
|
+
|
|
1906
|
+
|
|
1907
|
+
/* trigonometric functions */
|
|
1908
|
+
export function Trig(type, arg) { this.type = type; this.arg = arg; }
|
|
1909
|
+
Trig.prototype = new Expr();
|
|
1910
|
+
|
|
1911
|
+
_.extend(Trig.prototype, {
|
|
1912
|
+
func: Trig,
|
|
1913
|
+
args: function() { return [this.type, this.arg]; },
|
|
1914
|
+
|
|
1915
|
+
functions: {
|
|
1916
|
+
sin: {
|
|
1917
|
+
eval: Math.sin,
|
|
1918
|
+
codegen: "Math.sin((",
|
|
1919
|
+
tex: "\\sin",
|
|
1920
|
+
expand: function() { return this; }
|
|
1921
|
+
},
|
|
1922
|
+
cos: {
|
|
1923
|
+
eval: Math.cos,
|
|
1924
|
+
codegen: "Math.cos((",
|
|
1925
|
+
tex: "\\cos",
|
|
1926
|
+
expand: function() { return this; }
|
|
1927
|
+
},
|
|
1928
|
+
tan: {
|
|
1929
|
+
eval: Math.tan,
|
|
1930
|
+
codegen: "Math.tan((",
|
|
1931
|
+
tex: "\\tan",
|
|
1932
|
+
expand: function() {
|
|
1933
|
+
return Mul.handleDivide(Trig.sin(this.arg), Trig.cos(this.arg));
|
|
1934
|
+
}
|
|
1935
|
+
},
|
|
1936
|
+
csc: {
|
|
1937
|
+
eval: function(arg) { return 1 / Math.sin(arg); },
|
|
1938
|
+
codegen: "(1/Math.sin(",
|
|
1939
|
+
tex: "\\csc",
|
|
1940
|
+
expand: function() {
|
|
1941
|
+
return Mul.handleDivide(Num.One, Trig.sin(this.arg));
|
|
1942
|
+
}
|
|
1943
|
+
},
|
|
1944
|
+
sec: {
|
|
1945
|
+
eval: function(arg) { return 1 / Math.cos(arg); },
|
|
1946
|
+
codegen: "(1/Math.cos(",
|
|
1947
|
+
tex: "\\sec",
|
|
1948
|
+
expand: function() {
|
|
1949
|
+
return Mul.handleDivide(Num.One, Trig.cos(this.arg));
|
|
1950
|
+
}
|
|
1951
|
+
},
|
|
1952
|
+
cot: {
|
|
1953
|
+
eval: function(arg) { return 1 / Math.tan(arg); },
|
|
1954
|
+
codegen: "(1/Math.tan(",
|
|
1955
|
+
tex: "\\cot",
|
|
1956
|
+
expand: function() {
|
|
1957
|
+
return Mul.handleDivide(Trig.cos(this.arg), Trig.sin(this.arg));
|
|
1958
|
+
}
|
|
1959
|
+
},
|
|
1960
|
+
arcsin: {
|
|
1961
|
+
eval: Math.asin,
|
|
1962
|
+
codegen: "Math.asin((",
|
|
1963
|
+
tex: "\\arcsin"
|
|
1964
|
+
},
|
|
1965
|
+
arccos: {
|
|
1966
|
+
eval: Math.acos,
|
|
1967
|
+
codegen: "Math.acos((",
|
|
1968
|
+
tex: "\\arccos"
|
|
1969
|
+
},
|
|
1970
|
+
arctan: {
|
|
1971
|
+
eval: Math.atan,
|
|
1972
|
+
codegen: "Math.atan((",
|
|
1973
|
+
tex: "\\arctan"
|
|
1974
|
+
},
|
|
1975
|
+
arccsc: {
|
|
1976
|
+
eval: function(arg) { return Math.asin(1 / arg); },
|
|
1977
|
+
codegen: "Math.asin(1/(",
|
|
1978
|
+
tex: "\\operatorname{arccsc}"
|
|
1979
|
+
},
|
|
1980
|
+
arcsec: {
|
|
1981
|
+
eval: function(arg) { return Math.acos(1 / arg); },
|
|
1982
|
+
codegen: "Math.acos(1/(",
|
|
1983
|
+
tex: "\\operatorname{arcsec}"
|
|
1984
|
+
},
|
|
1985
|
+
arccot: {
|
|
1986
|
+
eval: function(arg) { return Math.atan(1 / arg); },
|
|
1987
|
+
codegen: "Math.atan(1/(",
|
|
1988
|
+
tex: "\\operatorname{arccot}"
|
|
1989
|
+
},
|
|
1990
|
+
sinh: {
|
|
1991
|
+
eval: function(arg) {
|
|
1992
|
+
return (Math.exp(arg) - Math.exp(-arg)) / 2;
|
|
1993
|
+
},
|
|
1994
|
+
codegen: function(argStr) {
|
|
1995
|
+
return "((Math.exp(" + argStr + ") - Math.exp(-(" + argStr + "))) / 2)";
|
|
1996
|
+
},
|
|
1997
|
+
tex: "\\sinh",
|
|
1998
|
+
expand: function() { return this; }
|
|
1999
|
+
},
|
|
2000
|
+
cosh: {
|
|
2001
|
+
eval: function(arg) {
|
|
2002
|
+
return (Math.exp(arg) + Math.exp(-arg)) / 2;
|
|
2003
|
+
},
|
|
2004
|
+
codegen: function(argStr) {
|
|
2005
|
+
return "((Math.exp(" + argStr + ") + Math.exp(-(" + argStr + "))) / 2)";
|
|
2006
|
+
},
|
|
2007
|
+
tex: "\\cosh",
|
|
2008
|
+
expand: function() { return this; }
|
|
2009
|
+
},
|
|
2010
|
+
tanh: {
|
|
2011
|
+
eval: function(arg) {
|
|
2012
|
+
return (Math.exp(arg) - Math.exp(-arg)) / (Math.exp(arg) + Math.exp(-arg));
|
|
2013
|
+
},
|
|
2014
|
+
codegen: function(argStr) {
|
|
2015
|
+
return "(" +
|
|
2016
|
+
"(Math.exp(" + argStr + ") - Math.exp(-(" + argStr + ")))" +
|
|
2017
|
+
" / " +
|
|
2018
|
+
"(Math.exp(" + argStr + ") + Math.exp(-(" + argStr + ")))" +
|
|
2019
|
+
")";
|
|
2020
|
+
},
|
|
2021
|
+
tex: "\\tanh",
|
|
2022
|
+
expand: function() {
|
|
2023
|
+
return Mul.handleDivide(Trig.sinh(this.arg), Trig.cosh(this.arg));
|
|
2024
|
+
}
|
|
2025
|
+
},
|
|
2026
|
+
csch: {
|
|
2027
|
+
eval: function(arg) { return 2 / (Math.exp(arg) - Math.exp(-arg)); },
|
|
2028
|
+
codegen: function(argStr) {
|
|
2029
|
+
return "(2 / (Math.exp(" + argStr + ") - Math.exp(-(" + argStr + "))))";
|
|
2030
|
+
},
|
|
2031
|
+
tex: "\\csch",
|
|
2032
|
+
expand: function() {
|
|
2033
|
+
return Mul.handleDivide(Num.One, Trig.sinh(this.arg));
|
|
2034
|
+
}
|
|
2035
|
+
},
|
|
2036
|
+
sech: {
|
|
2037
|
+
eval: function(arg) { return 2 / (Math.exp(arg) + Math.exp(-arg)); },
|
|
2038
|
+
codegen: function(argStr) {
|
|
2039
|
+
return "(2 / (Math.exp(" + argStr + ") + Math.exp(-(" + argStr + "))))";
|
|
2040
|
+
},
|
|
2041
|
+
tex: "\\sech",
|
|
2042
|
+
expand: function() {
|
|
2043
|
+
return Mul.handleDivide(Num.One, Trig.cosh(this.arg));
|
|
2044
|
+
}
|
|
2045
|
+
},
|
|
2046
|
+
coth: {
|
|
2047
|
+
eval: function(arg) {
|
|
2048
|
+
return (Math.exp(arg) + Math.exp(-arg)) / (Math.exp(arg) - Math.exp(-arg));
|
|
2049
|
+
},
|
|
2050
|
+
codegen: function(argStr) {
|
|
2051
|
+
return "(" +
|
|
2052
|
+
"(Math.exp(" + argStr + ") + Math.exp(-(" + argStr + ")))" +
|
|
2053
|
+
" / " +
|
|
2054
|
+
"(Math.exp(" + argStr + ") - Math.exp(-(" + argStr + ")))" +
|
|
2055
|
+
")";
|
|
2056
|
+
},
|
|
2057
|
+
tex: "\\coth",
|
|
2058
|
+
expand: function() {
|
|
2059
|
+
return Mul.handleDivide(Trig.cosh(this.arg), Trig.sinh(this.arg));
|
|
2060
|
+
}
|
|
2061
|
+
},
|
|
2062
|
+
},
|
|
2063
|
+
|
|
2064
|
+
isEven: function() {
|
|
2065
|
+
return _.contains(["cos", "sec"], this.type);
|
|
2066
|
+
},
|
|
2067
|
+
|
|
2068
|
+
isInverse: function() {
|
|
2069
|
+
return this.type.indexOf("arc") === 0;
|
|
2070
|
+
},
|
|
2071
|
+
|
|
2072
|
+
isBasic: function() {
|
|
2073
|
+
return _.contains(["sin", "cos"], this.type);
|
|
2074
|
+
},
|
|
2075
|
+
|
|
2076
|
+
eval: function(vars, options) {
|
|
2077
|
+
var func = this.functions[this.type].eval;
|
|
2078
|
+
var arg = this.arg.eval(vars, options);
|
|
2079
|
+
return func(arg);
|
|
2080
|
+
},
|
|
2081
|
+
|
|
2082
|
+
codegen: function() {
|
|
2083
|
+
var func = this.functions[this.type].codegen;
|
|
2084
|
+
if (typeof func === "function") {
|
|
2085
|
+
return func(this.arg.codegen());
|
|
2086
|
+
} else if (typeof func === "string") {
|
|
2087
|
+
return func + this.arg.codegen() + "))";
|
|
2088
|
+
} else {
|
|
2089
|
+
throw new Error("codegen not implemented for " + this.type);
|
|
2090
|
+
}
|
|
2091
|
+
},
|
|
2092
|
+
|
|
2093
|
+
print: function() {
|
|
2094
|
+
return this.type + "(" + this.arg.print() + ")";
|
|
2095
|
+
},
|
|
2096
|
+
|
|
2097
|
+
tex: function(options) {
|
|
2098
|
+
var func = this.functions[this.type].tex;
|
|
2099
|
+
var arg = "(" + this.arg.tex() + ")";
|
|
2100
|
+
return (options && options.split) ? [func, arg] : func + arg;
|
|
2101
|
+
},
|
|
2102
|
+
|
|
2103
|
+
hints: _.extend(Trig.prototype.hints, {
|
|
2104
|
+
open: false
|
|
2105
|
+
}),
|
|
2106
|
+
|
|
2107
|
+
isPositive: function() {
|
|
2108
|
+
var trig = this.collect();
|
|
2109
|
+
|
|
2110
|
+
if (trig.arg instanceof Num) {
|
|
2111
|
+
return this.eval() > 0;
|
|
2112
|
+
} else {
|
|
2113
|
+
return false;
|
|
2114
|
+
}
|
|
2115
|
+
},
|
|
2116
|
+
|
|
2117
|
+
completeParse: function() {
|
|
2118
|
+
if (this.exp) {
|
|
2119
|
+
var pow = new Pow(this, this.exp);
|
|
2120
|
+
this.exp = undefined;
|
|
2121
|
+
return pow;
|
|
2122
|
+
} else {
|
|
2123
|
+
return this;
|
|
2124
|
+
}
|
|
2125
|
+
},
|
|
2126
|
+
|
|
2127
|
+
// TODO(alex): does every new node type need to redefine these?
|
|
2128
|
+
needsExplicitMul: function() { return false; },
|
|
2129
|
+
|
|
2130
|
+
expand: function() {
|
|
2131
|
+
var trig = this.recurse("expand");
|
|
2132
|
+
if (!trig.isInverse()) {
|
|
2133
|
+
// e.g. tan(x) -> sin(x)/cos(x)
|
|
2134
|
+
var expand = trig.functions[trig.type].expand;
|
|
2135
|
+
return _.bind(expand, trig)();
|
|
2136
|
+
} else {
|
|
2137
|
+
return trig;
|
|
2138
|
+
}
|
|
2139
|
+
},
|
|
2140
|
+
|
|
2141
|
+
collect: function(options) {
|
|
2142
|
+
var trig = this.recurse("collect", options);
|
|
2143
|
+
if (!trig.isInverse() && trig.arg.isNegative()) {
|
|
2144
|
+
var arg;
|
|
2145
|
+
if (trig.arg instanceof Num) {
|
|
2146
|
+
arg = trig.arg.abs();
|
|
2147
|
+
} else {
|
|
2148
|
+
arg = Mul.handleDivide(trig.arg, Num.Neg).collect(options);
|
|
2149
|
+
}
|
|
2150
|
+
|
|
2151
|
+
if (trig.isEven()) {
|
|
2152
|
+
// e.g. cos(-x) -> cos(x)
|
|
2153
|
+
return new Trig(trig.type, arg);
|
|
2154
|
+
|
|
2155
|
+
} else {
|
|
2156
|
+
// e.g. sin(-x) -> -sin(x)
|
|
2157
|
+
return new Mul(Num.Neg, new Trig(trig.type, arg));
|
|
2158
|
+
}
|
|
2159
|
+
} else {
|
|
2160
|
+
return trig;
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
});
|
|
2164
|
+
|
|
2165
|
+
_.extend(Trig, {
|
|
2166
|
+
create: function(pair, arg) {
|
|
2167
|
+
var type = pair[0];
|
|
2168
|
+
var exp = pair[1];
|
|
2169
|
+
|
|
2170
|
+
if (exp && exp.equals(Num.Neg)) {
|
|
2171
|
+
// e.g. sin^-1(x) -> arcsin(x)
|
|
2172
|
+
type = "arc" + type;
|
|
2173
|
+
exp = undefined;
|
|
2174
|
+
}
|
|
2175
|
+
|
|
2176
|
+
var trig = new Trig(type, arg);
|
|
2177
|
+
if (!arg.hints.parens) {
|
|
2178
|
+
trig = trig.addHint("open");
|
|
2179
|
+
}
|
|
2180
|
+
|
|
2181
|
+
if (exp) {
|
|
2182
|
+
trig.exp = exp;
|
|
2183
|
+
}
|
|
2184
|
+
|
|
2185
|
+
return trig;
|
|
2186
|
+
},
|
|
2187
|
+
|
|
2188
|
+
sin: function(arg) {
|
|
2189
|
+
return new Trig("sin", arg);
|
|
2190
|
+
},
|
|
2191
|
+
|
|
2192
|
+
cos: function(arg) {
|
|
2193
|
+
return new Trig("cos", arg);
|
|
2194
|
+
},
|
|
2195
|
+
|
|
2196
|
+
sinh: function(arg) {
|
|
2197
|
+
return new Trig("sinh", arg);
|
|
2198
|
+
},
|
|
2199
|
+
|
|
2200
|
+
cosh: function(arg) {
|
|
2201
|
+
return new Trig("cosh", arg);
|
|
2202
|
+
}
|
|
2203
|
+
});
|
|
2204
|
+
|
|
2205
|
+
|
|
2206
|
+
export function Abs(arg) { this.arg = arg; }
|
|
2207
|
+
Abs.prototype = new Expr();
|
|
2208
|
+
|
|
2209
|
+
_.extend(Abs.prototype, {
|
|
2210
|
+
func: Abs,
|
|
2211
|
+
args: function() { return [this.arg]; },
|
|
2212
|
+
eval: function(vars, options) { return Math.abs(this.arg.eval(vars, options)); },
|
|
2213
|
+
codegen: function() { return "Math.abs(" + this.arg.codegen() + ")"; },
|
|
2214
|
+
print: function() { return "abs(" + this.arg.print() + ")"; },
|
|
2215
|
+
|
|
2216
|
+
tex: function() {
|
|
2217
|
+
return "\\left|" + this.arg.tex() + "\\right|";
|
|
2218
|
+
},
|
|
2219
|
+
|
|
2220
|
+
collect: function(options) {
|
|
2221
|
+
var abs = this.recurse("collect", options);
|
|
2222
|
+
|
|
2223
|
+
if (abs.arg.isPositive()) {
|
|
2224
|
+
// e.g. |2^x| -> 2^x
|
|
2225
|
+
return abs.arg;
|
|
2226
|
+
} else if (abs.arg instanceof Num) {
|
|
2227
|
+
// e.g. |-2| -> 2
|
|
2228
|
+
return abs.arg.abs();
|
|
2229
|
+
} else if (abs.arg instanceof Mul) {
|
|
2230
|
+
// e.g. |-2*pi*x| -> 2*pi*|x|
|
|
2231
|
+
var terms = _.groupBy(abs.arg.terms, function(term) {
|
|
2232
|
+
if (term.isPositive()) {
|
|
2233
|
+
return "positive";
|
|
2234
|
+
} else if (term instanceof Num) {
|
|
2235
|
+
return "number";
|
|
2236
|
+
} else {
|
|
2237
|
+
return "other";
|
|
2238
|
+
}
|
|
2239
|
+
});
|
|
2240
|
+
|
|
2241
|
+
var positives = terms.positive.concat(_.invoke(terms.number, "abs"));
|
|
2242
|
+
|
|
2243
|
+
if (terms.other.length) {
|
|
2244
|
+
positives.push(new Abs(new Mul(terms.other).flatten()));
|
|
2245
|
+
}
|
|
2246
|
+
|
|
2247
|
+
return new Mul(positives).flatten();
|
|
2248
|
+
} else {
|
|
2249
|
+
return abs;
|
|
2250
|
+
}
|
|
2251
|
+
},
|
|
2252
|
+
|
|
2253
|
+
// this should definitely be behind a super-simplify flag
|
|
2254
|
+
expand: function() {
|
|
2255
|
+
var abs = this.recurse("expand");
|
|
2256
|
+
|
|
2257
|
+
if (abs.arg instanceof Mul) {
|
|
2258
|
+
// e.g. |xyz| -> |x|*|y|*|z|
|
|
2259
|
+
var terms = _.map(abs.arg.terms, function(term) {
|
|
2260
|
+
return new Abs(term);
|
|
2261
|
+
});
|
|
2262
|
+
return new Mul(terms);
|
|
2263
|
+
} else {
|
|
2264
|
+
return abs;
|
|
2265
|
+
}
|
|
2266
|
+
},
|
|
2267
|
+
|
|
2268
|
+
isPositive: function() { return true; }
|
|
2269
|
+
});
|
|
2270
|
+
|
|
2271
|
+
|
|
2272
|
+
/* equation */
|
|
2273
|
+
export function Eq(left, type, right) {
|
|
2274
|
+
this.left = left;
|
|
2275
|
+
this.type = type;
|
|
2276
|
+
this.right = right;
|
|
2277
|
+
}
|
|
2278
|
+
Eq.prototype = new Expr();
|
|
2279
|
+
|
|
2280
|
+
_.extend(Eq.prototype, {
|
|
2281
|
+
func: Eq,
|
|
2282
|
+
args: function() { return [this.left, this.type, this.right]; },
|
|
2283
|
+
|
|
2284
|
+
needsExplicitMul: function() { return false; },
|
|
2285
|
+
|
|
2286
|
+
print: function() {
|
|
2287
|
+
return this.left.print() + this.type + this.right.print();
|
|
2288
|
+
},
|
|
2289
|
+
|
|
2290
|
+
signs: {
|
|
2291
|
+
"=": " = ",
|
|
2292
|
+
"<": " < ",
|
|
2293
|
+
">": " > ",
|
|
2294
|
+
"<>": " \\ne ",
|
|
2295
|
+
"<=": " \\le ",
|
|
2296
|
+
">=": " \\ge "
|
|
2297
|
+
},
|
|
2298
|
+
|
|
2299
|
+
tex: function() {
|
|
2300
|
+
return this.left.tex() + this.signs[this.type] + this.right.tex();
|
|
2301
|
+
},
|
|
2302
|
+
|
|
2303
|
+
normalize: function() {
|
|
2304
|
+
var eq = this.recurse("normalize");
|
|
2305
|
+
|
|
2306
|
+
if (_.contains([">", ">="], eq.type)) {
|
|
2307
|
+
// inequalities should have the smaller side on the left
|
|
2308
|
+
return new Eq(eq.right, eq.type.replace(">", "<"), eq.left);
|
|
2309
|
+
} else {
|
|
2310
|
+
return eq;
|
|
2311
|
+
}
|
|
2312
|
+
},
|
|
2313
|
+
|
|
2314
|
+
// convert this equation to an expression set to zero
|
|
2315
|
+
// the expression is normalized to a canonical form
|
|
2316
|
+
// e.g. y/2=x/4 -> y/2-x/4(=0) -> 2y-x(=0)
|
|
2317
|
+
// unless unfactored is specified, will then divide through
|
|
2318
|
+
asExpr: function(unfactored) {
|
|
2319
|
+
var isZero = function(expr) {
|
|
2320
|
+
return expr instanceof Num && expr.isSimple() && expr.eval() === 0;
|
|
2321
|
+
};
|
|
2322
|
+
|
|
2323
|
+
// first convert to a sequence of additive terms
|
|
2324
|
+
var terms = [];
|
|
2325
|
+
|
|
2326
|
+
if (this.left instanceof Add) {
|
|
2327
|
+
terms = _.clone(this.left.terms);
|
|
2328
|
+
} else if (!isZero(this.left)) {
|
|
2329
|
+
terms = [this.left];
|
|
2330
|
+
}
|
|
2331
|
+
|
|
2332
|
+
if (this.right instanceof Add) {
|
|
2333
|
+
terms = terms.concat(this.right.negate().terms);
|
|
2334
|
+
} else if (!isZero(this.right)) {
|
|
2335
|
+
terms.push(this.right.negate());
|
|
2336
|
+
}
|
|
2337
|
+
|
|
2338
|
+
var isInequality = !this.isEquality();
|
|
2339
|
+
|
|
2340
|
+
// Collect over each term individually to transform simple expressions
|
|
2341
|
+
// into numbers that might have denominators, taking into account
|
|
2342
|
+
// float precision. We have to be very careful to not introduce any
|
|
2343
|
+
// irrational floats before asExpr() returns, because by definition
|
|
2344
|
+
// they do not have exact denominators...
|
|
2345
|
+
terms = _.invoke(terms, "collect", {preciseFloats: true});
|
|
2346
|
+
|
|
2347
|
+
// ...and we multiply through by every denominator.
|
|
2348
|
+
for (var i = 0; i < terms.length; i++) {
|
|
2349
|
+
var denominator = terms[i].getDenominator();
|
|
2350
|
+
|
|
2351
|
+
// Can't multiply inequalities by non 100% positive factors
|
|
2352
|
+
if (isInequality && !denominator.isPositive()) {
|
|
2353
|
+
denominator = denominator.asPositiveFactor();
|
|
2354
|
+
}
|
|
2355
|
+
|
|
2356
|
+
if (!denominator.equals(Num.One)) {
|
|
2357
|
+
terms = _.map(terms, function(term) {
|
|
2358
|
+
return Mul.createOrAppend(term, denominator).simplify({
|
|
2359
|
+
once: true,
|
|
2360
|
+
preciseFloats: true
|
|
2361
|
+
});
|
|
2362
|
+
});
|
|
2363
|
+
}
|
|
2364
|
+
}
|
|
2365
|
+
|
|
2366
|
+
var add = new Add(terms).flatten();
|
|
2367
|
+
return unfactored ? add : this.divideThrough(add);
|
|
2368
|
+
},
|
|
2369
|
+
|
|
2370
|
+
// divide through by every common factor in the expression
|
|
2371
|
+
// e.g. 2y-4x(=0) -> y-2x(=0)
|
|
2372
|
+
// TODO(alex): Make it an option to only divide by variables/expressions
|
|
2373
|
+
// guaranteed to be nonzero
|
|
2374
|
+
divideThrough: function(expr) {
|
|
2375
|
+
var isInequality = !this.isEquality();
|
|
2376
|
+
|
|
2377
|
+
var simplified = expr.simplify({once: true});
|
|
2378
|
+
var factored = simplified.factor({keepNegative: isInequality});
|
|
2379
|
+
|
|
2380
|
+
if (!(factored instanceof Mul)) {
|
|
2381
|
+
return expr;
|
|
2382
|
+
}
|
|
2383
|
+
|
|
2384
|
+
var terms = factored.terms;
|
|
2385
|
+
|
|
2386
|
+
var isAdd = function(term) { return term instanceof Add; };
|
|
2387
|
+
var hasVar = function(term) { return !!term.getVars().length; };
|
|
2388
|
+
var isOne = function(term) { return term.equals(Num.One); };
|
|
2389
|
+
|
|
2390
|
+
var grouped = _.groupBy(terms, isAdd);
|
|
2391
|
+
var adds = grouped[true] || [];
|
|
2392
|
+
var others = grouped[false] || [];
|
|
2393
|
+
|
|
2394
|
+
if (adds.length && this.isEquality()) {
|
|
2395
|
+
// keep only Adds
|
|
2396
|
+
// e.g. 2xy(z+1)(=0) -> z+1(=0)
|
|
2397
|
+
return new Mul(adds).flatten();
|
|
2398
|
+
}
|
|
2399
|
+
|
|
2400
|
+
var denominator = others;
|
|
2401
|
+
|
|
2402
|
+
if (!adds.length) {
|
|
2403
|
+
// if no Adds, keep all variable terms to preserve meaning
|
|
2404
|
+
// e.g. 42xyz(=0) -> xyz(=0)
|
|
2405
|
+
denominator = _.reject(denominator, hasVar);
|
|
2406
|
+
}
|
|
2407
|
+
|
|
2408
|
+
if (isInequality) {
|
|
2409
|
+
// can't divide inequalities by non 100% positive factors
|
|
2410
|
+
// e.g. 42x^2y(z+1)(=0) -> y(z+1)(=0)
|
|
2411
|
+
denominator = _.invoke(denominator, "asPositiveFactor");
|
|
2412
|
+
}
|
|
2413
|
+
|
|
2414
|
+
// don't need to divide by one
|
|
2415
|
+
denominator = _.reject(denominator, isOne);
|
|
2416
|
+
|
|
2417
|
+
denominator = _.map(denominator, function(term) {
|
|
2418
|
+
return new Pow(term, Num.Div);
|
|
2419
|
+
});
|
|
2420
|
+
|
|
2421
|
+
var dividedResult = new Mul(terms.concat(denominator)).collect();
|
|
2422
|
+
|
|
2423
|
+
// If the end result is the same as the original factoring,
|
|
2424
|
+
// rollback the factoring and discard all intermediate steps.
|
|
2425
|
+
if (dividedResult.equals(factored)) {
|
|
2426
|
+
return simplified;
|
|
2427
|
+
} else {
|
|
2428
|
+
return dividedResult;
|
|
2429
|
+
}
|
|
2430
|
+
},
|
|
2431
|
+
|
|
2432
|
+
isEquality: function() {
|
|
2433
|
+
return _.contains(["=", "<>"], this.type);
|
|
2434
|
+
},
|
|
2435
|
+
|
|
2436
|
+
compare: function(other) {
|
|
2437
|
+
// expression comparisons are handled by Expr.compare()
|
|
2438
|
+
if (!(other instanceof Eq)) {
|
|
2439
|
+
return false;
|
|
2440
|
+
}
|
|
2441
|
+
|
|
2442
|
+
var eq1 = this.normalize();
|
|
2443
|
+
var eq2 = other.normalize();
|
|
2444
|
+
|
|
2445
|
+
if (eq1.type !== eq2.type) {
|
|
2446
|
+
return false;
|
|
2447
|
+
}
|
|
2448
|
+
|
|
2449
|
+
// need to collect to properly factor out common factors
|
|
2450
|
+
// e.g x+2x=6 -> 3x=6 -> 3x-6(=0) -> x-2(=0)
|
|
2451
|
+
var expr1 = eq1.divideThrough(eq1.asExpr(/* unfactored */ true).collect());
|
|
2452
|
+
var expr2 = eq2.divideThrough(eq2.asExpr(/* unfactored */ true).collect());
|
|
2453
|
+
|
|
2454
|
+
if (eq1.isEquality()) {
|
|
2455
|
+
// equals and not-equals can be subtracted either way
|
|
2456
|
+
return expr1.compare(expr2) ||
|
|
2457
|
+
expr1.compare(Mul.handleNegative(expr2));
|
|
2458
|
+
} else {
|
|
2459
|
+
return expr1.compare(expr2);
|
|
2460
|
+
}
|
|
2461
|
+
},
|
|
2462
|
+
|
|
2463
|
+
// should only be done after compare() returns true to avoid false positives
|
|
2464
|
+
sameForm: function(other) {
|
|
2465
|
+
var eq1 = this.normalize();
|
|
2466
|
+
var eq2 = other.normalize();
|
|
2467
|
+
|
|
2468
|
+
var same = eq1.left.sameForm(eq2.left) && eq1.right.sameForm(eq2.right);
|
|
2469
|
+
|
|
2470
|
+
if (eq1.isEquality()) {
|
|
2471
|
+
// equals and not-equals can be commutative with respect to the sign
|
|
2472
|
+
return same || (eq1.left.sameForm(eq2.right) && eq1.right.sameForm(eq2.left));
|
|
2473
|
+
} else {
|
|
2474
|
+
return same;
|
|
2475
|
+
}
|
|
2476
|
+
},
|
|
2477
|
+
|
|
2478
|
+
// we don't want to override collect because it would turn y=x into y-x(=0)
|
|
2479
|
+
// instead, we ask if the equation was in that form, would it be simplified?
|
|
2480
|
+
isSimplified: function() {
|
|
2481
|
+
var expr = this.asExpr(/* unfactored */ true);
|
|
2482
|
+
var simplified = this.divideThrough(expr).simplify();
|
|
2483
|
+
return expr.equals(simplified) &&
|
|
2484
|
+
this.left.isSimplified() &&
|
|
2485
|
+
this.right.isSimplified();
|
|
2486
|
+
}
|
|
2487
|
+
});
|
|
2488
|
+
|
|
2489
|
+
_.extend(Eq.prototype, {
|
|
2490
|
+
// Assumptions: Expression is of the form a+bx, and we solve for x
|
|
2491
|
+
solveLinearEquationForVariable: function(variable) {
|
|
2492
|
+
var expr = this.asExpr();
|
|
2493
|
+
if (!expr.is(Add) || expr.terms.length !== 2) {
|
|
2494
|
+
throw new Error("Can only handle linear equations of the form " +
|
|
2495
|
+
"a + bx (= 0)");
|
|
2496
|
+
}
|
|
2497
|
+
|
|
2498
|
+
var hasVar = function(term) {
|
|
2499
|
+
return term.has(Var) && _.contains(term.getVars(), variable.symbol);
|
|
2500
|
+
};
|
|
2501
|
+
|
|
2502
|
+
var a, b;
|
|
2503
|
+
|
|
2504
|
+
if (hasVar(expr.terms[0])) {
|
|
2505
|
+
a = Mul.handleNegative(expr.terms[1]);
|
|
2506
|
+
b = Mul.handleDivide(expr.terms[0], variable);
|
|
2507
|
+
} else {
|
|
2508
|
+
a = Mul.handleNegative(expr.terms[0]);
|
|
2509
|
+
b = Mul.handleDivide(expr.terms[1], variable);
|
|
2510
|
+
}
|
|
2511
|
+
|
|
2512
|
+
return Mul.handleDivide(a, b).simplify();
|
|
2513
|
+
}
|
|
2514
|
+
});
|
|
2515
|
+
|
|
2516
|
+
|
|
2517
|
+
/* abstract symbol node */
|
|
2518
|
+
function Symbol() {}
|
|
2519
|
+
Symbol.prototype = new Expr();
|
|
2520
|
+
|
|
2521
|
+
_.extend(Symbol.prototype, {
|
|
2522
|
+
|
|
2523
|
+
needsExplicitMul: function() { return false; },
|
|
2524
|
+
|
|
2525
|
+
findGCD: function(factor) {
|
|
2526
|
+
if (factor instanceof Symbol || factor instanceof Num) {
|
|
2527
|
+
return this.equals(factor) ? this : Num.One;
|
|
2528
|
+
} else {
|
|
2529
|
+
return factor.findGCD(this);
|
|
2530
|
+
}
|
|
2531
|
+
}
|
|
2532
|
+
});
|
|
2533
|
+
|
|
2534
|
+
|
|
2535
|
+
/* function variable */
|
|
2536
|
+
export function Func(symbol, arg) {
|
|
2537
|
+
this.symbol = symbol; this.arg = arg;
|
|
2538
|
+
}
|
|
2539
|
+
Func.prototype = new Symbol();
|
|
2540
|
+
|
|
2541
|
+
_.extend(Func.prototype, {
|
|
2542
|
+
func: Func,
|
|
2543
|
+
args: function() { return [this.symbol, this.arg]; },
|
|
2544
|
+
|
|
2545
|
+
print: function() {
|
|
2546
|
+
return this.symbol + "(" + this.arg.print() + ")";
|
|
2547
|
+
},
|
|
2548
|
+
|
|
2549
|
+
tex: function() {
|
|
2550
|
+
return this.symbol + "(" + this.arg.tex() + ")";
|
|
2551
|
+
},
|
|
2552
|
+
|
|
2553
|
+
eval: function(vars, options) {
|
|
2554
|
+
var arg = this.arg;
|
|
2555
|
+
var func = vars[this.symbol];
|
|
2556
|
+
var newVars = _.extend(_.clone(vars), {
|
|
2557
|
+
x: arg.eval(vars, options)
|
|
2558
|
+
});
|
|
2559
|
+
var parsedFunc = parse(func, options);
|
|
2560
|
+
if (parsedFunc.parsed) {
|
|
2561
|
+
return parsedFunc.expr.eval(newVars, options);
|
|
2562
|
+
}
|
|
2563
|
+
// If parsedFunc isn't actually parsed, return its error
|
|
2564
|
+
return parsedFunc;
|
|
2565
|
+
},
|
|
2566
|
+
|
|
2567
|
+
codegen: function() {
|
|
2568
|
+
return 'vars["' + this.symbol + '"](' +
|
|
2569
|
+
this.arg.codegen() + ')';
|
|
2570
|
+
},
|
|
2571
|
+
|
|
2572
|
+
getUnits: function() {
|
|
2573
|
+
return this.arg.getUnits();
|
|
2574
|
+
},
|
|
2575
|
+
|
|
2576
|
+
getVars: function(excludeFunc) {
|
|
2577
|
+
if (excludeFunc) {
|
|
2578
|
+
return this.arg.getVars();
|
|
2579
|
+
} else {
|
|
2580
|
+
return _.union(this.arg.getVars(), [this.symbol]).sort();
|
|
2581
|
+
}
|
|
2582
|
+
},
|
|
2583
|
+
|
|
2584
|
+
getConsts: function() {
|
|
2585
|
+
return this.arg.getConsts();
|
|
2586
|
+
},
|
|
2587
|
+
});
|
|
2588
|
+
|
|
2589
|
+
|
|
2590
|
+
/* variable */
|
|
2591
|
+
export function Var(symbol, subscript) {
|
|
2592
|
+
this.symbol = symbol;
|
|
2593
|
+
this.subscript = subscript;
|
|
2594
|
+
}
|
|
2595
|
+
Var.prototype = new Symbol();
|
|
2596
|
+
|
|
2597
|
+
_.extend(Var.prototype, {
|
|
2598
|
+
func: Var,
|
|
2599
|
+
args: function() { return [this.symbol, this.subscript]; },
|
|
2600
|
+
|
|
2601
|
+
exprArgs: function() { return []; },
|
|
2602
|
+
recurse: function() { return this; },
|
|
2603
|
+
|
|
2604
|
+
print: function() {
|
|
2605
|
+
var sub = "";
|
|
2606
|
+
if (this.subscript) {
|
|
2607
|
+
sub = "_(" + this.subscript.print() + ")";
|
|
2608
|
+
}
|
|
2609
|
+
return this.symbol + sub;
|
|
2610
|
+
},
|
|
2611
|
+
|
|
2612
|
+
// Provide a way to easily evalate expressions with the common case,
|
|
2613
|
+
// subscripts that consist of a single number or symbol e.g. x_a or x_42
|
|
2614
|
+
prettyPrint: function() {
|
|
2615
|
+
var sub = this.subscript;
|
|
2616
|
+
if (sub && (sub instanceof Num || sub instanceof Symbol)) {
|
|
2617
|
+
return this.symbol + "_" + sub.print();
|
|
2618
|
+
} else {
|
|
2619
|
+
return this.print();
|
|
2620
|
+
}
|
|
2621
|
+
},
|
|
2622
|
+
|
|
2623
|
+
tex: function() {
|
|
2624
|
+
var sub = "";
|
|
2625
|
+
if (this.subscript) {
|
|
2626
|
+
sub = "_{" + this.subscript.tex() + "}";
|
|
2627
|
+
}
|
|
2628
|
+
var prefix = this.symbol.length > 1 ? "\\" : "";
|
|
2629
|
+
return prefix + this.symbol + sub;
|
|
2630
|
+
},
|
|
2631
|
+
|
|
2632
|
+
repr: function() { return "Var(" + this.print() + ")"; },
|
|
2633
|
+
|
|
2634
|
+
eval: function(vars, options) {
|
|
2635
|
+
return vars[this.prettyPrint()];
|
|
2636
|
+
},
|
|
2637
|
+
|
|
2638
|
+
codegen: function() {
|
|
2639
|
+
return 'vars["' + this.prettyPrint() + '"]';
|
|
2640
|
+
},
|
|
2641
|
+
|
|
2642
|
+
getVars: function() { return [this.prettyPrint()]; },
|
|
2643
|
+
|
|
2644
|
+
isPositive: function() { return false; }
|
|
2645
|
+
});
|
|
2646
|
+
|
|
2647
|
+
|
|
2648
|
+
/* constant */
|
|
2649
|
+
export function Const(symbol) { this.symbol = symbol; }
|
|
2650
|
+
Const.prototype = new Symbol();
|
|
2651
|
+
|
|
2652
|
+
_.extend(Const.prototype, {
|
|
2653
|
+
func: Const,
|
|
2654
|
+
args: function() { return [this.symbol]; },
|
|
2655
|
+
recurse: function() { return this; },
|
|
2656
|
+
|
|
2657
|
+
eval: function(vars, options) {
|
|
2658
|
+
if (this.symbol === "pi") {
|
|
2659
|
+
return Math.PI;
|
|
2660
|
+
} else if (this.symbol === "e") {
|
|
2661
|
+
return Math.E;
|
|
2662
|
+
}
|
|
2663
|
+
},
|
|
2664
|
+
|
|
2665
|
+
codegen: function() {
|
|
2666
|
+
if (this.symbol === "pi") {
|
|
2667
|
+
return "Math.PI";
|
|
2668
|
+
} else if (this.symbol === "e") {
|
|
2669
|
+
return "Math.E";
|
|
2670
|
+
}
|
|
2671
|
+
},
|
|
2672
|
+
|
|
2673
|
+
print: function() { return this.symbol; },
|
|
2674
|
+
|
|
2675
|
+
tex: function() {
|
|
2676
|
+
if (this.symbol === "pi") {
|
|
2677
|
+
return "\\pi ";
|
|
2678
|
+
} else if (this.symbol === "e") {
|
|
2679
|
+
return "e";
|
|
2680
|
+
}
|
|
2681
|
+
},
|
|
2682
|
+
|
|
2683
|
+
isPositive: function() {
|
|
2684
|
+
return this.eval() > 0;
|
|
2685
|
+
},
|
|
2686
|
+
|
|
2687
|
+
abs: function() {
|
|
2688
|
+
if (this.eval() > 0) {
|
|
2689
|
+
return this;
|
|
2690
|
+
} else {
|
|
2691
|
+
return Mul.handleNegative(this);
|
|
2692
|
+
}
|
|
2693
|
+
},
|
|
2694
|
+
|
|
2695
|
+
getConsts: function() {
|
|
2696
|
+
return [this.print()];
|
|
2697
|
+
},
|
|
2698
|
+
});
|
|
2699
|
+
|
|
2700
|
+
Const.e = new Const("e");
|
|
2701
|
+
Const.pi = new Const("pi");
|
|
2702
|
+
|
|
2703
|
+
|
|
2704
|
+
/* abstract number node */
|
|
2705
|
+
function Num() {}
|
|
2706
|
+
Num.prototype = new Expr();
|
|
2707
|
+
|
|
2708
|
+
_.extend(Num.prototype, {
|
|
2709
|
+
repr: function() { return this.print(); },
|
|
2710
|
+
strip: function() { return this.abs(); },
|
|
2711
|
+
recurse: function() { return this; },
|
|
2712
|
+
codegen: function() { return this.print(); },
|
|
2713
|
+
|
|
2714
|
+
// takes another Num and returns a new Num
|
|
2715
|
+
add: abstract,
|
|
2716
|
+
mul: abstract,
|
|
2717
|
+
|
|
2718
|
+
// returns this Num's additive inverse
|
|
2719
|
+
negate: abstract,
|
|
2720
|
+
|
|
2721
|
+
isSubtract: function() { return this.hints.subtract; },
|
|
2722
|
+
|
|
2723
|
+
// return the absolute value of the number
|
|
2724
|
+
abs: abstract,
|
|
2725
|
+
|
|
2726
|
+
needsExplicitMul: function() { return true; },
|
|
2727
|
+
|
|
2728
|
+
findGCD: abstract,
|
|
2729
|
+
|
|
2730
|
+
isPositive: function() {
|
|
2731
|
+
return this.eval() > 0;
|
|
2732
|
+
},
|
|
2733
|
+
|
|
2734
|
+
isNegative: function() {
|
|
2735
|
+
return this.eval() < 0;
|
|
2736
|
+
},
|
|
2737
|
+
|
|
2738
|
+
asPositiveFactor: function() {
|
|
2739
|
+
return this.isPositive() ? this : this.abs();
|
|
2740
|
+
},
|
|
2741
|
+
|
|
2742
|
+
// hints for interpreting and rendering user input
|
|
2743
|
+
hints: _.extend(Num.prototype.hints, {
|
|
2744
|
+
negate: false,
|
|
2745
|
+
subtract: false,
|
|
2746
|
+
divide: false,
|
|
2747
|
+
root: false,
|
|
2748
|
+
fraction: false,
|
|
2749
|
+
entered: false
|
|
2750
|
+
}),
|
|
2751
|
+
|
|
2752
|
+
// whether a number is considered simple (one term)
|
|
2753
|
+
// e.g. for reals, ints and floats are simple
|
|
2754
|
+
isSimple: abstract,
|
|
2755
|
+
|
|
2756
|
+
// Based on http://stackoverflow.com/a/10454560/2571482
|
|
2757
|
+
getDecimalPlaces: function() {
|
|
2758
|
+
var match = ("" + this.n).match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/);
|
|
2759
|
+
if (match) {
|
|
2760
|
+
return Math.max(
|
|
2761
|
+
0,
|
|
2762
|
+
// Number of digits right of decimal point
|
|
2763
|
+
(match[1] ? match[1].length : 0) -
|
|
2764
|
+
// Adjust for scientific notation
|
|
2765
|
+
(match[2] ? +match[2] : 0)
|
|
2766
|
+
);
|
|
2767
|
+
} else {
|
|
2768
|
+
return 0;
|
|
2769
|
+
}
|
|
2770
|
+
},
|
|
2771
|
+
|
|
2772
|
+
asRational: abstract
|
|
2773
|
+
});
|
|
2774
|
+
|
|
2775
|
+
|
|
2776
|
+
/* rational number (n: numerator, d: denominator) */
|
|
2777
|
+
export function Rational(numerator, denominator) {
|
|
2778
|
+
var n = numerator; var d = denominator;
|
|
2779
|
+
if (d < 0) {
|
|
2780
|
+
n = -n; d = -d;
|
|
2781
|
+
}
|
|
2782
|
+
this.n = n; this.d = d;
|
|
2783
|
+
}
|
|
2784
|
+
Rational.prototype = new Num();
|
|
2785
|
+
|
|
2786
|
+
_.extend(Rational.prototype, {
|
|
2787
|
+
func: Rational,
|
|
2788
|
+
args: function() { return [this.n, this.d]; },
|
|
2789
|
+
eval: function() { return this.n / this.d; },
|
|
2790
|
+
|
|
2791
|
+
print: function() {
|
|
2792
|
+
return this.n.toString() + "/" + this.d.toString();
|
|
2793
|
+
},
|
|
2794
|
+
|
|
2795
|
+
tex: function() {
|
|
2796
|
+
var tex = "\\frac{" + Math.abs(this.n).toString() + "}{" + this.d.toString() + "}";
|
|
2797
|
+
return this.n < 0 ? "-" + tex : tex;
|
|
2798
|
+
},
|
|
2799
|
+
|
|
2800
|
+
add: function(num, options) {
|
|
2801
|
+
if (num instanceof Rational) {
|
|
2802
|
+
return new Rational(this.n * num.d + this.d * num.n, this.d * num.d).collect();
|
|
2803
|
+
} else {
|
|
2804
|
+
return num.add(this, options);
|
|
2805
|
+
}
|
|
2806
|
+
},
|
|
2807
|
+
|
|
2808
|
+
mul: function(num, options) {
|
|
2809
|
+
if (num instanceof Rational) {
|
|
2810
|
+
return new Rational(this.n * num.n, this.d * num.d).collect();
|
|
2811
|
+
} else {
|
|
2812
|
+
return num.mul(this, options);
|
|
2813
|
+
}
|
|
2814
|
+
},
|
|
2815
|
+
|
|
2816
|
+
collect: function() {
|
|
2817
|
+
var gcd = Num.findGCD(this.n, this.d);
|
|
2818
|
+
|
|
2819
|
+
var n = this.n / gcd;
|
|
2820
|
+
var d = this.d / gcd;
|
|
2821
|
+
|
|
2822
|
+
if (d === 1) {
|
|
2823
|
+
return new Int(n);
|
|
2824
|
+
} else {
|
|
2825
|
+
return new Rational(n, d);
|
|
2826
|
+
}
|
|
2827
|
+
},
|
|
2828
|
+
|
|
2829
|
+
negate: function() {
|
|
2830
|
+
return new Rational(-this.n, this.d);
|
|
2831
|
+
},
|
|
2832
|
+
|
|
2833
|
+
abs: function() {
|
|
2834
|
+
return new Rational(Math.abs(this.n), this.d);
|
|
2835
|
+
},
|
|
2836
|
+
|
|
2837
|
+
findGCD: function(factor) {
|
|
2838
|
+
// Attempt to factor out common numerators and denominators to return
|
|
2839
|
+
// a Rational instead of a Float
|
|
2840
|
+
if (factor instanceof Rational) {
|
|
2841
|
+
// For more background, see
|
|
2842
|
+
// http://math.stackexchange.com/questions/151081/gcd-of-rationals
|
|
2843
|
+
var numerator = Num.findGCD(this.n * factor.d, factor.n * this.d);
|
|
2844
|
+
var denominator = this.d * factor.d;
|
|
2845
|
+
// Create the rational, then call .collect() to simplify it
|
|
2846
|
+
return new Rational(numerator, denominator).collect();
|
|
2847
|
+
} else if (factor instanceof Int) {
|
|
2848
|
+
return new Rational(Num.findGCD(this.n, factor.n), this.d);
|
|
2849
|
+
} else {
|
|
2850
|
+
return factor.findGCD(this);
|
|
2851
|
+
}
|
|
2852
|
+
},
|
|
2853
|
+
|
|
2854
|
+
// for now, assuming that exp is a Num
|
|
2855
|
+
raiseToThe: function(exp) {
|
|
2856
|
+
if (exp instanceof Int) {
|
|
2857
|
+
var positive = exp.eval() > 0;
|
|
2858
|
+
var abs = exp.abs().eval();
|
|
2859
|
+
var n = Math.pow(this.n, abs);
|
|
2860
|
+
var d = Math.pow(this.d, abs);
|
|
2861
|
+
if (positive) {
|
|
2862
|
+
return new Rational(n, d).collect();
|
|
2863
|
+
} else {
|
|
2864
|
+
return new Rational(d, n).collect();
|
|
2865
|
+
}
|
|
2866
|
+
} else {
|
|
2867
|
+
return new Float(this.eval()).raiseToThe(exp);
|
|
2868
|
+
}
|
|
2869
|
+
},
|
|
2870
|
+
|
|
2871
|
+
getDenominator: function() {
|
|
2872
|
+
return new Int(this.d);
|
|
2873
|
+
},
|
|
2874
|
+
|
|
2875
|
+
isSimple: function() { return false; },
|
|
2876
|
+
|
|
2877
|
+
asRational: function() { return this; }
|
|
2878
|
+
});
|
|
2879
|
+
|
|
2880
|
+
|
|
2881
|
+
/* integer (n: numerator/number) */
|
|
2882
|
+
export function Int(number) { this.n = number; }
|
|
2883
|
+
Int.prototype = new Rational(0, 1);
|
|
2884
|
+
|
|
2885
|
+
_.extend(Int.prototype, {
|
|
2886
|
+
func: Int,
|
|
2887
|
+
args: function() { return [this.n]; },
|
|
2888
|
+
print: function() { return this.n.toString(); },
|
|
2889
|
+
tex: function() { return this.n.toString(); },
|
|
2890
|
+
negate: function() { return new Int(-this.n); },
|
|
2891
|
+
abs: function() { return new Int(Math.abs(this.n)); },
|
|
2892
|
+
isSimple: function() { return true; },
|
|
2893
|
+
findGCD: function(factor) {
|
|
2894
|
+
if (factor instanceof Int) {
|
|
2895
|
+
return new Int(Num.findGCD(this.n, factor.n));
|
|
2896
|
+
} else {
|
|
2897
|
+
return factor.findGCD(this);
|
|
2898
|
+
}
|
|
2899
|
+
}
|
|
2900
|
+
});
|
|
2901
|
+
|
|
2902
|
+
_.extend(Int, {
|
|
2903
|
+
create: function(n) { return new Int(n).addHint("entered"); }
|
|
2904
|
+
});
|
|
2905
|
+
|
|
2906
|
+
/* float (n: number) */
|
|
2907
|
+
export function Float(number) { this.n = number; }
|
|
2908
|
+
Float.prototype = new Num();
|
|
2909
|
+
|
|
2910
|
+
_.extend(Float.prototype, {
|
|
2911
|
+
func: Float,
|
|
2912
|
+
args: function() { return [this.n]; },
|
|
2913
|
+
eval: function() { return this.n; },
|
|
2914
|
+
|
|
2915
|
+
// TODO(alex): when we internationalize number parsing/display
|
|
2916
|
+
// we should make sure to use the appropriate decimal mark here
|
|
2917
|
+
print: function() { return this.n.toString(); },
|
|
2918
|
+
tex: function() { return this.n.toString(); },
|
|
2919
|
+
|
|
2920
|
+
add: function(num, options) {
|
|
2921
|
+
if (options && options.preciseFloats) {
|
|
2922
|
+
return Float.toDecimalPlaces(
|
|
2923
|
+
this.n + num.eval(),
|
|
2924
|
+
Math.max(this.getDecimalPlaces(), num.getDecimalPlaces())
|
|
2925
|
+
);
|
|
2926
|
+
} else {
|
|
2927
|
+
return new Float(this.n + num.eval()).collect();
|
|
2928
|
+
}
|
|
2929
|
+
},
|
|
2930
|
+
|
|
2931
|
+
mul: function(num, options) {
|
|
2932
|
+
if (options && options.preciseFloats) {
|
|
2933
|
+
return Float.toDecimalPlaces(
|
|
2934
|
+
this.n * num.eval(),
|
|
2935
|
+
this.getDecimalPlaces() + num.getDecimalPlaces()
|
|
2936
|
+
);
|
|
2937
|
+
} else {
|
|
2938
|
+
return new Float(this.n * num.eval()).collect();
|
|
2939
|
+
}
|
|
2940
|
+
},
|
|
2941
|
+
|
|
2942
|
+
collect: function() {
|
|
2943
|
+
// We used to simplify Floats to Ints here whenever possible, but no
|
|
2944
|
+
// longer do so in order to preserve significant figures.
|
|
2945
|
+
return this;
|
|
2946
|
+
},
|
|
2947
|
+
|
|
2948
|
+
negate: function() { return new Float(-this.n); },
|
|
2949
|
+
abs: function() { return new Float(Math.abs(this.n)); },
|
|
2950
|
+
|
|
2951
|
+
findGCD: function(factor) {
|
|
2952
|
+
if (factor instanceof Num) {
|
|
2953
|
+
return new Float(Num.findGCD(this.eval(), factor.eval())).collect();
|
|
2954
|
+
} else {
|
|
2955
|
+
return factor.findGCD(this);
|
|
2956
|
+
}
|
|
2957
|
+
},
|
|
2958
|
+
|
|
2959
|
+
// for now, assuming that exp is a Num
|
|
2960
|
+
raiseToThe: function(exp, options) {
|
|
2961
|
+
if (options && options.preciseFloats &&
|
|
2962
|
+
exp instanceof Int && exp.n > 1) {
|
|
2963
|
+
return Float.toDecimalPlaces(
|
|
2964
|
+
new Pow(this, exp).eval(),
|
|
2965
|
+
this.getDecimalPlaces() * exp.n
|
|
2966
|
+
);
|
|
2967
|
+
} else {
|
|
2968
|
+
return new Float(new Pow(this, exp).eval()).collect();
|
|
2969
|
+
}
|
|
2970
|
+
},
|
|
2971
|
+
|
|
2972
|
+
// only to be used on non-repeating decimals (e.g. user-provided)
|
|
2973
|
+
asRational: function() {
|
|
2974
|
+
var parts = this.n.toString().split(".");
|
|
2975
|
+
if (parts.length === 1) {
|
|
2976
|
+
return new Rational(this.n, 1);
|
|
2977
|
+
} else {
|
|
2978
|
+
var numerator = Number(parts.join(""));
|
|
2979
|
+
var denominator = Math.pow(10, parts[1].length);
|
|
2980
|
+
return new Rational(numerator, denominator).collect();
|
|
2981
|
+
}
|
|
2982
|
+
},
|
|
2983
|
+
|
|
2984
|
+
getDenominator: function() {
|
|
2985
|
+
return this.asRational().getDenominator();
|
|
2986
|
+
},
|
|
2987
|
+
|
|
2988
|
+
isSimple: function() { return true; }
|
|
2989
|
+
});
|
|
2990
|
+
|
|
2991
|
+
_.extend(Float, {
|
|
2992
|
+
create: function(n) { return new Float(n).addHint("entered"); },
|
|
2993
|
+
|
|
2994
|
+
// Account for floating point imprecision by explicitly controlling the
|
|
2995
|
+
// number of decimal places in common operations (e.g. +, *, ^)
|
|
2996
|
+
toDecimalPlaces: function(n, places) {
|
|
2997
|
+
return new Float(+n.toFixed(Math.min(places, 20))).collect();
|
|
2998
|
+
}
|
|
2999
|
+
});
|
|
3000
|
+
|
|
3001
|
+
// static methods and fields that are best defined on Num
|
|
3002
|
+
_.extend(Num, {
|
|
3003
|
+
negativeOne: function(hint) {
|
|
3004
|
+
if (hint === "subtract") {
|
|
3005
|
+
return Num.Sub;
|
|
3006
|
+
} else if (hint === "divide") {
|
|
3007
|
+
return Num.Div;
|
|
3008
|
+
} else {
|
|
3009
|
+
return Num.Neg;
|
|
3010
|
+
}
|
|
3011
|
+
},
|
|
3012
|
+
|
|
3013
|
+
// find the greatest common denominator
|
|
3014
|
+
findGCD: function(a, b) {
|
|
3015
|
+
var mod;
|
|
3016
|
+
|
|
3017
|
+
a = Math.abs(a);
|
|
3018
|
+
b = Math.abs(b);
|
|
3019
|
+
|
|
3020
|
+
// Euclid's method doesn't handle non-integers very well. For now
|
|
3021
|
+
// we just say we can't pull out a common factor. It might be
|
|
3022
|
+
// reasonable to do better than this in the future.
|
|
3023
|
+
if (a !== Math.floor(a) || b !== Math.floor(b)) {
|
|
3024
|
+
return 1;
|
|
3025
|
+
}
|
|
3026
|
+
|
|
3027
|
+
while (b) {
|
|
3028
|
+
mod = a % b;
|
|
3029
|
+
a = b;
|
|
3030
|
+
b = mod;
|
|
3031
|
+
}
|
|
3032
|
+
|
|
3033
|
+
return a;
|
|
3034
|
+
},
|
|
3035
|
+
|
|
3036
|
+
min: function() {
|
|
3037
|
+
return _.min(_.toArray(arguments), function(num) {
|
|
3038
|
+
return num.eval();
|
|
3039
|
+
});
|
|
3040
|
+
},
|
|
3041
|
+
|
|
3042
|
+
max: function() {
|
|
3043
|
+
return _.max(_.toArray(arguments), function(num) {
|
|
3044
|
+
return num.eval();
|
|
3045
|
+
});
|
|
3046
|
+
}
|
|
3047
|
+
});
|
|
3048
|
+
|
|
3049
|
+
Num.Neg = new Int(-1).addHint("negate");
|
|
3050
|
+
Num.Sub = new Int(-1).addHint("subtract");
|
|
3051
|
+
Num.Div = new Int(-1).addHint("divide");
|
|
3052
|
+
|
|
3053
|
+
Num.Sqrt = new Rational(1, 2).addHint("root");
|
|
3054
|
+
|
|
3055
|
+
Num.Zero = new Int(0);
|
|
3056
|
+
Num.One = new Int(1);
|
|
3057
|
+
Num.Ten = new Int(10);
|
|
3058
|
+
|
|
3059
|
+
|
|
3060
|
+
// set identities here
|
|
3061
|
+
Add.prototype.identity = Num.Zero;
|
|
3062
|
+
Mul.prototype.identity = Num.One;
|
|
3063
|
+
|
|
3064
|
+
var parseError = function(str, hash) {
|
|
3065
|
+
// return int location of parsing error
|
|
3066
|
+
throw new Error(hash.loc.first_column);
|
|
3067
|
+
};
|
|
3068
|
+
|
|
3069
|
+
// expose concrete nodes to parser scope
|
|
3070
|
+
// see http://zaach.github.io/jison/docs/#sharing-scope
|
|
3071
|
+
parser.yy = {
|
|
3072
|
+
Add: Add,
|
|
3073
|
+
Mul: Mul,
|
|
3074
|
+
Pow: Pow,
|
|
3075
|
+
Log: Log,
|
|
3076
|
+
Trig: Trig,
|
|
3077
|
+
Eq: Eq,
|
|
3078
|
+
Abs: Abs,
|
|
3079
|
+
Func: Func,
|
|
3080
|
+
Const: Const,
|
|
3081
|
+
Var: Var,
|
|
3082
|
+
Int: Int,
|
|
3083
|
+
Float: Float,
|
|
3084
|
+
parseError: parseError,
|
|
3085
|
+
|
|
3086
|
+
constants: ["e"],
|
|
3087
|
+
symbolLexer: function(symbol) {
|
|
3088
|
+
if (_.contains(parser.yy.constants, symbol)) {
|
|
3089
|
+
return "CONST";
|
|
3090
|
+
} else if (_.contains(parser.yy.functions, symbol)) {
|
|
3091
|
+
return "FUNC";
|
|
3092
|
+
} else {
|
|
3093
|
+
return "VAR";
|
|
3094
|
+
}
|
|
3095
|
+
}
|
|
3096
|
+
};
|
|
3097
|
+
|
|
3098
|
+
export const parse = function(input, options) {
|
|
3099
|
+
try {
|
|
3100
|
+
if (options && options.functions) {
|
|
3101
|
+
// reserve the symbol "i" for complex numbers
|
|
3102
|
+
parser.yy.functions = _.without(options.functions, "i");
|
|
3103
|
+
} else {
|
|
3104
|
+
parser.yy.functions = [];
|
|
3105
|
+
}
|
|
3106
|
+
|
|
3107
|
+
// If ',' is the decimal dividor in your country, replace any ','s
|
|
3108
|
+
// with '.'s.
|
|
3109
|
+
// This isn't perfect, since the output will all still have '.'s.
|
|
3110
|
+
// TODO(jack): Fix the output to have ','s in this case
|
|
3111
|
+
if (options && options.decimal_separator) {
|
|
3112
|
+
input = input.split(options.decimal_separator).join(".");
|
|
3113
|
+
}
|
|
3114
|
+
|
|
3115
|
+
var expr = parser.parse(input).completeParse();
|
|
3116
|
+
return { parsed: true, expr: expr };
|
|
3117
|
+
} catch (e) {
|
|
3118
|
+
return { parsed: false, error: e.message };
|
|
3119
|
+
}
|
|
3120
|
+
};
|
|
3121
|
+
|
|
3122
|
+
/* unit */
|
|
3123
|
+
export function Unit(symbol) {
|
|
3124
|
+
this.symbol = symbol;
|
|
3125
|
+
}
|
|
3126
|
+
Unit.prototype = new Symbol();
|
|
3127
|
+
|
|
3128
|
+
// If possible, replace unit prefixes with a multiplication.
|
|
3129
|
+
//
|
|
3130
|
+
// "g" -> Unit("g")
|
|
3131
|
+
// "kg" -> 1000 * Unit("g")
|
|
3132
|
+
var unprefixify = function(symbol) {
|
|
3133
|
+
if (_(baseUnits).has(symbol) || _(derivedUnits).has(symbol)) {
|
|
3134
|
+
return new Unit(symbol);
|
|
3135
|
+
}
|
|
3136
|
+
|
|
3137
|
+
// check for prefix
|
|
3138
|
+
var prefix = _(_(siPrefixes).keys()).find(function(testPrefix) {
|
|
3139
|
+
return new RegExp("^" + testPrefix).test(symbol);
|
|
3140
|
+
});
|
|
3141
|
+
|
|
3142
|
+
if (prefix) {
|
|
3143
|
+
var base = symbol.replace(new RegExp("^" + prefix), "");
|
|
3144
|
+
|
|
3145
|
+
// It's okay to be here if either:
|
|
3146
|
+
// * `base` is a base unit (the seven units listed in baseUnits)
|
|
3147
|
+
// * `base` is a derived unit which allows prefixes
|
|
3148
|
+
//
|
|
3149
|
+
// Otherwise, we're trying to parse a unit label which is not
|
|
3150
|
+
// allowed (mwk, mBTU, etc).
|
|
3151
|
+
if (_(baseUnits).has(base) ||
|
|
3152
|
+
(derivedUnits[base] &&
|
|
3153
|
+
derivedUnits[base].prefixes === hasPrefixes)) {
|
|
3154
|
+
|
|
3155
|
+
return new Mul(siPrefixes[prefix], new Unit(base));
|
|
3156
|
+
} else {
|
|
3157
|
+
throw new Error(base + " does not allow prefixes");
|
|
3158
|
+
}
|
|
3159
|
+
} else {
|
|
3160
|
+
return new Unit(symbol);
|
|
3161
|
+
}
|
|
3162
|
+
};
|
|
3163
|
+
|
|
3164
|
+
export const unitParse = function(input) {
|
|
3165
|
+
try {
|
|
3166
|
+
var parseResult = unitParser.parse(input);
|
|
3167
|
+
|
|
3168
|
+
// parseResult looks like:
|
|
3169
|
+
// {
|
|
3170
|
+
// magnitude: "5",
|
|
3171
|
+
// unit: {
|
|
3172
|
+
// num: [
|
|
3173
|
+
// { name: "s", pow: 2 }
|
|
3174
|
+
// ],
|
|
3175
|
+
// denom: [
|
|
3176
|
+
// { name: "kg", pow: 1 }
|
|
3177
|
+
// ]
|
|
3178
|
+
// }
|
|
3179
|
+
// }
|
|
3180
|
+
//
|
|
3181
|
+
// denom is optionally null
|
|
3182
|
+
|
|
3183
|
+
var unitArray = [];
|
|
3184
|
+
|
|
3185
|
+
_(parseResult.unit.num).each(function(unitSpec) {
|
|
3186
|
+
unitArray.push(
|
|
3187
|
+
new Pow(unprefixify(unitSpec.name), new Int(unitSpec.pow))
|
|
3188
|
+
);
|
|
3189
|
+
});
|
|
3190
|
+
|
|
3191
|
+
_(parseResult.unit.denom).each(function(unitSpec) {
|
|
3192
|
+
unitArray.push(
|
|
3193
|
+
new Pow(unprefixify(unitSpec.name), new Int(-1 * unitSpec.pow))
|
|
3194
|
+
);
|
|
3195
|
+
});
|
|
3196
|
+
|
|
3197
|
+
var unit = new Mul(unitArray).flatten();
|
|
3198
|
+
|
|
3199
|
+
if (parseResult.type === "unitMagnitude") {
|
|
3200
|
+
// in the first case we have a magnitude coefficient as well as the
|
|
3201
|
+
// unit itself.
|
|
3202
|
+
var coefArray =
|
|
3203
|
+
[new Float(+parseResult.magnitude)].concat(unitArray);
|
|
3204
|
+
var expr = new Mul(coefArray);
|
|
3205
|
+
return {
|
|
3206
|
+
parsed: true,
|
|
3207
|
+
unit: unit,
|
|
3208
|
+
expr: expr,
|
|
3209
|
+
coefficient: parseResult.magnitude,
|
|
3210
|
+
type: parseResult.type
|
|
3211
|
+
};
|
|
3212
|
+
} else {
|
|
3213
|
+
|
|
3214
|
+
// in the second case it's just the unit with no magnitude.
|
|
3215
|
+
return {
|
|
3216
|
+
parsed: true,
|
|
3217
|
+
unit: unit,
|
|
3218
|
+
type: parseResult.type,
|
|
3219
|
+
};
|
|
3220
|
+
}
|
|
3221
|
+
} catch (e) {
|
|
3222
|
+
return { parsed: false, error: e.message };
|
|
3223
|
+
}
|
|
3224
|
+
};
|
|
3225
|
+
|
|
3226
|
+
_.extend(Unit.prototype, {
|
|
3227
|
+
func: Unit,
|
|
3228
|
+
args: function() { return [this.symbol]; },
|
|
3229
|
+
recurse: function() { return this; },
|
|
3230
|
+
|
|
3231
|
+
eval: function(vars, options) {
|
|
3232
|
+
// This is called when comparing units. A unit doesn't affect the
|
|
3233
|
+
// numerical value of its coefficient, so this needs to be 1.
|
|
3234
|
+
//
|
|
3235
|
+
// On the other hand, things must not evaluate to the same thing if
|
|
3236
|
+
// they don't have the same type. I believe that's also true - form is
|
|
3237
|
+
// checked before numerical equivalence. I do not know where, though.
|
|
3238
|
+
// However, there are a couple tests checking this.
|
|
3239
|
+
return 1;
|
|
3240
|
+
},
|
|
3241
|
+
|
|
3242
|
+
getUnits: function() { return [{ unit: this.symbol, pow: 1 }]; },
|
|
3243
|
+
|
|
3244
|
+
codegen: function() { return "1"; },
|
|
3245
|
+
|
|
3246
|
+
print: function() { return this.symbol; },
|
|
3247
|
+
|
|
3248
|
+
tex: function() { return this.symbol; },
|
|
3249
|
+
|
|
3250
|
+
// Simplify units by replacing prefixes with multiplication
|
|
3251
|
+
collect: function(options) {
|
|
3252
|
+
if (_(baseUnits).has(this.symbol)) {
|
|
3253
|
+
return this;
|
|
3254
|
+
} else if (_(derivedUnits).has(this.symbol)) {
|
|
3255
|
+
return derivedUnits[this.symbol].conversion;
|
|
3256
|
+
} else {
|
|
3257
|
+
throw new Error("could not understand unit: " + this.symbol);
|
|
3258
|
+
}
|
|
3259
|
+
},
|
|
3260
|
+
});
|
|
3261
|
+
|
|
3262
|
+
var baseUnits = {
|
|
3263
|
+
m: new Unit("m"),
|
|
3264
|
+
// Note: kg is the SI base unit but we use g for consistency
|
|
3265
|
+
g: new Unit("g"),
|
|
3266
|
+
s: new Unit("s"),
|
|
3267
|
+
A: new Unit("A"),
|
|
3268
|
+
K: new Unit("K"),
|
|
3269
|
+
mol: new Unit("mol"),
|
|
3270
|
+
cd: new Unit("cd"),
|
|
3271
|
+
};
|
|
3272
|
+
|
|
3273
|
+
var siPrefixes = {
|
|
3274
|
+
a: new Pow(new Int(10), new Int(-18)),
|
|
3275
|
+
f: new Pow(new Int(10), new Int(-15)),
|
|
3276
|
+
p: new Pow(new Int(10), new Int(-12)),
|
|
3277
|
+
n: new Pow(new Int(10), new Int(-9)),
|
|
3278
|
+
u: new Pow(new Int(10), new Int(-6)),
|
|
3279
|
+
m: new Pow(new Int(10), new Int(-3)),
|
|
3280
|
+
c: new Pow(new Int(10), new Int(-2)),
|
|
3281
|
+
d: new Pow(new Int(10), new Int(-1)),
|
|
3282
|
+
da: new Int(10),
|
|
3283
|
+
h: new Pow(new Int(10), new Int(2)),
|
|
3284
|
+
k: new Pow(new Int(10), new Int(3)),
|
|
3285
|
+
M: new Pow(new Int(10), new Int(6)),
|
|
3286
|
+
G: new Pow(new Int(10), new Int(9)),
|
|
3287
|
+
T: new Pow(new Int(10), new Int(12)),
|
|
3288
|
+
P: new Pow(new Int(10), new Int(15)),
|
|
3289
|
+
E: new Pow(new Int(10), new Int(18)),
|
|
3290
|
+
// http://en.wikipedia.org/wiki/Metric_prefix#.22Hella.22_prefix_proposal
|
|
3291
|
+
hella: new Pow(new Int(10), new Int(27)),
|
|
3292
|
+
};
|
|
3293
|
+
|
|
3294
|
+
// Use these two values to mark a unit as either SI-prefixable or not.
|
|
3295
|
+
var hasPrefixes = {};
|
|
3296
|
+
var hasntPrefixes = {};
|
|
3297
|
+
|
|
3298
|
+
var makeAlias = function(str, prefixes) {
|
|
3299
|
+
var splits = str.split("|");
|
|
3300
|
+
var coefficientStr = splits[0].trim();
|
|
3301
|
+
var unitsStr = splits[1].trim();
|
|
3302
|
+
|
|
3303
|
+
var coefficient = Num.One;
|
|
3304
|
+
if (coefficientStr !== "") {
|
|
3305
|
+
coefficient = parse(coefficientStr).expr;
|
|
3306
|
+
}
|
|
3307
|
+
|
|
3308
|
+
var numdenomStr = unitsStr.split("/");
|
|
3309
|
+
var numdenom = [coefficient];
|
|
3310
|
+
|
|
3311
|
+
if (numdenomStr[0]) {
|
|
3312
|
+
numdenomStr[0]
|
|
3313
|
+
.split(" ")
|
|
3314
|
+
.filter(function(x) {
|
|
3315
|
+
return x !== "";
|
|
3316
|
+
}).map(function(x) {
|
|
3317
|
+
numdenom.push(new Unit(x));
|
|
3318
|
+
});
|
|
3319
|
+
}
|
|
3320
|
+
|
|
3321
|
+
if (numdenomStr[1]) {
|
|
3322
|
+
numdenomStr[1]
|
|
3323
|
+
.split(" ")
|
|
3324
|
+
.filter(function(x) {
|
|
3325
|
+
return x !== "";
|
|
3326
|
+
}).map(function(x) {
|
|
3327
|
+
numdenom.push(new Pow(new Unit(x), Num.Div));
|
|
3328
|
+
});
|
|
3329
|
+
}
|
|
3330
|
+
|
|
3331
|
+
return {
|
|
3332
|
+
conversion: new Mul(numdenom),
|
|
3333
|
+
prefixes: prefixes,
|
|
3334
|
+
};
|
|
3335
|
+
};
|
|
3336
|
+
|
|
3337
|
+
// This is a mapping of derived units (or different names for a unit) to their
|
|
3338
|
+
// definitions. For example, an inch is defined as 0.0254 m.
|
|
3339
|
+
//
|
|
3340
|
+
// Definitions don't need to be in terms of base units. For example, tsp is
|
|
3341
|
+
// defined in terms of tbsp (which is defined in terms of cup -> gal -> L ->
|
|
3342
|
+
// m^3). However, units must get simpler. I.e. there's no loop checking.
|
|
3343
|
+
//
|
|
3344
|
+
// makeAlias takes two parameters:
|
|
3345
|
+
// * a string specifying the simplification to perform
|
|
3346
|
+
// - a required pipe separates the constant factor from the base units
|
|
3347
|
+
// - the constant factor is parsed by KAS
|
|
3348
|
+
// - the base units are in a simple format which disallows exponents and
|
|
3349
|
+
// requires multiplicands to be space-separated ("m m" rather than "m^2)
|
|
3350
|
+
// with an optional "/" separating numerator and denominator
|
|
3351
|
+
// - prefixes are not allowed to be used in the converted to units
|
|
3352
|
+
// (note that this restriction, the format of the string, and the choice to
|
|
3353
|
+
// use a string in the first place are made out of laziness to minimize
|
|
3354
|
+
// both typing and parsing)
|
|
3355
|
+
// * a boolean specifying whether or not it's acceptable to use SI units
|
|
3356
|
+
//
|
|
3357
|
+
// Where possible, these units are taken from "The International System of
|
|
3358
|
+
// Units (SI)" 8th edition (2006).
|
|
3359
|
+
var derivedUnits = {
|
|
3360
|
+
// mass
|
|
3361
|
+
// The atomic mass unit / dalton.
|
|
3362
|
+
Da: makeAlias("1.6605388628 x 10^-24 | g", hasPrefixes),
|
|
3363
|
+
u: makeAlias("| Da", hasntPrefixes),
|
|
3364
|
+
|
|
3365
|
+
// length
|
|
3366
|
+
"meter": makeAlias("| m", hasntPrefixes),
|
|
3367
|
+
"meters": makeAlias("| m", hasntPrefixes),
|
|
3368
|
+
"in": makeAlias("254 / 10000 | m", hasntPrefixes),
|
|
3369
|
+
"ft": makeAlias("3048 / 10000 | m", hasntPrefixes),
|
|
3370
|
+
"yd": makeAlias("9144 / 10000 | m", hasntPrefixes),
|
|
3371
|
+
"mi": makeAlias("1609344 / 1000 | m", hasntPrefixes),
|
|
3372
|
+
"ly": makeAlias("9.4607 x 10^15 | m", hasntPrefixes),
|
|
3373
|
+
"nmi": makeAlias("1852 | m", hasntPrefixes),
|
|
3374
|
+
"Å": makeAlias("10^-10 | m", hasntPrefixes),
|
|
3375
|
+
"pc": makeAlias("3.0857 x 10^16 | m", hasntPrefixes),
|
|
3376
|
+
|
|
3377
|
+
// time
|
|
3378
|
+
"min": makeAlias("60 | s", hasntPrefixes),
|
|
3379
|
+
"hr": makeAlias("3600 | s", hasntPrefixes),
|
|
3380
|
+
"sec": makeAlias("| s", hasntPrefixes),
|
|
3381
|
+
// TODO(joel) make day work
|
|
3382
|
+
"day": makeAlias("86400 | s", hasntPrefixes),
|
|
3383
|
+
"wk": makeAlias("604800 | s", hasntPrefixes),
|
|
3384
|
+
"fortnight": makeAlias("14 | day", hasntPrefixes),
|
|
3385
|
+
"shake": makeAlias("10^-8 | s", hasntPrefixes),
|
|
3386
|
+
"olympiad": makeAlias("126200000 | s", hasntPrefixes),
|
|
3387
|
+
|
|
3388
|
+
// temperature
|
|
3389
|
+
"°C": makeAlias("1 | K", hasntPrefixes),
|
|
3390
|
+
"°F": makeAlias("5/9 | K", hasntPrefixes),
|
|
3391
|
+
"°R": makeAlias("5/9 | K", hasntPrefixes),
|
|
3392
|
+
|
|
3393
|
+
// electric charge
|
|
3394
|
+
"e": makeAlias("1.6021765314 x 10^-19 | C", hasntPrefixes),
|
|
3395
|
+
|
|
3396
|
+
// speed
|
|
3397
|
+
"c": makeAlias("299792458 | m / s", hasntPrefixes),
|
|
3398
|
+
"kn": makeAlias("514/1000 | m / s", hasntPrefixes),
|
|
3399
|
+
"kt": makeAlias("| kn", hasntPrefixes),
|
|
3400
|
+
"knot": makeAlias("| kn", hasntPrefixes),
|
|
3401
|
+
|
|
3402
|
+
// energy
|
|
3403
|
+
"J": makeAlias("| N m", hasPrefixes),
|
|
3404
|
+
"BTU": makeAlias("1060 | J", hasntPrefixes),
|
|
3405
|
+
"cal": makeAlias("4184 / 1000 | J", hasPrefixes),
|
|
3406
|
+
"eV": makeAlias("1.602176514 x 10^-19 | J", hasPrefixes),
|
|
3407
|
+
"erg": makeAlias("10^−7 | J", hasPrefixes),
|
|
3408
|
+
|
|
3409
|
+
// power
|
|
3410
|
+
"W": makeAlias("| J / s", hasPrefixes),
|
|
3411
|
+
"H-e": makeAlias("80 | W", hasntPrefixes),
|
|
3412
|
+
|
|
3413
|
+
// force
|
|
3414
|
+
"N": makeAlias("1000 | g m / s s", hasPrefixes),
|
|
3415
|
+
// "lb": makeAlias("4448 / 1000 | N", hasntPrefixes),
|
|
3416
|
+
// 4.4482216152605
|
|
3417
|
+
"lb": makeAlias("4448221615 / 1000000000 | N", hasntPrefixes),
|
|
3418
|
+
"dyn": makeAlias("10^-5 | N", hasntPrefixes),
|
|
3419
|
+
|
|
3420
|
+
// pressure
|
|
3421
|
+
"Pa": makeAlias("1 | N / m m m", hasPrefixes),
|
|
3422
|
+
"bar": makeAlias("10^5 | Pa", hasPrefixes),
|
|
3423
|
+
"㏔": makeAlias("1/1000 | bar", hasntPrefixes),
|
|
3424
|
+
"㍴": makeAlias("| bar", hasntPrefixes),
|
|
3425
|
+
"atm": makeAlias("101325 | Pa", hasntPrefixes),
|
|
3426
|
+
"Torr": makeAlias("1/760 | atm", hasntPrefixes),
|
|
3427
|
+
"mmHg": makeAlias("| Torr", hasntPrefixes),
|
|
3428
|
+
|
|
3429
|
+
// area
|
|
3430
|
+
"ha": makeAlias("10^4 | m m", hasntPrefixes),
|
|
3431
|
+
"b": makeAlias("10^−28 | m m", hasPrefixes),
|
|
3432
|
+
"barn": makeAlias("| b", hasPrefixes),
|
|
3433
|
+
"acre": makeAlias("4046.87 | m m", hasntPrefixes),
|
|
3434
|
+
"skilodge": makeAlias("10^-31 | m m", hasntPrefixes),
|
|
3435
|
+
"outhouse": makeAlias("10^-34 | m m", hasntPrefixes),
|
|
3436
|
+
"shed": makeAlias("10^-52 | m m", hasntPrefixes),
|
|
3437
|
+
|
|
3438
|
+
// volume
|
|
3439
|
+
"L": makeAlias("1/1000 | m m m", hasPrefixes),
|
|
3440
|
+
"gal": makeAlias("3785/1000 | L", hasPrefixes),
|
|
3441
|
+
"cup": makeAlias("1/16 | gal", hasntPrefixes),
|
|
3442
|
+
"qt": makeAlias("1/4 | gal", hasntPrefixes),
|
|
3443
|
+
"quart": makeAlias("| qt", hasntPrefixes),
|
|
3444
|
+
"p": makeAlias("1/8 | gal", hasntPrefixes),
|
|
3445
|
+
"pt": makeAlias("| p", hasntPrefixes),
|
|
3446
|
+
"pint": makeAlias("| p", hasntPrefixes),
|
|
3447
|
+
"fl oz": makeAlias("1/8 | cup", hasntPrefixes),
|
|
3448
|
+
"fl. oz.": makeAlias("1/8 | cup", hasntPrefixes),
|
|
3449
|
+
"tbsp": makeAlias("1/16 | cup", hasntPrefixes),
|
|
3450
|
+
"tsp": makeAlias("1/3 | tbsp", hasntPrefixes),
|
|
3451
|
+
|
|
3452
|
+
// rotational
|
|
3453
|
+
// "rad":
|
|
3454
|
+
"rev": makeAlias("2 pi | rad", hasntPrefixes),
|
|
3455
|
+
"deg": makeAlias("180 pi | rad", hasntPrefixes),
|
|
3456
|
+
"°": makeAlias("| deg", hasntPrefixes),
|
|
3457
|
+
"arcminute": makeAlias("1/60 | deg", hasntPrefixes),
|
|
3458
|
+
"arcsec": makeAlias("1/3600 | deg", hasntPrefixes),
|
|
3459
|
+
|
|
3460
|
+
// dimensionless
|
|
3461
|
+
// "B": makeAlias("10 | dB", hasntPrefixes), // XXX danger - logarithmic
|
|
3462
|
+
// "dB"
|
|
3463
|
+
// "nP"
|
|
3464
|
+
"Hu": makeAlias("1000 | dB", hasPrefixes),
|
|
3465
|
+
"dozen": makeAlias("12 |", hasntPrefixes),
|
|
3466
|
+
// XXX
|
|
3467
|
+
"mol": makeAlias("6.0221412927 x 10^23 |", hasPrefixes),
|
|
3468
|
+
"%": makeAlias("1/100 |", hasntPrefixes),
|
|
3469
|
+
"percent": makeAlias("| %", hasntPrefixes),
|
|
3470
|
+
"ppm": makeAlias("1/1000000 |", hasntPrefixes),
|
|
3471
|
+
|
|
3472
|
+
// electric / magnetic
|
|
3473
|
+
"V": makeAlias("1000 | g m m / s s C", hasPrefixes),
|
|
3474
|
+
"C": makeAlias("| A s", hasPrefixes),
|
|
3475
|
+
"ampere": makeAlias("| A", hasntPrefixes),
|
|
3476
|
+
"Ω": makeAlias("| V / A", hasPrefixes),
|
|
3477
|
+
"ohm": makeAlias("| Ω", hasntPrefixes),
|
|
3478
|
+
"F": makeAlias("| C / V", hasPrefixes),
|
|
3479
|
+
"H": makeAlias("| ohm s", hasPrefixes),
|
|
3480
|
+
"T": makeAlias("1000 | g / C s", hasPrefixes),
|
|
3481
|
+
"Wb": makeAlias("1000 | g m m / C s", hasPrefixes),
|
|
3482
|
+
|
|
3483
|
+
// photometry
|
|
3484
|
+
// TODO not sure this is right
|
|
3485
|
+
"lm": makeAlias("pi x 10^4 | cd / m m", hasntPrefixes),
|
|
3486
|
+
"lx": makeAlias("| lm / m m", hasntPrefixes),
|
|
3487
|
+
"nit": makeAlias("| cd / m m", hasntPrefixes),
|
|
3488
|
+
"sb": makeAlias("10^4 | cd / m m", hasntPrefixes),
|
|
3489
|
+
"stilb": makeAlias("1 | sb", hasntPrefixes),
|
|
3490
|
+
"apostilb": makeAlias("1 / pi x 10^(-4) | sb", hasntPrefixes),
|
|
3491
|
+
"blondel": makeAlias("| apostilb", hasntPrefixes),
|
|
3492
|
+
"asb": makeAlias("| apostilb", hasntPrefixes),
|
|
3493
|
+
"la": makeAlias("| lm", hasntPrefixes),
|
|
3494
|
+
"Lb": makeAlias("| lm", hasntPrefixes),
|
|
3495
|
+
"sk": makeAlias("10^-7 | lm", hasntPrefixes),
|
|
3496
|
+
"skot": makeAlias("| sk", hasntPrefixes),
|
|
3497
|
+
"bril": makeAlias("10^-11 | lm", hasntPrefixes),
|
|
3498
|
+
|
|
3499
|
+
// other
|
|
3500
|
+
"Hz": makeAlias("| / s", hasPrefixes),
|
|
3501
|
+
};
|
|
3502
|
+
|
|
3503
|
+
export const Zero = Num.Zero;
|
|
3504
|
+
export const One = Num.One;
|