@khanacademy/kas 1.0.0 → 2.0.0
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/dist/es/index.js +5979 -0
- package/dist/es/index.js.map +1 -0
- package/dist/index.js +1449 -399
- package/dist/index.js.map +1 -1
- package/package.json +5 -12
package/dist/index.js
CHANGED
|
@@ -1,15 +1,101 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var perseusUtils = require('@khanacademy/perseus-utils');
|
|
6
|
+
var _ = require('underscore');
|
|
7
|
+
|
|
8
|
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
|
9
|
+
|
|
10
|
+
var ___default = /*#__PURE__*/_interopDefaultCompat(_);
|
|
11
|
+
|
|
12
|
+
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
13
|
+
// version number during the release build.
|
|
14
|
+
// In dev, you'll never see the version number.
|
|
4
15
|
|
|
5
16
|
const libName = "@khanacademy/kas";
|
|
6
|
-
const libVersion = "
|
|
7
|
-
addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
17
|
+
const libVersion = "2.0.0";
|
|
18
|
+
perseusUtils.addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
19
|
+
|
|
20
|
+
// this is a @generated file
|
|
21
|
+
|
|
22
|
+
/* parser generated by jison 0.4.15 */
|
|
23
|
+
/*
|
|
24
|
+
Returns a Parser object of the following structure:
|
|
25
|
+
|
|
26
|
+
Parser: {
|
|
27
|
+
yy: {}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
Parser.prototype: {
|
|
31
|
+
yy: {},
|
|
32
|
+
trace: function(),
|
|
33
|
+
symbols_: {associative list: name ==> number},
|
|
34
|
+
terminals_: {associative list: number ==> name},
|
|
35
|
+
productions_: [...],
|
|
36
|
+
performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate, $$, _$),
|
|
37
|
+
table: [...],
|
|
38
|
+
defaultActions: {...},
|
|
39
|
+
parseError: function(str, hash),
|
|
40
|
+
parse: function(input),
|
|
41
|
+
|
|
42
|
+
lexer: {
|
|
43
|
+
EOF: 1,
|
|
44
|
+
parseError: function(str, hash),
|
|
45
|
+
setInput: function(input),
|
|
46
|
+
input: function(),
|
|
47
|
+
unput: function(str),
|
|
48
|
+
more: function(),
|
|
49
|
+
less: function(n),
|
|
50
|
+
pastInput: function(),
|
|
51
|
+
upcomingInput: function(),
|
|
52
|
+
showPosition: function(),
|
|
53
|
+
test_match: function(regex_match_array, rule_index),
|
|
54
|
+
next: function(),
|
|
55
|
+
lex: function(),
|
|
56
|
+
begin: function(condition),
|
|
57
|
+
popState: function(),
|
|
58
|
+
_currentRules: function(),
|
|
59
|
+
topState: function(),
|
|
60
|
+
pushState: function(condition),
|
|
61
|
+
|
|
62
|
+
options: {
|
|
63
|
+
ranges: boolean (optional: true ==> token location info will include a .range[] member)
|
|
64
|
+
flex: boolean (optional: true ==> flex-like lexing behaviour where the rules are tested exhaustively to find the longest match)
|
|
65
|
+
backtrack_lexer: boolean (optional: true ==> lexer regexes are tested in order and for each matching regex the action code is invoked; the lexer terminates the scan when a token is returned by the action code)
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
performAction: function(yy, yy_, $avoiding_name_collisions, YY_START),
|
|
69
|
+
rules: [...],
|
|
70
|
+
conditions: {associative list: name ==> set},
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
token location info (@$, _$, etc.): {
|
|
76
|
+
first_line: n,
|
|
77
|
+
last_line: n,
|
|
78
|
+
first_column: n,
|
|
79
|
+
last_column: n,
|
|
80
|
+
range: [start_number, end_number] (where the numbers are indexes into the input string, regular zero-based)
|
|
81
|
+
}
|
|
82
|
+
|
|
8
83
|
|
|
84
|
+
the parseError function receives a 'hash' object with these members for lexer and parser errors: {
|
|
85
|
+
text: (matched text)
|
|
86
|
+
token: (the produced terminal token, if any)
|
|
87
|
+
line: (yylineno)
|
|
88
|
+
}
|
|
89
|
+
while parser (grammar) errors will also provide these members, i.e. parser errors deliver a superset of attributes: {
|
|
90
|
+
loc: (yylloc)
|
|
91
|
+
expected: (string describing the set of expected tokens)
|
|
92
|
+
recoverable: (boolean: TRUE when the parser has a error recovery rule available for this particular error)
|
|
93
|
+
}
|
|
94
|
+
*/
|
|
9
95
|
var parser$1 = function () {
|
|
10
|
-
var o = function
|
|
11
|
-
for (
|
|
12
|
-
return
|
|
96
|
+
var o = function (k, v, o, l) {
|
|
97
|
+
for (o = o || {}, l = k.length; l--; o[k[l]] = v);
|
|
98
|
+
return o;
|
|
13
99
|
},
|
|
14
100
|
$V0 = [1, 11],
|
|
15
101
|
$V1 = [1, 9],
|
|
@@ -55,7 +141,9 @@ var parser$1 = function () {
|
|
|
55
141
|
20: "NEG"
|
|
56
142
|
},
|
|
57
143
|
productions_: [0, [3, 3], [3, 2], [4, 3], [4, 1], [5, 3], [5, 1], [10, 3], [10, 2], [10, 1], [12, 3], [12, 1], [14, 1], [7, 1], [7, 1], [16, 1], [9, 2], [9, 1]],
|
|
58
|
-
performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate
|
|
144
|
+
performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate /* action[1] */, $$ /* vstack */, _$ /* lstack */) {
|
|
145
|
+
/* this == yyval */
|
|
146
|
+
|
|
59
147
|
var $0 = $$.length - 1;
|
|
60
148
|
switch (yystate) {
|
|
61
149
|
case 1:
|
|
@@ -240,6 +328,7 @@ var parser$1 = function () {
|
|
|
240
328
|
} else {
|
|
241
329
|
this.parseError = Object.getPrototypeOf(this).parseError;
|
|
242
330
|
}
|
|
331
|
+
//_token_stack:
|
|
243
332
|
function lex() {
|
|
244
333
|
var token;
|
|
245
334
|
token = lexer.lex() || EOF;
|
|
@@ -339,6 +428,7 @@ var parser$1 = function () {
|
|
|
339
428
|
return true;
|
|
340
429
|
}
|
|
341
430
|
};
|
|
431
|
+
/* generated by jison-lex 0.3.4 */
|
|
342
432
|
var lexer = function () {
|
|
343
433
|
var lexer = {
|
|
344
434
|
EOF: 1,
|
|
@@ -349,6 +439,7 @@ var parser$1 = function () {
|
|
|
349
439
|
throw new Error(str);
|
|
350
440
|
}
|
|
351
441
|
},
|
|
442
|
+
// resets the lexer, sets new input
|
|
352
443
|
setInput: function (input, yy) {
|
|
353
444
|
this.yy = yy || this.yy || {};
|
|
354
445
|
this._input = input;
|
|
@@ -368,6 +459,7 @@ var parser$1 = function () {
|
|
|
368
459
|
this.offset = 0;
|
|
369
460
|
return this;
|
|
370
461
|
},
|
|
462
|
+
// consumes and returns one char from the input
|
|
371
463
|
input: function () {
|
|
372
464
|
var ch = this._input[0];
|
|
373
465
|
this.yytext += ch;
|
|
@@ -388,11 +480,13 @@ var parser$1 = function () {
|
|
|
388
480
|
this._input = this._input.slice(1);
|
|
389
481
|
return ch;
|
|
390
482
|
},
|
|
483
|
+
// unshifts one char (or a string) into the input
|
|
391
484
|
unput: function (ch) {
|
|
392
485
|
var len = ch.length;
|
|
393
486
|
var lines = ch.split(/(?:\r\n?|\n)/g);
|
|
394
487
|
this._input = ch + this._input;
|
|
395
488
|
this.yytext = this.yytext.substr(0, this.yytext.length - len);
|
|
489
|
+
//this.yyleng -= len;
|
|
396
490
|
this.offset -= len;
|
|
397
491
|
var oldLines = this.match.split(/(?:\r\n?|\n)/g);
|
|
398
492
|
this.match = this.match.substr(0, this.match.length - 1);
|
|
@@ -413,10 +507,12 @@ var parser$1 = function () {
|
|
|
413
507
|
this.yyleng = this.yytext.length;
|
|
414
508
|
return this;
|
|
415
509
|
},
|
|
510
|
+
// When called from action, caches matched text and appends it on next action
|
|
416
511
|
more: function () {
|
|
417
512
|
this._more = true;
|
|
418
513
|
return this;
|
|
419
514
|
},
|
|
515
|
+
// When called from action, signals the lexer that this rule fails to match the input, so the next matching rule (regex) should be tested instead.
|
|
420
516
|
reject: function () {
|
|
421
517
|
if (this.options.backtrack_lexer) {
|
|
422
518
|
this._backtrack = true;
|
|
@@ -429,13 +525,16 @@ var parser$1 = function () {
|
|
|
429
525
|
}
|
|
430
526
|
return this;
|
|
431
527
|
},
|
|
528
|
+
// retain first n characters of the match
|
|
432
529
|
less: function (n) {
|
|
433
530
|
this.unput(this.match.slice(n));
|
|
434
531
|
},
|
|
532
|
+
// displays already matched input, i.e. for error messages
|
|
435
533
|
pastInput: function () {
|
|
436
534
|
var past = this.matched.substr(0, this.matched.length - this.match.length);
|
|
437
535
|
return (past.length > 20 ? '...' : '') + past.substr(-20).replace(/\n/g, "");
|
|
438
536
|
},
|
|
537
|
+
// displays upcoming input, i.e. for error messages
|
|
439
538
|
upcomingInput: function () {
|
|
440
539
|
var next = this.match;
|
|
441
540
|
if (next.length < 20) {
|
|
@@ -443,14 +542,17 @@ var parser$1 = function () {
|
|
|
443
542
|
}
|
|
444
543
|
return (next.substr(0, 20) + (next.length > 20 ? '...' : '')).replace(/\n/g, "");
|
|
445
544
|
},
|
|
545
|
+
// displays the character position where the lexing error occurred, i.e. for error messages
|
|
446
546
|
showPosition: function () {
|
|
447
547
|
var pre = this.pastInput();
|
|
448
548
|
var c = new Array(pre.length + 1).join("-");
|
|
449
549
|
return pre + this.upcomingInput() + "\n" + c + "^";
|
|
450
550
|
},
|
|
551
|
+
// test the lexed token: return FALSE when not a match, otherwise return token
|
|
451
552
|
test_match: function (match, indexed_rule) {
|
|
452
553
|
var token, lines, backup;
|
|
453
554
|
if (this.options.backtrack_lexer) {
|
|
555
|
+
// save context
|
|
454
556
|
backup = {
|
|
455
557
|
yylineno: this.yylineno,
|
|
456
558
|
yylloc: {
|
|
@@ -503,13 +605,15 @@ var parser$1 = function () {
|
|
|
503
605
|
if (token) {
|
|
504
606
|
return token;
|
|
505
607
|
} else if (this._backtrack) {
|
|
608
|
+
// recover context
|
|
506
609
|
for (var k in backup) {
|
|
507
610
|
this[k] = backup[k];
|
|
508
611
|
}
|
|
509
|
-
return false;
|
|
612
|
+
return false; // rule action called reject() implying the next rule should be tested instead.
|
|
510
613
|
}
|
|
511
614
|
return false;
|
|
512
615
|
},
|
|
616
|
+
// return next match in input
|
|
513
617
|
next: function () {
|
|
514
618
|
if (this.done) {
|
|
515
619
|
return this.EOF;
|
|
@@ -534,8 +638,9 @@ var parser$1 = function () {
|
|
|
534
638
|
return token;
|
|
535
639
|
} else if (this._backtrack) {
|
|
536
640
|
match = false;
|
|
537
|
-
continue;
|
|
641
|
+
continue; // rule action called reject() implying a rule MISmatch.
|
|
538
642
|
} else {
|
|
643
|
+
// else: this is a lexer rule which consumes input without producing a token (e.g. whitespace)
|
|
539
644
|
return false;
|
|
540
645
|
}
|
|
541
646
|
} else if (!this.options.flex) {
|
|
@@ -548,6 +653,7 @@ var parser$1 = function () {
|
|
|
548
653
|
if (token !== false) {
|
|
549
654
|
return token;
|
|
550
655
|
}
|
|
656
|
+
// else: this is a lexer rule which consumes input without producing a token (e.g. whitespace)
|
|
551
657
|
return false;
|
|
552
658
|
}
|
|
553
659
|
if (this._input === "") {
|
|
@@ -560,6 +666,7 @@ var parser$1 = function () {
|
|
|
560
666
|
});
|
|
561
667
|
}
|
|
562
668
|
},
|
|
669
|
+
// return next match that has a token
|
|
563
670
|
lex: function lex() {
|
|
564
671
|
var r = this.next();
|
|
565
672
|
if (r) {
|
|
@@ -568,9 +675,11 @@ var parser$1 = function () {
|
|
|
568
675
|
return this.lex();
|
|
569
676
|
}
|
|
570
677
|
},
|
|
678
|
+
// activates a new lexer condition state (pushes the new lexer condition state onto the condition stack)
|
|
571
679
|
begin: function begin(condition) {
|
|
572
680
|
this.conditionStack.push(condition);
|
|
573
681
|
},
|
|
682
|
+
// pop the previously active lexer condition state off the condition stack
|
|
574
683
|
popState: function popState() {
|
|
575
684
|
var n = this.conditionStack.length - 1;
|
|
576
685
|
if (n > 0) {
|
|
@@ -579,6 +688,7 @@ var parser$1 = function () {
|
|
|
579
688
|
return this.conditionStack[0];
|
|
580
689
|
}
|
|
581
690
|
},
|
|
691
|
+
// produce the lexer rule set which is active for the currently active lexer condition state
|
|
582
692
|
_currentRules: function _currentRules() {
|
|
583
693
|
if (this.conditionStack.length && this.conditionStack[this.conditionStack.length - 1]) {
|
|
584
694
|
return this.conditions[this.conditionStack[this.conditionStack.length - 1]].rules;
|
|
@@ -586,6 +696,7 @@ var parser$1 = function () {
|
|
|
586
696
|
return this.conditions["INITIAL"].rules;
|
|
587
697
|
}
|
|
588
698
|
},
|
|
699
|
+
// return the currently active lexer condition state; when an index argument is provided it produces the N-th previous condition state, if available
|
|
589
700
|
topState: function topState(n) {
|
|
590
701
|
n = this.conditionStack.length - 1 - Math.abs(n || 0);
|
|
591
702
|
if (n >= 0) {
|
|
@@ -594,9 +705,11 @@ var parser$1 = function () {
|
|
|
594
705
|
return "INITIAL";
|
|
595
706
|
}
|
|
596
707
|
},
|
|
708
|
+
// alias for begin(condition)
|
|
597
709
|
pushState: function pushState(condition) {
|
|
598
710
|
this.begin(condition);
|
|
599
711
|
},
|
|
712
|
+
// return the number of states currently on the stack
|
|
600
713
|
stateStackSize: function stateStackSize() {
|
|
601
714
|
return this.conditionStack.length;
|
|
602
715
|
},
|
|
@@ -628,6 +741,7 @@ var parser$1 = function () {
|
|
|
628
741
|
case 11:
|
|
629
742
|
return 17;
|
|
630
743
|
case 12:
|
|
744
|
+
/* skip whitespace */
|
|
631
745
|
break;
|
|
632
746
|
case 13:
|
|
633
747
|
return 6;
|
|
@@ -653,10 +767,85 @@ var parser$1 = function () {
|
|
|
653
767
|
}();
|
|
654
768
|
const unitParser = parser$1;
|
|
655
769
|
|
|
770
|
+
// This is a @generated file
|
|
771
|
+
|
|
772
|
+
/* parser generated by jison 0.4.15 */
|
|
773
|
+
/*
|
|
774
|
+
Returns a Parser object of the following structure:
|
|
775
|
+
|
|
776
|
+
Parser: {
|
|
777
|
+
yy: {}
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
Parser.prototype: {
|
|
781
|
+
yy: {},
|
|
782
|
+
trace: function(),
|
|
783
|
+
symbols_: {associative list: name ==> number},
|
|
784
|
+
terminals_: {associative list: number ==> name},
|
|
785
|
+
productions_: [...],
|
|
786
|
+
performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate, $$, _$),
|
|
787
|
+
table: [...],
|
|
788
|
+
defaultActions: {...},
|
|
789
|
+
parseError: function(str, hash),
|
|
790
|
+
parse: function(input),
|
|
791
|
+
|
|
792
|
+
lexer: {
|
|
793
|
+
EOF: 1,
|
|
794
|
+
parseError: function(str, hash),
|
|
795
|
+
setInput: function(input),
|
|
796
|
+
input: function(),
|
|
797
|
+
unput: function(str),
|
|
798
|
+
more: function(),
|
|
799
|
+
less: function(n),
|
|
800
|
+
pastInput: function(),
|
|
801
|
+
upcomingInput: function(),
|
|
802
|
+
showPosition: function(),
|
|
803
|
+
test_match: function(regex_match_array, rule_index),
|
|
804
|
+
next: function(),
|
|
805
|
+
lex: function(),
|
|
806
|
+
begin: function(condition),
|
|
807
|
+
popState: function(),
|
|
808
|
+
_currentRules: function(),
|
|
809
|
+
topState: function(),
|
|
810
|
+
pushState: function(condition),
|
|
811
|
+
|
|
812
|
+
options: {
|
|
813
|
+
ranges: boolean (optional: true ==> token location info will include a .range[] member)
|
|
814
|
+
flex: boolean (optional: true ==> flex-like lexing behaviour where the rules are tested exhaustively to find the longest match)
|
|
815
|
+
backtrack_lexer: boolean (optional: true ==> lexer regexes are tested in order and for each matching regex the action code is invoked; the lexer terminates the scan when a token is returned by the action code)
|
|
816
|
+
},
|
|
817
|
+
|
|
818
|
+
performAction: function(yy, yy_, $avoiding_name_collisions, YY_START),
|
|
819
|
+
rules: [...],
|
|
820
|
+
conditions: {associative list: name ==> set},
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
|
|
825
|
+
token location info (@$, _$, etc.): {
|
|
826
|
+
first_line: n,
|
|
827
|
+
last_line: n,
|
|
828
|
+
first_column: n,
|
|
829
|
+
last_column: n,
|
|
830
|
+
range: [start_number, end_number] (where the numbers are indexes into the input string, regular zero-based)
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
|
|
834
|
+
the parseError function receives a 'hash' object with these members for lexer and parser errors: {
|
|
835
|
+
text: (matched text)
|
|
836
|
+
token: (the produced terminal token, if any)
|
|
837
|
+
line: (yylineno)
|
|
838
|
+
}
|
|
839
|
+
while parser (grammar) errors will also provide these members, i.e. parser errors deliver a superset of attributes: {
|
|
840
|
+
loc: (yylloc)
|
|
841
|
+
expected: (string describing the set of expected tokens)
|
|
842
|
+
recoverable: (boolean: TRUE when the parser has a error recovery rule available for this particular error)
|
|
843
|
+
}
|
|
844
|
+
*/
|
|
656
845
|
var parser = function () {
|
|
657
|
-
var o = function
|
|
658
|
-
for (
|
|
659
|
-
return
|
|
846
|
+
var o = function (k, v, o, l) {
|
|
847
|
+
for (o = o || {}, l = k.length; l--; o[k[l]] = v);
|
|
848
|
+
return o;
|
|
660
849
|
},
|
|
661
850
|
$V0 = [1, 7],
|
|
662
851
|
$V1 = [1, 17],
|
|
@@ -768,7 +957,9 @@ var parser = function () {
|
|
|
768
957
|
46: "FRAC"
|
|
769
958
|
},
|
|
770
959
|
productions_: [0, [3, 4], [3, 2], [3, 1], [4, 1], [7, 3], [7, 3], [7, 1], [9, 2], [9, 3], [9, 3], [9, 1], [13, 2], [13, 1], [15, 1], [17, 1], [17, 3], [17, 1], [20, 1], [20, 1], [20, 3], [11, 2], [11, 2], [11, 1], [25, 3], [25, 1], [27, 1], [24, 3], [24, 1], [24, 1], [24, 1], [24, 1], [24, 3], [24, 3], [36, 1], [38, 4], [38, 4], [38, 7], [38, 4], [38, 3], [38, 3], [38, 4], [26, 1], [26, 1], [26, 7]],
|
|
771
|
-
performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate
|
|
960
|
+
performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate /* action[1] */, $$ /* vstack */, _$ /* lstack */) {
|
|
961
|
+
/* this == yyval */
|
|
962
|
+
|
|
772
963
|
var $0 = $$.length - 1;
|
|
773
964
|
switch (yystate) {
|
|
774
965
|
case 1:
|
|
@@ -1814,6 +2005,7 @@ var parser = function () {
|
|
|
1814
2005
|
} else {
|
|
1815
2006
|
this.parseError = Object.getPrototypeOf(this).parseError;
|
|
1816
2007
|
}
|
|
2008
|
+
//_token_stack:
|
|
1817
2009
|
function lex() {
|
|
1818
2010
|
var token;
|
|
1819
2011
|
token = lexer.lex() || EOF;
|
|
@@ -1913,6 +2105,7 @@ var parser = function () {
|
|
|
1913
2105
|
return true;
|
|
1914
2106
|
}
|
|
1915
2107
|
};
|
|
2108
|
+
/* generated by jison-lex 0.3.4 */
|
|
1916
2109
|
var lexer = function () {
|
|
1917
2110
|
var lexer = {
|
|
1918
2111
|
EOF: 1,
|
|
@@ -1923,6 +2116,7 @@ var parser = function () {
|
|
|
1923
2116
|
throw new Error(str);
|
|
1924
2117
|
}
|
|
1925
2118
|
},
|
|
2119
|
+
// resets the lexer, sets new input
|
|
1926
2120
|
setInput: function (input, yy) {
|
|
1927
2121
|
this.yy = yy || this.yy || {};
|
|
1928
2122
|
this._input = input;
|
|
@@ -1942,6 +2136,7 @@ var parser = function () {
|
|
|
1942
2136
|
this.offset = 0;
|
|
1943
2137
|
return this;
|
|
1944
2138
|
},
|
|
2139
|
+
// consumes and returns one char from the input
|
|
1945
2140
|
input: function () {
|
|
1946
2141
|
var ch = this._input[0];
|
|
1947
2142
|
this.yytext += ch;
|
|
@@ -1962,11 +2157,13 @@ var parser = function () {
|
|
|
1962
2157
|
this._input = this._input.slice(1);
|
|
1963
2158
|
return ch;
|
|
1964
2159
|
},
|
|
2160
|
+
// unshifts one char (or a string) into the input
|
|
1965
2161
|
unput: function (ch) {
|
|
1966
2162
|
var len = ch.length;
|
|
1967
2163
|
var lines = ch.split(/(?:\r\n?|\n)/g);
|
|
1968
2164
|
this._input = ch + this._input;
|
|
1969
2165
|
this.yytext = this.yytext.substr(0, this.yytext.length - len);
|
|
2166
|
+
//this.yyleng -= len;
|
|
1970
2167
|
this.offset -= len;
|
|
1971
2168
|
var oldLines = this.match.split(/(?:\r\n?|\n)/g);
|
|
1972
2169
|
this.match = this.match.substr(0, this.match.length - 1);
|
|
@@ -1987,10 +2184,12 @@ var parser = function () {
|
|
|
1987
2184
|
this.yyleng = this.yytext.length;
|
|
1988
2185
|
return this;
|
|
1989
2186
|
},
|
|
2187
|
+
// When called from action, caches matched text and appends it on next action
|
|
1990
2188
|
more: function () {
|
|
1991
2189
|
this._more = true;
|
|
1992
2190
|
return this;
|
|
1993
2191
|
},
|
|
2192
|
+
// When called from action, signals the lexer that this rule fails to match the input, so the next matching rule (regex) should be tested instead.
|
|
1994
2193
|
reject: function () {
|
|
1995
2194
|
if (this.options.backtrack_lexer) {
|
|
1996
2195
|
this._backtrack = true;
|
|
@@ -2003,13 +2202,16 @@ var parser = function () {
|
|
|
2003
2202
|
}
|
|
2004
2203
|
return this;
|
|
2005
2204
|
},
|
|
2205
|
+
// retain first n characters of the match
|
|
2006
2206
|
less: function (n) {
|
|
2007
2207
|
this.unput(this.match.slice(n));
|
|
2008
2208
|
},
|
|
2209
|
+
// displays already matched input, i.e. for error messages
|
|
2009
2210
|
pastInput: function () {
|
|
2010
2211
|
var past = this.matched.substr(0, this.matched.length - this.match.length);
|
|
2011
2212
|
return (past.length > 20 ? '...' : '') + past.substr(-20).replace(/\n/g, "");
|
|
2012
2213
|
},
|
|
2214
|
+
// displays upcoming input, i.e. for error messages
|
|
2013
2215
|
upcomingInput: function () {
|
|
2014
2216
|
var next = this.match;
|
|
2015
2217
|
if (next.length < 20) {
|
|
@@ -2017,14 +2219,17 @@ var parser = function () {
|
|
|
2017
2219
|
}
|
|
2018
2220
|
return (next.substr(0, 20) + (next.length > 20 ? '...' : '')).replace(/\n/g, "");
|
|
2019
2221
|
},
|
|
2222
|
+
// displays the character position where the lexing error occurred, i.e. for error messages
|
|
2020
2223
|
showPosition: function () {
|
|
2021
2224
|
var pre = this.pastInput();
|
|
2022
2225
|
var c = new Array(pre.length + 1).join("-");
|
|
2023
2226
|
return pre + this.upcomingInput() + "\n" + c + "^";
|
|
2024
2227
|
},
|
|
2228
|
+
// test the lexed token: return FALSE when not a match, otherwise return token
|
|
2025
2229
|
test_match: function (match, indexed_rule) {
|
|
2026
2230
|
var token, lines, backup;
|
|
2027
2231
|
if (this.options.backtrack_lexer) {
|
|
2232
|
+
// save context
|
|
2028
2233
|
backup = {
|
|
2029
2234
|
yylineno: this.yylineno,
|
|
2030
2235
|
yylloc: {
|
|
@@ -2077,13 +2282,15 @@ var parser = function () {
|
|
|
2077
2282
|
if (token) {
|
|
2078
2283
|
return token;
|
|
2079
2284
|
} else if (this._backtrack) {
|
|
2285
|
+
// recover context
|
|
2080
2286
|
for (var k in backup) {
|
|
2081
2287
|
this[k] = backup[k];
|
|
2082
2288
|
}
|
|
2083
|
-
return false;
|
|
2289
|
+
return false; // rule action called reject() implying the next rule should be tested instead.
|
|
2084
2290
|
}
|
|
2085
2291
|
return false;
|
|
2086
2292
|
},
|
|
2293
|
+
// return next match in input
|
|
2087
2294
|
next: function () {
|
|
2088
2295
|
if (this.done) {
|
|
2089
2296
|
return this.EOF;
|
|
@@ -2108,8 +2315,9 @@ var parser = function () {
|
|
|
2108
2315
|
return token;
|
|
2109
2316
|
} else if (this._backtrack) {
|
|
2110
2317
|
match = false;
|
|
2111
|
-
continue;
|
|
2318
|
+
continue; // rule action called reject() implying a rule MISmatch.
|
|
2112
2319
|
} else {
|
|
2320
|
+
// else: this is a lexer rule which consumes input without producing a token (e.g. whitespace)
|
|
2113
2321
|
return false;
|
|
2114
2322
|
}
|
|
2115
2323
|
} else if (!this.options.flex) {
|
|
@@ -2122,6 +2330,7 @@ var parser = function () {
|
|
|
2122
2330
|
if (token !== false) {
|
|
2123
2331
|
return token;
|
|
2124
2332
|
}
|
|
2333
|
+
// else: this is a lexer rule which consumes input without producing a token (e.g. whitespace)
|
|
2125
2334
|
return false;
|
|
2126
2335
|
}
|
|
2127
2336
|
if (this._input === "") {
|
|
@@ -2134,6 +2343,7 @@ var parser = function () {
|
|
|
2134
2343
|
});
|
|
2135
2344
|
}
|
|
2136
2345
|
},
|
|
2346
|
+
// return next match that has a token
|
|
2137
2347
|
lex: function lex() {
|
|
2138
2348
|
var r = this.next();
|
|
2139
2349
|
if (r) {
|
|
@@ -2142,9 +2352,11 @@ var parser = function () {
|
|
|
2142
2352
|
return this.lex();
|
|
2143
2353
|
}
|
|
2144
2354
|
},
|
|
2355
|
+
// activates a new lexer condition state (pushes the new lexer condition state onto the condition stack)
|
|
2145
2356
|
begin: function begin(condition) {
|
|
2146
2357
|
this.conditionStack.push(condition);
|
|
2147
2358
|
},
|
|
2359
|
+
// pop the previously active lexer condition state off the condition stack
|
|
2148
2360
|
popState: function popState() {
|
|
2149
2361
|
var n = this.conditionStack.length - 1;
|
|
2150
2362
|
if (n > 0) {
|
|
@@ -2153,6 +2365,7 @@ var parser = function () {
|
|
|
2153
2365
|
return this.conditionStack[0];
|
|
2154
2366
|
}
|
|
2155
2367
|
},
|
|
2368
|
+
// produce the lexer rule set which is active for the currently active lexer condition state
|
|
2156
2369
|
_currentRules: function _currentRules() {
|
|
2157
2370
|
if (this.conditionStack.length && this.conditionStack[this.conditionStack.length - 1]) {
|
|
2158
2371
|
return this.conditions[this.conditionStack[this.conditionStack.length - 1]].rules;
|
|
@@ -2160,6 +2373,7 @@ var parser = function () {
|
|
|
2160
2373
|
return this.conditions["INITIAL"].rules;
|
|
2161
2374
|
}
|
|
2162
2375
|
},
|
|
2376
|
+
// return the currently active lexer condition state; when an index argument is provided it produces the N-th previous condition state, if available
|
|
2163
2377
|
topState: function topState(n) {
|
|
2164
2378
|
n = this.conditionStack.length - 1 - Math.abs(n || 0);
|
|
2165
2379
|
if (n >= 0) {
|
|
@@ -2168,9 +2382,11 @@ var parser = function () {
|
|
|
2168
2382
|
return "INITIAL";
|
|
2169
2383
|
}
|
|
2170
2384
|
},
|
|
2385
|
+
// alias for begin(condition)
|
|
2171
2386
|
pushState: function pushState(condition) {
|
|
2172
2387
|
this.begin(condition);
|
|
2173
2388
|
},
|
|
2389
|
+
// return the number of states currently on the stack
|
|
2174
2390
|
stateStackSize: function stateStackSize() {
|
|
2175
2391
|
return this.conditionStack.length;
|
|
2176
2392
|
},
|
|
@@ -2180,10 +2396,13 @@ var parser = function () {
|
|
|
2180
2396
|
performAction: function anonymous(yy, yy_, $avoiding_name_collisions, YY_START) {
|
|
2181
2397
|
switch ($avoiding_name_collisions) {
|
|
2182
2398
|
case 0:
|
|
2399
|
+
/* skip whitespace */
|
|
2183
2400
|
break;
|
|
2184
2401
|
case 1:
|
|
2402
|
+
/* skip \space */
|
|
2185
2403
|
break;
|
|
2186
2404
|
case 2:
|
|
2405
|
+
/* skip '\ ' */
|
|
2187
2406
|
break;
|
|
2188
2407
|
case 3:
|
|
2189
2408
|
return "INT";
|
|
@@ -2405,20 +2624,65 @@ var parser = function () {
|
|
|
2405
2624
|
return new Parser();
|
|
2406
2625
|
}();
|
|
2407
2626
|
|
|
2408
|
-
|
|
2409
|
-
|
|
2627
|
+
/* eslint-disable prettier/prettier */
|
|
2628
|
+
/* eslint-disable import/order */
|
|
2629
|
+
/* TODO: fix these lint errors (http://eslint.org/docs/rules): */
|
|
2630
|
+
/* eslint-disable indent, no-undef, no-var, no-dupe-keys, no-new-func, no-redeclare, comma-dangle, max-len, prefer-spread, space-infix-ops, space-unary-ops */
|
|
2631
|
+
|
|
2632
|
+
/* The node hierarcy is as follows:
|
|
2633
|
+
|
|
2634
|
+
(Expr)
|
|
2635
|
+
(Seq) 2+ children
|
|
2636
|
+
Add
|
|
2637
|
+
Mul
|
|
2638
|
+
Pow 2 children
|
|
2639
|
+
Log 2 children
|
|
2640
|
+
Eq 2 children
|
|
2641
|
+
Trig 1 child
|
|
2642
|
+
Abs 1 child
|
|
2643
|
+
(Sym)
|
|
2644
|
+
Func 1 child e.g. f(x)
|
|
2645
|
+
Var leaf node e.g. x, x_n
|
|
2646
|
+
Const leaf node e.g. pi, e, <i>
|
|
2647
|
+
Unit leaf node e.g. kg
|
|
2648
|
+
(Num) leaf node
|
|
2649
|
+
Rational e.g. 2/3
|
|
2650
|
+
Int
|
|
2651
|
+
Float
|
|
2652
|
+
|
|
2653
|
+
(abstract, not meant to be instantiated)
|
|
2654
|
+
|
|
2655
|
+
== Key design concepts ==
|
|
2656
|
+
Functional: All methods return new nodes - nodes are never mutated.
|
|
2657
|
+
Ignore commutativity: Commutative inputs should be parsed equivalently.
|
|
2658
|
+
Exploit commutativity: Output should take advantage of ordering.
|
|
2659
|
+
*/
|
|
2660
|
+
|
|
2661
|
+
/* non user-facing functions */
|
|
2662
|
+
|
|
2663
|
+
// reliably detect NaN
|
|
2664
|
+
const isNaN = function (object) {
|
|
2410
2665
|
return object !== object;
|
|
2411
2666
|
};
|
|
2412
|
-
|
|
2667
|
+
|
|
2668
|
+
// return a random float between min (inclusive) and max (exclusive),
|
|
2669
|
+
// not that inclusivity means much, probabilistically, on floats
|
|
2670
|
+
const randomFloat = function (min, max) {
|
|
2413
2671
|
var extent = max - min;
|
|
2414
2672
|
return Math.random() * extent + min;
|
|
2415
2673
|
};
|
|
2674
|
+
|
|
2675
|
+
/* constants */
|
|
2416
2676
|
var ITERATIONS = 12;
|
|
2417
|
-
var TOLERANCE = 9;
|
|
2677
|
+
var TOLERANCE = 9; // decimal places
|
|
2678
|
+
|
|
2679
|
+
// NOTE(kevinb): _.partition exists in a more recent version of underscore.
|
|
2680
|
+
// To avoid having to update underscore I've added a hacky version of this
|
|
2681
|
+
// method here.
|
|
2418
2682
|
function partition(list, iteratee) {
|
|
2419
2683
|
const a = [];
|
|
2420
2684
|
const b = [];
|
|
2421
|
-
|
|
2685
|
+
___default.default.forEach(list, (elem, key, ctx) => {
|
|
2422
2686
|
if (iteratee(elem, key, ctx)) {
|
|
2423
2687
|
a.push(elem);
|
|
2424
2688
|
} else {
|
|
@@ -2430,7 +2694,7 @@ function partition(list, iteratee) {
|
|
|
2430
2694
|
function isExpr(arg) {
|
|
2431
2695
|
return arg instanceof Expr;
|
|
2432
2696
|
}
|
|
2433
|
-
const isAdd = function
|
|
2697
|
+
const isAdd = function (term) {
|
|
2434
2698
|
return term instanceof Add;
|
|
2435
2699
|
};
|
|
2436
2700
|
function isRational(arg) {
|
|
@@ -2443,13 +2707,23 @@ function getFactors(expr) {
|
|
|
2443
2707
|
return [expr];
|
|
2444
2708
|
}
|
|
2445
2709
|
}
|
|
2710
|
+
/* abstract base expression node */
|
|
2446
2711
|
class Expr {
|
|
2712
|
+
hints;
|
|
2447
2713
|
constructor() {
|
|
2448
|
-
this.hints = void 0;
|
|
2449
2714
|
this.hints = {
|
|
2450
2715
|
parens: false
|
|
2451
2716
|
};
|
|
2452
2717
|
}
|
|
2718
|
+
|
|
2719
|
+
// this node's immediate constructor
|
|
2720
|
+
// The `new (...args: any[]): any;` part of the type is a
|
|
2721
|
+
// "construct" signature. It indicates that `func` is a class.
|
|
2722
|
+
// See https://www.typescriptlang.org/docs/handbook/2/functions.html#construct-signatures.
|
|
2723
|
+
|
|
2724
|
+
// an array of the arguments to this node's immediate constructor
|
|
2725
|
+
|
|
2726
|
+
// make a new node with the given arguments
|
|
2453
2727
|
construct(args) {
|
|
2454
2728
|
const func = this.func;
|
|
2455
2729
|
const instance = new func(...args);
|
|
@@ -2458,29 +2732,59 @@ class Expr {
|
|
|
2458
2732
|
}
|
|
2459
2733
|
return instance;
|
|
2460
2734
|
}
|
|
2461
|
-
|
|
2735
|
+
|
|
2736
|
+
// an abstraction for chainable, bottom-up recursion
|
|
2737
|
+
// NOTE(kevinb): This method is highly dynamic. It's possible that it
|
|
2738
|
+
// could be made more type-safe using overload signatures.
|
|
2739
|
+
recurse(method) {
|
|
2740
|
+
for (var _len = arguments.length, passed = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
|
|
2741
|
+
passed[_key - 1] = arguments[_key];
|
|
2742
|
+
}
|
|
2462
2743
|
var args = this.args().map(function (arg) {
|
|
2463
|
-
return
|
|
2744
|
+
return ___default.default.isString(arg) || ___default.default.isNumber(arg) ? arg : arg?.[method].apply(arg, passed);
|
|
2464
2745
|
});
|
|
2465
2746
|
return this.construct(args);
|
|
2466
2747
|
}
|
|
2467
|
-
|
|
2468
|
-
|
|
2748
|
+
|
|
2749
|
+
// evaluate numerically with given variable mapping
|
|
2750
|
+
// NOTE(kevin): This could made into an abstract method but
|
|
2751
|
+
// Eq doesn't implement it. This indicates that we probably
|
|
2752
|
+
// need to introduce another class in our hierarchy.
|
|
2753
|
+
eval() {
|
|
2754
|
+
throw new Error("Abstract method - must override for expr: " +
|
|
2755
|
+
// eslint-disable-next-line @babel/no-invalid-this
|
|
2756
|
+
this.print());
|
|
2469
2757
|
}
|
|
2758
|
+
|
|
2759
|
+
// NOTE(kevin): This could made into an abstract method but
|
|
2760
|
+
// Eq doesn't implement it. This indicates that we probably
|
|
2761
|
+
// need to introduce another class in our hierarchy.
|
|
2470
2762
|
codegen() {
|
|
2471
|
-
throw new Error("Abstract method - must override for expr: " +
|
|
2763
|
+
throw new Error("Abstract method - must override for expr: " +
|
|
2764
|
+
// eslint-disable-next-line @babel/no-invalid-this
|
|
2765
|
+
this.print());
|
|
2472
2766
|
}
|
|
2473
2767
|
compile() {
|
|
2474
2768
|
var code = this.codegen();
|
|
2475
2769
|
try {
|
|
2770
|
+
// @ts-expect-error: TypeScript doesn't want to unify
|
|
2771
|
+
// `Function` with the `compile`'s return type.
|
|
2476
2772
|
return new Function("vars", "return " + code + ";");
|
|
2477
|
-
} catch
|
|
2773
|
+
} catch {
|
|
2478
2774
|
throw new Error("Function did not compile: " + code);
|
|
2479
2775
|
}
|
|
2480
2776
|
}
|
|
2777
|
+
|
|
2778
|
+
// returns a string unambiguously representing the expression
|
|
2779
|
+
// should be valid as input
|
|
2780
|
+
// e.g. this.equals(parse(this.print())) === true
|
|
2781
|
+
|
|
2782
|
+
// returns a TeX string representing the expression
|
|
2783
|
+
|
|
2784
|
+
// returns a TeX string, modified by the given options
|
|
2481
2785
|
asTex(options) {
|
|
2482
2786
|
options = options || {};
|
|
2483
|
-
|
|
2787
|
+
___default.default.defaults(options, {
|
|
2484
2788
|
display: true,
|
|
2485
2789
|
dynamic: true,
|
|
2486
2790
|
times: false
|
|
@@ -2498,46 +2802,76 @@ class Expr {
|
|
|
2498
2802
|
}
|
|
2499
2803
|
return tex;
|
|
2500
2804
|
}
|
|
2805
|
+
|
|
2806
|
+
// returns the name of this expression's constructor as a string
|
|
2807
|
+
// only used for testing and debugging
|
|
2501
2808
|
name() {
|
|
2502
2809
|
return this.func.name;
|
|
2503
2810
|
}
|
|
2811
|
+
|
|
2812
|
+
// returns a string representing current node structure
|
|
2504
2813
|
repr() {
|
|
2505
2814
|
return this.name() + "(" + this.args().map(function (arg) {
|
|
2506
|
-
return
|
|
2815
|
+
return ___default.default.isString(arg) || ___default.default.isNumber(arg) ? arg : arg?.repr();
|
|
2507
2816
|
}).join(",") + ")";
|
|
2508
2817
|
}
|
|
2818
|
+
|
|
2819
|
+
// removes all negative signs
|
|
2509
2820
|
strip() {
|
|
2510
2821
|
return this.recurse("strip");
|
|
2511
2822
|
}
|
|
2823
|
+
|
|
2824
|
+
// canonically reorders all commutative elements
|
|
2512
2825
|
normalize() {
|
|
2513
2826
|
return this.recurse("normalize");
|
|
2514
2827
|
}
|
|
2828
|
+
|
|
2829
|
+
// expands the expression
|
|
2515
2830
|
expand() {
|
|
2516
2831
|
return this.recurse("expand");
|
|
2517
2832
|
}
|
|
2833
|
+
|
|
2834
|
+
// naively factors out like terms
|
|
2518
2835
|
factor(options) {
|
|
2519
2836
|
return this.recurse("factor", options);
|
|
2520
2837
|
}
|
|
2838
|
+
|
|
2839
|
+
// collect all like terms
|
|
2521
2840
|
collect(options) {
|
|
2522
2841
|
return this.recurse("collect", options);
|
|
2523
2842
|
}
|
|
2843
|
+
|
|
2844
|
+
// strict syntactic equality check
|
|
2524
2845
|
equals(other) {
|
|
2525
2846
|
return this.normalize().print() === other.normalize().print();
|
|
2526
2847
|
}
|
|
2848
|
+
|
|
2849
|
+
// expand and collect until the expression no longer changes
|
|
2527
2850
|
simplify(options) {
|
|
2528
|
-
options =
|
|
2529
|
-
once: false
|
|
2530
|
-
|
|
2851
|
+
options = {
|
|
2852
|
+
once: false,
|
|
2853
|
+
...options
|
|
2854
|
+
};
|
|
2855
|
+
|
|
2856
|
+
// Attempt to factor and collect
|
|
2531
2857
|
var step1 = this.factor(options);
|
|
2532
2858
|
var step2 = step1.collect(options);
|
|
2859
|
+
|
|
2860
|
+
// Rollback if collect didn't do anything
|
|
2533
2861
|
if (step1.equals(step2)) {
|
|
2534
2862
|
step2 = this.collect(options);
|
|
2535
2863
|
}
|
|
2864
|
+
|
|
2865
|
+
// Attempt to expand and collect
|
|
2536
2866
|
var step3 = step2.expand();
|
|
2537
2867
|
var step4 = step3.collect(options);
|
|
2868
|
+
|
|
2869
|
+
// Rollback if collect didn't do anything
|
|
2538
2870
|
if (step3.equals(step4)) {
|
|
2539
2871
|
step4 = step2.collect(options);
|
|
2540
2872
|
}
|
|
2873
|
+
|
|
2874
|
+
// One round of simplification complete
|
|
2541
2875
|
var simplified = step4;
|
|
2542
2876
|
if (options.once || this.equals(simplified)) {
|
|
2543
2877
|
return simplified;
|
|
@@ -2545,35 +2879,52 @@ class Expr {
|
|
|
2545
2879
|
return simplified.simplify(options);
|
|
2546
2880
|
}
|
|
2547
2881
|
}
|
|
2882
|
+
|
|
2883
|
+
// check whether this expression is simplified
|
|
2548
2884
|
isSimplified() {
|
|
2549
2885
|
return this.equals(this.simplify());
|
|
2550
2886
|
}
|
|
2887
|
+
|
|
2888
|
+
// return the child nodes of this node
|
|
2551
2889
|
exprArgs() {
|
|
2890
|
+
// @ts-expect-error: Type 'string | number | Expr | undefined' is not assignable to type 'string | Expr'.
|
|
2552
2891
|
return this.args().filter(isExpr);
|
|
2553
2892
|
}
|
|
2893
|
+
|
|
2894
|
+
// return the variables (function and non) within the expression
|
|
2554
2895
|
getVars(excludeFunc) {
|
|
2555
|
-
return
|
|
2896
|
+
return ___default.default.uniq(___default.default.flatten(___default.default.invoke(this.exprArgs(), "getVars", excludeFunc))).sort();
|
|
2556
2897
|
}
|
|
2557
2898
|
getConsts() {
|
|
2558
|
-
return
|
|
2899
|
+
return ___default.default.uniq(___default.default.flatten(___default.default.invoke(this.exprArgs(), "getConsts"))).sort();
|
|
2559
2900
|
}
|
|
2560
2901
|
getUnits() {
|
|
2561
|
-
return
|
|
2902
|
+
return ___default.default.flatten(___default.default.invoke(this.exprArgs(), "getUnits"));
|
|
2562
2903
|
}
|
|
2904
|
+
|
|
2905
|
+
// check whether this expression node is of a particular type
|
|
2563
2906
|
is(func) {
|
|
2564
2907
|
return this instanceof func;
|
|
2565
2908
|
}
|
|
2909
|
+
|
|
2910
|
+
// check whether this expression has a particular node type
|
|
2566
2911
|
has(func) {
|
|
2567
2912
|
if (this instanceof func) {
|
|
2568
2913
|
return true;
|
|
2569
2914
|
}
|
|
2570
|
-
return
|
|
2915
|
+
return ___default.default.any(this.exprArgs(), function (arg) {
|
|
2571
2916
|
return arg.has(func);
|
|
2572
2917
|
});
|
|
2573
2918
|
}
|
|
2919
|
+
|
|
2920
|
+
// raise this expression to a given exponent
|
|
2921
|
+
// most useful for eventually implementing i^3 = -i, etc.
|
|
2574
2922
|
raiseToThe(exp, options) {
|
|
2575
2923
|
return new Pow(this, exp);
|
|
2576
2924
|
}
|
|
2925
|
+
|
|
2926
|
+
// does this expression have a specific rendering hint?
|
|
2927
|
+
// rendering hints are picked up while parsing, but are lost during transformations
|
|
2577
2928
|
isSubtract() {
|
|
2578
2929
|
return false;
|
|
2579
2930
|
}
|
|
@@ -2583,17 +2934,25 @@ class Expr {
|
|
|
2583
2934
|
isRoot() {
|
|
2584
2935
|
return false;
|
|
2585
2936
|
}
|
|
2937
|
+
|
|
2938
|
+
// whether this node needs an explicit multiplication sign if following a Num
|
|
2586
2939
|
needsExplicitMul() {
|
|
2587
2940
|
return this.exprArgs()[0].needsExplicitMul();
|
|
2588
2941
|
}
|
|
2942
|
+
|
|
2943
|
+
// check that the variables in both expressions are the same
|
|
2589
2944
|
sameVars(other) {
|
|
2590
2945
|
var vars1 = this.getVars();
|
|
2591
2946
|
var vars2 = other.getVars();
|
|
2592
|
-
|
|
2593
|
-
|
|
2947
|
+
|
|
2948
|
+
// the other Expr can have more variables than this one
|
|
2949
|
+
// this lets you multiply equations by other variables
|
|
2950
|
+
var same = function (array1, array2) {
|
|
2951
|
+
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
|
2952
|
+
return !___default.default.difference(array1, array2).length;
|
|
2594
2953
|
};
|
|
2595
|
-
var lower = function
|
|
2596
|
-
return
|
|
2954
|
+
var lower = function (array) {
|
|
2955
|
+
return ___default.default.uniq(___default.default.invoke(array, "toLowerCase")).sort();
|
|
2597
2956
|
};
|
|
2598
2957
|
var equal = same(vars1, vars2);
|
|
2599
2958
|
var equalIgnoringCase = same(lower(vars1), lower(vars2));
|
|
@@ -2602,39 +2961,86 @@ class Expr {
|
|
|
2602
2961
|
equalIgnoringCase: equalIgnoringCase
|
|
2603
2962
|
};
|
|
2604
2963
|
}
|
|
2964
|
+
|
|
2965
|
+
// semantic equality check, call after sameVars() to avoid potential false positives
|
|
2966
|
+
// plug in random numbers for the variables in both expressions
|
|
2967
|
+
// if they both consistently evaluate the same, then they're the same
|
|
2605
2968
|
compare(other) {
|
|
2969
|
+
// equation comparisons are handled by Eq.compare()
|
|
2606
2970
|
if (other instanceof Eq) {
|
|
2607
2971
|
return false;
|
|
2608
2972
|
}
|
|
2609
|
-
var varList =
|
|
2610
|
-
|
|
2973
|
+
var varList = ___default.default.union(this.getVars(/* excludeFunc */true), other.getVars(/* excludeFunc */true));
|
|
2974
|
+
|
|
2975
|
+
// If the numbers are large we would like to do a relative comparison
|
|
2976
|
+
// rather than an absolute one, but if they're small enough then an
|
|
2977
|
+
// absolute comparison makes more sense
|
|
2978
|
+
var getDelta = function (num1, num2) {
|
|
2611
2979
|
if (Math.abs(num1) < 1 || Math.abs(num2) < 1) {
|
|
2612
2980
|
return Math.abs(num1 - num2);
|
|
2613
2981
|
} else {
|
|
2614
2982
|
return Math.abs(1 - num1 / num2);
|
|
2615
2983
|
}
|
|
2616
2984
|
};
|
|
2617
|
-
var equalNumbers = function
|
|
2985
|
+
var equalNumbers = function (num1, num2) {
|
|
2618
2986
|
var delta = getDelta(num1, num2);
|
|
2619
|
-
return num1 === num2 || isNaN(num1) && isNaN(num2) || delta < Math.pow(10, -9);
|
|
2987
|
+
return num1 === num2 /* needed if either is +/- Infinity */ || isNaN(num1) && isNaN(num2) || delta < Math.pow(10, -9);
|
|
2620
2988
|
};
|
|
2621
|
-
|
|
2989
|
+
|
|
2990
|
+
// If no variables, only need to evaluate once.
|
|
2991
|
+
// note(matthew) Seems to be an optimization for simple cases like `2+2=4`
|
|
2992
|
+
// where there are no variables / functions.
|
|
2993
|
+
// Ran into issues with it in LEMS-2777 and found that tests pass
|
|
2994
|
+
// with this removed, but keeping a modified version out of caution.
|
|
2995
|
+
const varAndFuncList = ___default.default.union(this.getVars(/* excludeFunc */false), other.getVars(/* excludeFunc */false));
|
|
2996
|
+
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
|
2622
2997
|
if (!varAndFuncList.length && !this.has(Unit) && !other.has(Unit)) {
|
|
2623
2998
|
return equalNumbers(this.eval(), other.eval());
|
|
2624
2999
|
}
|
|
3000
|
+
|
|
3001
|
+
// collect here to avoid sometimes dividing by zero, and sometimes not
|
|
3002
|
+
// it is better to be deterministic, e.g. x/x -> 1
|
|
3003
|
+
// TODO(alex): may want to keep track of assumptions as they're made
|
|
2625
3004
|
var expr1 = this.collect();
|
|
2626
3005
|
var expr2 = other.collect();
|
|
2627
3006
|
var unitList1 = this.getUnits();
|
|
2628
3007
|
var unitList2 = other.getUnits();
|
|
2629
|
-
if (!
|
|
3008
|
+
if (!___default.default.isEqual(unitList1, unitList2)) {
|
|
2630
3009
|
return false;
|
|
2631
3010
|
}
|
|
3011
|
+
|
|
3012
|
+
// Compare at a set number (currently 12) of points to determine
|
|
3013
|
+
// equality.
|
|
3014
|
+
//
|
|
3015
|
+
// `range` (and `vars`) is the only variable that varies through the
|
|
3016
|
+
// iterations. For each of range = 10, 100, and 1000, each random
|
|
3017
|
+
// variable is picked from (-range, range).
|
|
3018
|
+
//
|
|
3019
|
+
// Note that because there are 12 iterations and three ranges, each
|
|
3020
|
+
// range is checked four times.
|
|
2632
3021
|
for (var i = 0; i < ITERATIONS; i++) {
|
|
2633
3022
|
var vars = {};
|
|
3023
|
+
|
|
3024
|
+
// One third total iterations each with range 10, 100, and 1000
|
|
2634
3025
|
var range = Math.pow(10, 1 + Math.floor(3 * i / ITERATIONS));
|
|
3026
|
+
|
|
3027
|
+
// Half of the iterations should only use integer values.
|
|
3028
|
+
// This is because expressions like (-2)^x are common but result
|
|
3029
|
+
// in NaN when evaluated in JS with non-integer values of x.
|
|
3030
|
+
// Without this, (-2)^x and (-2)^(x+1) both end up always being NaN
|
|
3031
|
+
// and thus equivalent. With this, the most common failure case is
|
|
3032
|
+
// avoided. However, less common cases such as (-2)^(x+0.1) and
|
|
3033
|
+
// (-2)^(x+1.1) will still both evaluate to NaN and result in a
|
|
3034
|
+
// false positive.
|
|
3035
|
+
//
|
|
3036
|
+
// Note that the above is only true in vanilla JS Number-land,
|
|
3037
|
+
// which has no concept of complex numbers. The solution is simple:
|
|
3038
|
+
// Integrate a library for handling complex numbers.
|
|
3039
|
+
//
|
|
3040
|
+
// TODO(alex): Add support for complex numbers, then remove this.
|
|
2635
3041
|
var useFloats = i % 2 === 0;
|
|
2636
|
-
|
|
2637
|
-
vars[v] = useFloats ? randomFloat(-range, range) :
|
|
3042
|
+
___default.default.each(varList, function (v) {
|
|
3043
|
+
vars[v] = useFloats ? randomFloat(-range, range) : ___default.default.random(-range, range);
|
|
2638
3044
|
});
|
|
2639
3045
|
let equal;
|
|
2640
3046
|
if (expr1.has(Func) || expr2.has(Func) || expr1.has(Unit) || expr2.has(Unit)) {
|
|
@@ -2652,6 +3058,8 @@ class Expr {
|
|
|
2652
3058
|
}
|
|
2653
3059
|
return true;
|
|
2654
3060
|
}
|
|
3061
|
+
|
|
3062
|
+
// evaluate as much of the expression as possible
|
|
2655
3063
|
partialEval(vars) {
|
|
2656
3064
|
if (this instanceof Unit) {
|
|
2657
3065
|
return this;
|
|
@@ -2663,50 +3071,86 @@ class Expr {
|
|
|
2663
3071
|
return this.recurse("partialEval", vars);
|
|
2664
3072
|
}
|
|
2665
3073
|
}
|
|
3074
|
+
|
|
3075
|
+
// check that the structure of both expressions is the same
|
|
3076
|
+
// all negative signs are stripped and the expressions are converted to
|
|
3077
|
+
// a canonical commutative form
|
|
3078
|
+
// should only be done after compare() returns true to avoid false positives
|
|
2666
3079
|
sameForm(other) {
|
|
2667
3080
|
return this.strip().equals(other.strip());
|
|
2668
3081
|
}
|
|
3082
|
+
|
|
3083
|
+
// returns the GCD of this expression and the given factor
|
|
2669
3084
|
findGCD(factor) {
|
|
2670
3085
|
return this.equals(factor) ? factor : NumOne;
|
|
2671
3086
|
}
|
|
3087
|
+
|
|
3088
|
+
// return this expression's denominator
|
|
2672
3089
|
getDenominator() {
|
|
2673
3090
|
return NumOne;
|
|
2674
3091
|
}
|
|
3092
|
+
|
|
3093
|
+
// return this expression as a Mul
|
|
2675
3094
|
asMul() {
|
|
2676
3095
|
return new Mul(NumOne, this);
|
|
2677
3096
|
}
|
|
3097
|
+
|
|
3098
|
+
// TODO(alex): rename to isDefinitePositive or similar?
|
|
3099
|
+
// return whether this expression is 100% positive
|
|
2678
3100
|
isPositive() {
|
|
2679
|
-
throw new Error("Abstract method - must override for expr: " +
|
|
3101
|
+
throw new Error("Abstract method - must override for expr: " +
|
|
3102
|
+
// eslint-disable-next-line @babel/no-invalid-this
|
|
3103
|
+
this.print());
|
|
2680
3104
|
}
|
|
3105
|
+
|
|
3106
|
+
// TODO(alex): rename to hasNegativeSign or similar?
|
|
3107
|
+
// return whether this expression has a negative sign
|
|
2681
3108
|
isNegative() {
|
|
2682
3109
|
return false;
|
|
2683
3110
|
}
|
|
3111
|
+
|
|
3112
|
+
// return a factor of this expression that is 100% positive
|
|
2684
3113
|
asPositiveFactor() {
|
|
2685
3114
|
return this.isPositive() ? this : NumOne;
|
|
2686
3115
|
}
|
|
3116
|
+
|
|
3117
|
+
// return a copy of the expression with a new hint set (preserves hints)
|
|
2687
3118
|
addHint(hint) {
|
|
2688
3119
|
if (!hint) {
|
|
2689
3120
|
return this;
|
|
2690
3121
|
}
|
|
2691
3122
|
var expr = this.construct(this.args());
|
|
2692
|
-
expr.hints =
|
|
3123
|
+
expr.hints = ___default.default.clone(this.hints);
|
|
2693
3124
|
expr.hints[hint] = true;
|
|
2694
3125
|
return expr;
|
|
2695
3126
|
}
|
|
3127
|
+
|
|
3128
|
+
// complete parse by performing a few necessary transformations
|
|
2696
3129
|
completeParse() {
|
|
2697
3130
|
return this.recurse("completeParse");
|
|
2698
3131
|
}
|
|
2699
3132
|
abs() {
|
|
2700
|
-
throw new Error("Abstract method - must override for expr: " +
|
|
3133
|
+
throw new Error("Abstract method - must override for expr: " +
|
|
3134
|
+
// eslint-disable-next-line @babel/no-invalid-this
|
|
3135
|
+
this.print());
|
|
2701
3136
|
}
|
|
2702
3137
|
negate() {
|
|
2703
3138
|
return new Mul(NumNeg, this);
|
|
2704
3139
|
}
|
|
2705
3140
|
}
|
|
3141
|
+
|
|
3142
|
+
/* abstract sequence node */
|
|
2706
3143
|
class Seq extends Expr {
|
|
2707
|
-
|
|
3144
|
+
// This should always have at least two terms.
|
|
3145
|
+
// TODO(kevinb): Try enforcing this at the type-level using [T, T, T[]]
|
|
3146
|
+
terms;
|
|
3147
|
+
|
|
3148
|
+
// TODO(kevinb): Update this use `...args: Expr[]`
|
|
3149
|
+
constructor() {
|
|
2708
3150
|
super();
|
|
2709
|
-
|
|
3151
|
+
for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
|
|
3152
|
+
args[_key2] = arguments[_key2];
|
|
3153
|
+
}
|
|
2710
3154
|
if (args.length === 1) {
|
|
2711
3155
|
this.terms = args[0];
|
|
2712
3156
|
} else {
|
|
@@ -2717,7 +3161,7 @@ class Seq extends Expr {
|
|
|
2717
3161
|
return this.terms;
|
|
2718
3162
|
}
|
|
2719
3163
|
normalize() {
|
|
2720
|
-
var terms =
|
|
3164
|
+
var terms = ___default.default.sortBy(___default.default.invoke(this.terms, "normalize"), term => {
|
|
2721
3165
|
return term.print();
|
|
2722
3166
|
});
|
|
2723
3167
|
return new this.func(terms);
|
|
@@ -2725,35 +3169,54 @@ class Seq extends Expr {
|
|
|
2725
3169
|
expand() {
|
|
2726
3170
|
return this.recurse("expand").flatten();
|
|
2727
3171
|
}
|
|
3172
|
+
|
|
3173
|
+
// partition the sequence into its numeric and non-numeric parts
|
|
3174
|
+
// makes no guarantees about the validity of either part!
|
|
2728
3175
|
partition() {
|
|
2729
3176
|
var [numbers, others] = partition(this.terms, term => {
|
|
2730
3177
|
return term instanceof Num;
|
|
2731
3178
|
});
|
|
2732
3179
|
return [new this.func(numbers), new this.func(others)];
|
|
2733
3180
|
}
|
|
3181
|
+
|
|
3182
|
+
// ensure that sequences have 2+ terms and no nested sequences of the same type
|
|
3183
|
+
// this is a shallow flattening and will return a non-Seq if terms.length <= 1
|
|
2734
3184
|
flatten() {
|
|
2735
3185
|
var type = this;
|
|
2736
|
-
var terms =
|
|
3186
|
+
var terms = ___default.default.reject(this.terms, term => {
|
|
3187
|
+
// @ts-expect-error: `identity` is defined on Add and Mul but doesn't
|
|
3188
|
+
// exist on Seq itself.
|
|
2737
3189
|
return term.equals(type.identity);
|
|
2738
3190
|
});
|
|
2739
3191
|
if (terms.length === 0) {
|
|
3192
|
+
// @ts-expect-error: `identity` is defined on Add and Mul but doesn't
|
|
3193
|
+
// exist on Seq itself.
|
|
2740
3194
|
return type.identity;
|
|
2741
3195
|
}
|
|
2742
3196
|
if (terms.length === 1) {
|
|
2743
3197
|
return terms[0];
|
|
2744
3198
|
}
|
|
3199
|
+
|
|
3200
|
+
// same contains the children which are Seqs of the same type as this Seq
|
|
2745
3201
|
const [same, others] = partition(terms, term => {
|
|
2746
3202
|
return term instanceof type.func;
|
|
2747
3203
|
});
|
|
2748
|
-
var flattened = others.concat(
|
|
3204
|
+
var flattened = others.concat(___default.default.flatten(___default.default.pluck(same, "terms"), /* shallow: */true));
|
|
2749
3205
|
return new type.func(flattened);
|
|
2750
3206
|
}
|
|
3207
|
+
|
|
3208
|
+
// reduce a numeric sequence to a Num
|
|
3209
|
+
|
|
2751
3210
|
isPositive() {
|
|
2752
|
-
var terms =
|
|
2753
|
-
return
|
|
3211
|
+
var terms = ___default.default.invoke(this.terms, "collect");
|
|
3212
|
+
return ___default.default.all(___default.default.invoke(terms, "isPositive"));
|
|
2754
3213
|
}
|
|
3214
|
+
|
|
3215
|
+
// return a new Seq with a given term replaced by a different term
|
|
3216
|
+
// (or array of terms). given term can be passed directly, or by index
|
|
3217
|
+
// if no new term is provided, the old one is simply removed
|
|
2755
3218
|
replace(oldTerm, newTerm) {
|
|
2756
|
-
const index = oldTerm instanceof Expr ?
|
|
3219
|
+
const index = oldTerm instanceof Expr ? ___default.default.indexOf(this.terms, oldTerm) : oldTerm;
|
|
2757
3220
|
var newTerms = [];
|
|
2758
3221
|
if (Array.isArray(newTerm)) {
|
|
2759
3222
|
newTerms = newTerm;
|
|
@@ -2763,35 +3226,39 @@ class Seq extends Expr {
|
|
|
2763
3226
|
var terms = this.terms.slice(0, index).concat(newTerms).concat(this.terms.slice(index + 1));
|
|
2764
3227
|
return new this.func(terms);
|
|
2765
3228
|
}
|
|
3229
|
+
|
|
3230
|
+
// syntactic sugar for replace()
|
|
2766
3231
|
remove(term) {
|
|
2767
3232
|
return this.replace(term);
|
|
2768
3233
|
}
|
|
2769
3234
|
getDenominator() {
|
|
2770
|
-
|
|
3235
|
+
// TODO(alex): find and return LCM
|
|
3236
|
+
return new Mul(___default.default.invoke(this.terms, "getDenominator")).flatten();
|
|
2771
3237
|
}
|
|
2772
3238
|
}
|
|
3239
|
+
|
|
3240
|
+
/* sequence of additive terms */
|
|
2773
3241
|
class Add extends Seq {
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
return _.reduce(this.terms, (memo, term) => {
|
|
3242
|
+
identity = NumZero;
|
|
3243
|
+
func = Add;
|
|
3244
|
+
eval() {
|
|
3245
|
+
let vars = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
3246
|
+
let options = arguments.length > 1 ? arguments[1] : undefined;
|
|
3247
|
+
return ___default.default.reduce(this.terms, (memo, term) => {
|
|
2781
3248
|
return memo + term.eval(vars, options);
|
|
2782
3249
|
}, 0);
|
|
2783
3250
|
}
|
|
2784
3251
|
codegen() {
|
|
2785
|
-
return
|
|
3252
|
+
return ___default.default.map(this.terms, term => {
|
|
2786
3253
|
return "(" + term.codegen() + ")";
|
|
2787
3254
|
}).join(" + ") || "0";
|
|
2788
3255
|
}
|
|
2789
3256
|
print() {
|
|
2790
|
-
return
|
|
3257
|
+
return ___default.default.invoke(this.terms, "print").join("+");
|
|
2791
3258
|
}
|
|
2792
3259
|
tex() {
|
|
2793
3260
|
let tex = "";
|
|
2794
|
-
|
|
3261
|
+
___default.default.each(this.terms, term => {
|
|
2795
3262
|
if (!tex || term.isSubtract()) {
|
|
2796
3263
|
tex += term.tex();
|
|
2797
3264
|
} else {
|
|
@@ -2801,9 +3268,9 @@ class Add extends Seq {
|
|
|
2801
3268
|
return tex;
|
|
2802
3269
|
}
|
|
2803
3270
|
collect(options) {
|
|
2804
|
-
var terms =
|
|
3271
|
+
var terms = ___default.default.invoke(this.terms, "collect", options);
|
|
2805
3272
|
var pairs = [];
|
|
2806
|
-
|
|
3273
|
+
___default.default.each(terms, term => {
|
|
2807
3274
|
if (term instanceof Mul) {
|
|
2808
3275
|
var muls = term.partition();
|
|
2809
3276
|
pairs.push([muls[1].flatten(), muls[0].reduce(options)]);
|
|
@@ -2813,20 +3280,30 @@ class Add extends Seq {
|
|
|
2813
3280
|
pairs.push([term, NumOne]);
|
|
2814
3281
|
}
|
|
2815
3282
|
});
|
|
2816
|
-
|
|
3283
|
+
|
|
3284
|
+
// { (Expr expr).print(): [[Expr expr, Num coefficient]] }
|
|
3285
|
+
var grouped = ___default.default.groupBy(pairs, pair => {
|
|
2817
3286
|
return pair[0].normalize().print();
|
|
2818
3287
|
});
|
|
2819
|
-
var collected =
|
|
3288
|
+
var collected = ___default.default.compact(___default.default.map(grouped, pairs => {
|
|
2820
3289
|
var expr = pairs[0][0];
|
|
2821
|
-
var sum = new Add(
|
|
3290
|
+
var sum = new Add(___default.default.zip.apply(___default.default, pairs)[1]);
|
|
2822
3291
|
var coefficient = sum.reduce(options);
|
|
2823
3292
|
return new Mul(coefficient, expr).collect(options);
|
|
2824
3293
|
}));
|
|
3294
|
+
|
|
3295
|
+
// TODO(alex): use the Pythagorean identity here
|
|
3296
|
+
// e.g. x*sin^2(y) + x*cos^2(y) -> x
|
|
3297
|
+
|
|
2825
3298
|
return new Add(collected).flatten();
|
|
2826
3299
|
}
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
3300
|
+
|
|
3301
|
+
// naively factor out anything that is common to all terms
|
|
3302
|
+
// if options.keepNegative is specified, won't factor out a common -1
|
|
3303
|
+
factor() {
|
|
3304
|
+
let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {
|
|
3305
|
+
keepNegative: false
|
|
3306
|
+
};
|
|
2830
3307
|
const terms = this.terms.map(term => term.collect());
|
|
2831
3308
|
let factors;
|
|
2832
3309
|
if (terms[0] instanceof Mul) {
|
|
@@ -2834,8 +3311,8 @@ class Add extends Seq {
|
|
|
2834
3311
|
} else {
|
|
2835
3312
|
factors = [terms[0]];
|
|
2836
3313
|
}
|
|
2837
|
-
|
|
2838
|
-
factors =
|
|
3314
|
+
___default.default.each(___default.default.rest(this.terms), term => {
|
|
3315
|
+
factors = ___default.default.map(factors, factor => {
|
|
2839
3316
|
return term.findGCD(factor);
|
|
2840
3317
|
});
|
|
2841
3318
|
});
|
|
@@ -2848,7 +3325,9 @@ class Add extends Seq {
|
|
|
2848
3325
|
return Mul.createOrAppend(left, right).flatten();
|
|
2849
3326
|
}
|
|
2850
3327
|
reduce(options) {
|
|
2851
|
-
return
|
|
3328
|
+
return ___default.default.reduce(this.terms,
|
|
3329
|
+
// @ts-expect-error: Type 'Expr' is not assignable to type 'Num'.
|
|
3330
|
+
(memo, term) => {
|
|
2852
3331
|
return memo.add(term, options);
|
|
2853
3332
|
}, this.identity);
|
|
2854
3333
|
}
|
|
@@ -2856,12 +3335,14 @@ class Add extends Seq {
|
|
|
2856
3335
|
return false;
|
|
2857
3336
|
}
|
|
2858
3337
|
isNegative() {
|
|
2859
|
-
var terms =
|
|
2860
|
-
return
|
|
3338
|
+
var terms = ___default.default.invoke(this.terms, "collect");
|
|
3339
|
+
return ___default.default.all(___default.default.invoke(terms, "isNegative"));
|
|
2861
3340
|
}
|
|
2862
3341
|
negate() {
|
|
2863
|
-
return new Add(
|
|
3342
|
+
return new Add(___default.default.invoke(this.terms, "negate"));
|
|
2864
3343
|
}
|
|
3344
|
+
|
|
3345
|
+
// create a new sequence unless left is already one (returns a copy)
|
|
2865
3346
|
static createOrAppend(left, right) {
|
|
2866
3347
|
if (left instanceof Add) {
|
|
2867
3348
|
return new Add(left.terms.concat(right));
|
|
@@ -2870,37 +3351,41 @@ class Add extends Seq {
|
|
|
2870
3351
|
}
|
|
2871
3352
|
}
|
|
2872
3353
|
}
|
|
3354
|
+
|
|
3355
|
+
/* sequence of multiplicative terms */
|
|
2873
3356
|
class Mul extends Seq {
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
return _.reduce(this.terms, (memo, term) => {
|
|
3357
|
+
identity = NumOne;
|
|
3358
|
+
func = Mul;
|
|
3359
|
+
eval() {
|
|
3360
|
+
let vars = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
3361
|
+
let options = arguments.length > 1 ? arguments[1] : undefined;
|
|
3362
|
+
return ___default.default.reduce(this.terms, (memo, term) => {
|
|
2881
3363
|
return memo * term.eval(vars, options);
|
|
2882
3364
|
}, 1);
|
|
2883
3365
|
}
|
|
2884
3366
|
codegen() {
|
|
2885
|
-
return
|
|
3367
|
+
return ___default.default.map(this.terms, term => {
|
|
2886
3368
|
return "(" + term.codegen() + ")";
|
|
2887
3369
|
}).join(" * ") || "0";
|
|
2888
3370
|
}
|
|
2889
3371
|
print() {
|
|
2890
|
-
return
|
|
3372
|
+
return ___default.default.map(this.terms, term => {
|
|
2891
3373
|
return term instanceof Add ? "(" + term.print() + ")" : term.print();
|
|
2892
3374
|
}).join("*");
|
|
2893
3375
|
}
|
|
2894
3376
|
getUnits() {
|
|
2895
|
-
var tmUnits =
|
|
3377
|
+
var tmUnits = ___default.default(this.terms).chain().map(term => {
|
|
2896
3378
|
return term.getUnits();
|
|
2897
3379
|
}).flatten().value();
|
|
2898
3380
|
tmUnits.sort((a, b) => a.unit.localeCompare(b.unit));
|
|
2899
3381
|
return tmUnits;
|
|
2900
3382
|
}
|
|
3383
|
+
|
|
3384
|
+
// since we don't care about commutativity, we can render a Mul any way we choose
|
|
3385
|
+
// so we follow convention: first any negatives, then any numbers, then everything else
|
|
2901
3386
|
tex() {
|
|
2902
3387
|
var cdot = " \\cdot ";
|
|
2903
|
-
var terms =
|
|
3388
|
+
var terms = ___default.default.groupBy(this.terms, term => {
|
|
2904
3389
|
if (term.isDivide()) {
|
|
2905
3390
|
return "inverse";
|
|
2906
3391
|
} else if (term instanceof Num) {
|
|
@@ -2909,11 +3394,19 @@ class Mul extends Seq {
|
|
|
2909
3394
|
return "other";
|
|
2910
3395
|
}
|
|
2911
3396
|
});
|
|
3397
|
+
|
|
3398
|
+
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
|
2912
3399
|
var inverses = terms.inverse || [];
|
|
3400
|
+
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
|
2913
3401
|
var numbers = terms.number || [];
|
|
3402
|
+
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
|
2914
3403
|
var others = terms.other || [];
|
|
2915
3404
|
var negatives = "";
|
|
2916
3405
|
var numerator;
|
|
3406
|
+
|
|
3407
|
+
// check all the numbers to see if there is a rational we can extract,
|
|
3408
|
+
// since we would like 1/2x/y to come out as \frac{1}{2}\frac{x}{y},
|
|
3409
|
+
// and not \frac{1x}{2y}.
|
|
2917
3410
|
for (var i = 0; i < numbers.length; i++) {
|
|
2918
3411
|
var isRational = numbers[i] instanceof Rational && !(numbers[i] instanceof Int);
|
|
2919
3412
|
if (isRational && others.length > 0 && inverses.length > 0) {
|
|
@@ -2923,84 +3416,102 @@ class Mul extends Seq {
|
|
|
2923
3416
|
return numbers[i].tex() + new Mul(newTerms).tex();
|
|
2924
3417
|
}
|
|
2925
3418
|
}
|
|
2926
|
-
numbers =
|
|
3419
|
+
numbers = ___default.default.compact(___default.default.map(numbers, term => {
|
|
2927
3420
|
var shouldPushDown = !term.hints.fraction || inverses.length > 0;
|
|
2928
3421
|
if (term instanceof Rational && !(term instanceof Int) && shouldPushDown) {
|
|
3422
|
+
// e.g. 3x/4 -> 3/4*x (internally) -> 3x/4 (rendered)
|
|
2929
3423
|
inverses.push(new Pow(new Int(term.d), NumDiv));
|
|
2930
3424
|
var number = new Int(term.n);
|
|
2931
3425
|
number.hints = term.hints;
|
|
2932
|
-
return
|
|
3426
|
+
return ___default.default.any(term.hints) ? number : null;
|
|
2933
3427
|
} else {
|
|
2934
3428
|
return term;
|
|
2935
3429
|
}
|
|
2936
3430
|
}));
|
|
2937
3431
|
if (numbers.length === 0 && others.length === 1) {
|
|
3432
|
+
// e.g. (x+y)/z -> \frac{x+y}{z}
|
|
2938
3433
|
numerator = others[0].tex();
|
|
2939
3434
|
} else {
|
|
2940
3435
|
var tex = "";
|
|
2941
|
-
|
|
3436
|
+
___default.default.each(numbers, term => {
|
|
2942
3437
|
if (term.hints.subtract && term.hints.entered) {
|
|
2943
3438
|
negatives += "-";
|
|
2944
3439
|
tex += (tex ? cdot : "") + term.abs().tex();
|
|
2945
3440
|
} else if (term instanceof Int && term.n === -1 && (term.hints.negate || term.hints.subtract)) {
|
|
3441
|
+
// e.g. -1*-1 -> --1
|
|
3442
|
+
// e.g. -1*x -> -x
|
|
2946
3443
|
negatives += "-";
|
|
2947
3444
|
} else {
|
|
3445
|
+
// e.g. 2*3 -> 2(dot)3
|
|
2948
3446
|
tex += (tex ? cdot : "") + term.tex();
|
|
2949
3447
|
}
|
|
2950
3448
|
});
|
|
2951
|
-
|
|
3449
|
+
___default.default.each(others, term => {
|
|
2952
3450
|
if (term.needsExplicitMul()) {
|
|
3451
|
+
// e.g. 2*2^3 -> 2(dot)2^3
|
|
2953
3452
|
tex += (tex ? cdot : "") + term.tex();
|
|
2954
3453
|
} else if (term instanceof Add) {
|
|
3454
|
+
// e.g. (a+b)*c -> (a+b)c
|
|
2955
3455
|
tex += "(" + term.tex() + ")";
|
|
2956
3456
|
} else {
|
|
3457
|
+
// e.g. a*b*c -> abc
|
|
2957
3458
|
tex += term.tex();
|
|
2958
3459
|
}
|
|
2959
3460
|
});
|
|
2960
3461
|
numerator = tex ? tex : "1";
|
|
2961
3462
|
}
|
|
3463
|
+
|
|
3464
|
+
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
|
2962
3465
|
if (!inverses.length) {
|
|
2963
3466
|
return negatives + numerator;
|
|
2964
3467
|
} else {
|
|
2965
|
-
var denominator = new Mul(
|
|
3468
|
+
var denominator = new Mul(___default.default.invoke(inverses, "asDivide")).flatten().tex();
|
|
2966
3469
|
return negatives + "\\frac{" + numerator + "}{" + denominator + "}";
|
|
2967
3470
|
}
|
|
2968
3471
|
}
|
|
2969
3472
|
strip() {
|
|
2970
|
-
var terms =
|
|
3473
|
+
var terms = ___default.default.map(this.terms, term => {
|
|
2971
3474
|
return term instanceof Num ? term.abs() : term.strip();
|
|
2972
3475
|
});
|
|
2973
3476
|
return new Mul(terms).flatten();
|
|
2974
3477
|
}
|
|
3478
|
+
|
|
3479
|
+
// expand numerator and denominator separately
|
|
2975
3480
|
expand() {
|
|
2976
|
-
const isInverse = function
|
|
3481
|
+
const isInverse = function (term) {
|
|
2977
3482
|
return term instanceof Pow && term.exp.isNegative();
|
|
2978
3483
|
};
|
|
2979
|
-
const isInverseAdd = function
|
|
3484
|
+
const isInverseAdd = function (term) {
|
|
2980
3485
|
return isInverse(term) && isAdd(term.base);
|
|
2981
3486
|
};
|
|
2982
3487
|
const mul = this.recurse("expand").flatten();
|
|
2983
3488
|
const factors = getFactors(mul);
|
|
2984
|
-
const hasAdd =
|
|
2985
|
-
const hasInverseAdd =
|
|
3489
|
+
const hasAdd = ___default.default.any(factors, isAdd);
|
|
3490
|
+
const hasInverseAdd = ___default.default.any(factors, isInverseAdd);
|
|
2986
3491
|
if (!(hasAdd || hasInverseAdd)) {
|
|
2987
3492
|
return mul;
|
|
2988
3493
|
}
|
|
2989
3494
|
let [inverses, normals] = partition(factors, isInverse);
|
|
2990
3495
|
if (hasAdd) {
|
|
2991
3496
|
const [adds, others] = partition(normals, isAdd);
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
3497
|
+
|
|
3498
|
+
// loop over each additive sequence
|
|
3499
|
+
const expanded = ___default.default.reduce(adds, function (expanded, add) {
|
|
3500
|
+
// loop over each expanded array of terms
|
|
3501
|
+
return ___default.default.reduce(expanded, function (temp, array) {
|
|
3502
|
+
// loop over each additive sequence's terms
|
|
3503
|
+
return temp.concat(___default.default.map(add.terms, term => array.concat(term)));
|
|
2995
3504
|
}, []);
|
|
2996
3505
|
}, [[]]);
|
|
2997
|
-
|
|
3506
|
+
|
|
3507
|
+
// join each fully expanded array of factors with remaining multiplicative factors
|
|
3508
|
+
const muls = ___default.default.map(expanded, function (array) {
|
|
2998
3509
|
return new Mul(others.concat(array)).flatten();
|
|
2999
3510
|
});
|
|
3000
3511
|
normals = [new Add(muls)];
|
|
3001
3512
|
}
|
|
3002
3513
|
if (hasInverseAdd) {
|
|
3003
|
-
const denominator = new Mul(
|
|
3514
|
+
const denominator = new Mul(___default.default.invoke(inverses, "getDenominator")).flatten();
|
|
3004
3515
|
inverses = [new Pow(denominator.expand(), NumDiv)];
|
|
3005
3516
|
}
|
|
3006
3517
|
return new Mul(normals.concat(inverses)).flatten();
|
|
@@ -3010,10 +3521,16 @@ class Mul extends Seq {
|
|
|
3010
3521
|
if (!(factored instanceof Mul)) {
|
|
3011
3522
|
return factored;
|
|
3012
3523
|
}
|
|
3524
|
+
|
|
3525
|
+
// Combine any factored out Rationals into one, but don't collect
|
|
3013
3526
|
var [rationals, others] = partition(factored.terms, term => {
|
|
3014
3527
|
return term instanceof Rational;
|
|
3015
3528
|
});
|
|
3016
|
-
|
|
3529
|
+
|
|
3530
|
+
// Could also accomplish this by passing a new option
|
|
3531
|
+
// e.g. return memo.mul(term, {autocollect: false});
|
|
3532
|
+
// TODO(alex): Decide whether this is a good use of options or not
|
|
3533
|
+
const ratObj = ___default.default.reduce(rationals, (memo, term) => {
|
|
3017
3534
|
return {
|
|
3018
3535
|
n: memo.n * term.n,
|
|
3019
3536
|
d: memo.d * term.d
|
|
@@ -3028,28 +3545,35 @@ class Mul extends Seq {
|
|
|
3028
3545
|
collect(options) {
|
|
3029
3546
|
var partitioned = this.recurse("collect", options).partition();
|
|
3030
3547
|
var number = partitioned[0].reduce(options);
|
|
3548
|
+
|
|
3549
|
+
// e.g. 0*x -> 0
|
|
3031
3550
|
if (number.eval() === 0) {
|
|
3032
3551
|
return NumZero;
|
|
3033
3552
|
}
|
|
3034
3553
|
const other = partitioned[1].flatten();
|
|
3554
|
+
|
|
3555
|
+
// e.g. 2*2 -> 4
|
|
3556
|
+
// e.g. 2*2*x -> 4*x
|
|
3035
3557
|
if (!(other instanceof Mul)) {
|
|
3036
3558
|
return new Mul(number, other).flatten();
|
|
3037
3559
|
}
|
|
3038
3560
|
const others = other.terms;
|
|
3039
3561
|
var pairs = [];
|
|
3040
|
-
|
|
3562
|
+
___default.default.each(others, term => {
|
|
3041
3563
|
if (term instanceof Pow) {
|
|
3042
3564
|
pairs.push([term.base, term.exp]);
|
|
3043
3565
|
} else {
|
|
3044
3566
|
pairs.push([term, NumOne]);
|
|
3045
3567
|
}
|
|
3046
3568
|
});
|
|
3047
|
-
|
|
3569
|
+
|
|
3570
|
+
// {(Expr base).print(): [[Expr base, Expr exp]]}
|
|
3571
|
+
var grouped = ___default.default.groupBy(pairs, pair => {
|
|
3048
3572
|
return pair[0].normalize().print();
|
|
3049
3573
|
});
|
|
3050
|
-
var summed =
|
|
3574
|
+
var summed = ___default.default.compact(___default.default.map(grouped, pairs => {
|
|
3051
3575
|
var base = pairs[0][0];
|
|
3052
|
-
var sum = new Add(
|
|
3576
|
+
var sum = new Add(___default.default.zip.apply(___default.default, pairs)[1]);
|
|
3053
3577
|
var exp = sum.collect(options);
|
|
3054
3578
|
if (exp instanceof Num && exp.eval() === 0) {
|
|
3055
3579
|
return null;
|
|
@@ -3057,7 +3581,9 @@ class Mul extends Seq {
|
|
|
3057
3581
|
return [base, exp];
|
|
3058
3582
|
}
|
|
3059
3583
|
}));
|
|
3060
|
-
|
|
3584
|
+
|
|
3585
|
+
// XXX `pairs` is shadowed four or five times in this function
|
|
3586
|
+
const groupedPairs = ___default.default.groupBy(summed, pair => {
|
|
3061
3587
|
if (pair[0] instanceof Trig && pair[0].isBasic()) {
|
|
3062
3588
|
return "trig";
|
|
3063
3589
|
} else if (pair[0] instanceof Log) {
|
|
@@ -3066,24 +3592,33 @@ class Mul extends Seq {
|
|
|
3066
3592
|
return "expr";
|
|
3067
3593
|
}
|
|
3068
3594
|
});
|
|
3595
|
+
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
|
3069
3596
|
let trigs = groupedPairs.trig || [];
|
|
3597
|
+
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
|
3070
3598
|
let logs = groupedPairs.log || [];
|
|
3599
|
+
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
|
3071
3600
|
const exprs = groupedPairs.expr || [];
|
|
3072
3601
|
if (trigs.length > 1) {
|
|
3073
|
-
|
|
3602
|
+
// combine sines and cosines into other trig functions
|
|
3603
|
+
|
|
3604
|
+
// {Trig.arg.print(): [[Trig base, Expr exp]]}
|
|
3605
|
+
var byArg = ___default.default.groupBy(trigs, pair => {
|
|
3074
3606
|
return pair[0].arg.normalize().print();
|
|
3075
3607
|
});
|
|
3076
3608
|
trigs = [];
|
|
3077
|
-
|
|
3609
|
+
___default.default.each(byArg, pairs => {
|
|
3078
3610
|
const arg = pairs[0][0].arg;
|
|
3611
|
+
|
|
3612
|
+
// {Trig.type: Expr exp}
|
|
3079
3613
|
let funcs = {
|
|
3080
3614
|
sin: NumZero,
|
|
3081
3615
|
cos: NumZero
|
|
3082
3616
|
};
|
|
3083
|
-
|
|
3617
|
+
___default.default.each(pairs, pair => {
|
|
3084
3618
|
funcs[pair[0].type] = pair[1];
|
|
3085
3619
|
});
|
|
3086
3620
|
if (Mul.handleNegative(funcs.sin).collect(options).equals(funcs.cos)) {
|
|
3621
|
+
// e.g. sin^x(y)/cos^x(y) -> tan^x(y)
|
|
3087
3622
|
if (funcs.cos.isNegative()) {
|
|
3088
3623
|
funcs = {
|
|
3089
3624
|
tan: funcs.sin
|
|
@@ -3094,18 +3629,30 @@ class Mul extends Seq {
|
|
|
3094
3629
|
};
|
|
3095
3630
|
}
|
|
3096
3631
|
}
|
|
3097
|
-
|
|
3632
|
+
|
|
3633
|
+
// TODO(alex): combine even if exponents not a perfect match
|
|
3634
|
+
// TODO(alex): transform 1/sin and 1/cos into csc and sec
|
|
3635
|
+
|
|
3636
|
+
___default.default.each(funcs, (exp, type) => {
|
|
3098
3637
|
trigs.push([new Trig(type, arg), exp]);
|
|
3099
3638
|
});
|
|
3100
3639
|
});
|
|
3101
3640
|
}
|
|
3102
3641
|
if (logs.length > 1) {
|
|
3103
|
-
|
|
3642
|
+
// combine logs with the same base
|
|
3643
|
+
|
|
3644
|
+
// {Log.base.print(): [[Log base, Expr exp]]}
|
|
3645
|
+
var byBase = ___default.default.groupBy(logs, pair => {
|
|
3104
3646
|
return pair[0].base.normalize().print();
|
|
3105
3647
|
});
|
|
3106
3648
|
logs = [];
|
|
3107
|
-
|
|
3649
|
+
___default.default.each(byBase, pairs => {
|
|
3650
|
+
// only combine two logs of the same base, otherwise commutative
|
|
3651
|
+
// differences result in different equally valid output
|
|
3652
|
+
// e.g. ln(x)/ln(z)*ln(y) -> log_z(x)*ln(y)
|
|
3653
|
+
// e.g. ln(x)*ln(y)/ln(z) -> ln(x)*log_z(y)
|
|
3108
3654
|
if (pairs.length === 2 && Mul.handleNegative(pairs[0][1]).collect(options).equals(pairs[1][1])) {
|
|
3655
|
+
// e.g. ln(x)^y/ln(b)^y -> log_b(x)^y
|
|
3109
3656
|
if (pairs[0][1].isNegative()) {
|
|
3110
3657
|
logs.push([new Log(pairs[0][0].power, pairs[1][0].power), pairs[1][1]]);
|
|
3111
3658
|
} else {
|
|
@@ -3115,32 +3662,48 @@ class Mul extends Seq {
|
|
|
3115
3662
|
logs = logs.concat(pairs);
|
|
3116
3663
|
}
|
|
3117
3664
|
});
|
|
3665
|
+
|
|
3666
|
+
// TODO(alex): combine if all inverses are the same e.g. ln(y)*ln(z)/ln(x)/ln(x)
|
|
3118
3667
|
}
|
|
3119
|
-
var collected =
|
|
3668
|
+
var collected = ___default.default.map([...trigs, ...logs, ...exprs], pair => {
|
|
3120
3669
|
return new Pow(pair[0], pair[1]).collect(options);
|
|
3121
3670
|
});
|
|
3122
3671
|
return new Mul([number].concat(collected)).flatten();
|
|
3123
3672
|
}
|
|
3124
3673
|
isSubtract() {
|
|
3125
|
-
return
|
|
3674
|
+
return ___default.default.any(this.terms, term => {
|
|
3126
3675
|
return term instanceof Num && Boolean(term.hints.subtract);
|
|
3127
3676
|
});
|
|
3128
3677
|
}
|
|
3678
|
+
|
|
3679
|
+
// factor a single -1 in to the Mul
|
|
3680
|
+
// combine with a Num if all Nums are positive, else add as a term
|
|
3129
3681
|
factorIn(hint) {
|
|
3130
3682
|
var partitioned = this.partition();
|
|
3683
|
+
// `partition` splits the terms into two Seqs - one containing
|
|
3684
|
+
// only Nums and the all non-Num nodes.
|
|
3131
3685
|
var numbers = partitioned[0].terms;
|
|
3132
|
-
|
|
3686
|
+
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
|
3687
|
+
var fold = numbers.length && ___default.default.all(numbers, num => num.n > 0);
|
|
3688
|
+
|
|
3689
|
+
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
|
3133
3690
|
if (fold) {
|
|
3691
|
+
// e.g. - x*2*3 -> x*-2*3
|
|
3134
3692
|
var num = numbers[0].negate();
|
|
3135
3693
|
num.hints = numbers[0].hints;
|
|
3136
3694
|
return this.replace(numbers[0], num.addHint(hint));
|
|
3137
3695
|
} else {
|
|
3696
|
+
// e.g. - x*y -> -1*x*y
|
|
3697
|
+
// e.g. - x*-2 -> -1*x*-2
|
|
3138
3698
|
return new Mul([Num.negativeOne(hint)].concat(this.terms));
|
|
3139
3699
|
}
|
|
3140
3700
|
}
|
|
3701
|
+
|
|
3702
|
+
// factor out a single hinted -1 (assume it is the division hint)
|
|
3703
|
+
// TODO(alex): make more general or rename to be more specific
|
|
3141
3704
|
factorOut() {
|
|
3142
3705
|
var factored = false;
|
|
3143
|
-
var terms =
|
|
3706
|
+
var terms = ___default.default.compact(___default.default.map(this.terms, term => {
|
|
3144
3707
|
if (!factored && term instanceof Num && term.hints.divide) {
|
|
3145
3708
|
factored = true;
|
|
3146
3709
|
return term.n !== -1 ? term.negate() : null;
|
|
@@ -3155,12 +3718,14 @@ class Mul extends Seq {
|
|
|
3155
3718
|
}
|
|
3156
3719
|
}
|
|
3157
3720
|
reduce(options) {
|
|
3158
|
-
return
|
|
3721
|
+
return ___default.default.reduce(this.terms,
|
|
3722
|
+
// @ts-expect-error: Type 'Expr' is not assignable to type 'Num'.
|
|
3723
|
+
(memo, term) => {
|
|
3159
3724
|
return memo.mul(term, options);
|
|
3160
3725
|
}, this.identity);
|
|
3161
3726
|
}
|
|
3162
3727
|
findGCD(factor) {
|
|
3163
|
-
return new Mul(
|
|
3728
|
+
return new Mul(___default.default.invoke(this.terms, "findGCD", factor)).flatten();
|
|
3164
3729
|
}
|
|
3165
3730
|
asMul() {
|
|
3166
3731
|
return this;
|
|
@@ -3175,7 +3740,7 @@ class Mul extends Seq {
|
|
|
3175
3740
|
}
|
|
3176
3741
|
isNegative() {
|
|
3177
3742
|
const terms = getFactors(this.collect()).map(factor => factor.isNegative());
|
|
3178
|
-
return
|
|
3743
|
+
return ___default.default.any(terms);
|
|
3179
3744
|
}
|
|
3180
3745
|
fold() {
|
|
3181
3746
|
return Mul.fold(this);
|
|
@@ -3184,28 +3749,42 @@ class Mul extends Seq {
|
|
|
3184
3749
|
var isNum = expr => {
|
|
3185
3750
|
return expr instanceof Num;
|
|
3186
3751
|
};
|
|
3187
|
-
const num =
|
|
3752
|
+
const num = ___default.default.find(this.terms, isNum);
|
|
3188
3753
|
if (num) {
|
|
3189
3754
|
return this.replace(num, num.negate());
|
|
3190
3755
|
} else {
|
|
3191
3756
|
return new Mul([NumNeg].concat(this.terms));
|
|
3192
3757
|
}
|
|
3193
3758
|
}
|
|
3759
|
+
|
|
3760
|
+
// negative signs should be folded into numbers whenever possible
|
|
3761
|
+
// never fold into a Num that's already negative or a Mul that has a negative Num
|
|
3762
|
+
// an optional hint is kept track of to properly render user input
|
|
3763
|
+
// an empty hint means negation
|
|
3194
3764
|
static handleNegative(expr, hint) {
|
|
3195
3765
|
if (expr instanceof Num && expr.n > 0) {
|
|
3766
|
+
// e.g. - 2 -> -2
|
|
3196
3767
|
var negated = expr.negate();
|
|
3768
|
+
// TODO(alex): rework hint system so that this isn't necessary
|
|
3197
3769
|
negated.hints = expr.hints;
|
|
3198
3770
|
return negated.addHint(hint);
|
|
3199
3771
|
} else if (expr instanceof Mul) {
|
|
3772
|
+
// e.g. - x*2*3 -> x*-2*3
|
|
3773
|
+
// e.g. - x*y -> -1*x*y
|
|
3774
|
+
// e.g. - x*-2 -> -1*x*-2
|
|
3200
3775
|
return expr.factorIn(hint);
|
|
3201
3776
|
} else {
|
|
3777
|
+
// e.g. - x -> -1*x
|
|
3202
3778
|
return new Mul(Num.negativeOne(hint), expr);
|
|
3203
3779
|
}
|
|
3204
3780
|
}
|
|
3781
|
+
|
|
3782
|
+
// division can create either a Rational or a Mul
|
|
3205
3783
|
static handleDivide(left, right) {
|
|
3784
|
+
// dividing by a Mul is the same as repeated division by its terms
|
|
3206
3785
|
if (right instanceof Mul) {
|
|
3207
3786
|
var first = Mul.handleDivide(left, right.terms[0]);
|
|
3208
|
-
var rest = new Mul(
|
|
3787
|
+
var rest = new Mul(___default.default.rest(right.terms)).flatten();
|
|
3209
3788
|
return Mul.handleDivide(first, rest);
|
|
3210
3789
|
}
|
|
3211
3790
|
var isInt = expr => {
|
|
@@ -3214,7 +3793,11 @@ class Mul extends Seq {
|
|
|
3214
3793
|
var isRational = expr => {
|
|
3215
3794
|
return expr instanceof Rational;
|
|
3216
3795
|
};
|
|
3217
|
-
|
|
3796
|
+
|
|
3797
|
+
// for simplification purposes, fold Ints into Rationals if possible
|
|
3798
|
+
// e.g. 3x / 4 -> 3/4 * x (will still render as 3x/4)
|
|
3799
|
+
if (isInt(right) && left instanceof Mul && ___default.default.any(left.terms, isInt)) {
|
|
3800
|
+
// search from the right
|
|
3218
3801
|
var reversed = left.terms.slice().reverse();
|
|
3219
3802
|
var num = reversed.find(isRational);
|
|
3220
3803
|
if (!isInt(num)) {
|
|
@@ -3222,6 +3805,9 @@ class Mul extends Seq {
|
|
|
3222
3805
|
}
|
|
3223
3806
|
var rational = new Rational(num.n, right.n);
|
|
3224
3807
|
rational.hints = num.hints;
|
|
3808
|
+
|
|
3809
|
+
// in the case of something like 1/3 * 6/8, we want the
|
|
3810
|
+
// 6/8 to be considered a fraction, not just a division
|
|
3225
3811
|
if (num === reversed[0]) {
|
|
3226
3812
|
rational = rational.addHint("fraction");
|
|
3227
3813
|
}
|
|
@@ -3236,11 +3822,17 @@ class Mul extends Seq {
|
|
|
3236
3822
|
if (b instanceof Int) {
|
|
3237
3823
|
if (a instanceof Int) {
|
|
3238
3824
|
if (a.n < 0 && b.n < 0) {
|
|
3825
|
+
// e.g. -2 / -3 -> -1*-2/3
|
|
3239
3826
|
return [NumNeg, new Rational(a.n, -b.n).addHint("fraction")];
|
|
3240
3827
|
} else {
|
|
3828
|
+
// e.g. 2 / 3 -> 2/3
|
|
3829
|
+
// e.g. -2 / 3 -> -2/3
|
|
3830
|
+
// e.g. 2 / -3 -> -2/3
|
|
3241
3831
|
return [new Rational(a.n, b.n).addHint("fraction")];
|
|
3242
3832
|
}
|
|
3243
3833
|
} else {
|
|
3834
|
+
// e.g. x / 3 -> x*1/3
|
|
3835
|
+
// e.g. x / -3 -> x*-1/3
|
|
3244
3836
|
var inverse = new Rational(1, b.eval());
|
|
3245
3837
|
if (b.eval() < 0) {
|
|
3246
3838
|
return [a, inverse.addHint("negate")];
|
|
@@ -3251,42 +3843,66 @@ class Mul extends Seq {
|
|
|
3251
3843
|
} else {
|
|
3252
3844
|
var pow;
|
|
3253
3845
|
if (b instanceof Trig && b.exp) {
|
|
3846
|
+
// e.g. sin^2(x) -> sin(x)^2
|
|
3254
3847
|
var exp = b.exp;
|
|
3255
3848
|
b.exp = undefined;
|
|
3256
3849
|
b = new Pow(b, exp);
|
|
3257
3850
|
}
|
|
3258
3851
|
if (b instanceof Pow) {
|
|
3852
|
+
// e.g. (x^2) ^ -1 -> x^-2
|
|
3853
|
+
// e.g. (x^y) ^ -1 -> x^(-1*y)
|
|
3854
|
+
// e.g. (x^(yz)) ^ -1 -> x^(-1*y*z)
|
|
3259
3855
|
pow = new Pow(b.base, Mul.handleNegative(b.exp, "divide"));
|
|
3260
3856
|
} else {
|
|
3857
|
+
// e.g. x ^ -1 -> x^-1
|
|
3261
3858
|
pow = new Pow(b, NumDiv);
|
|
3262
3859
|
}
|
|
3263
3860
|
if (a instanceof Int && a.n === 1) {
|
|
3861
|
+
// e.g. 1 / x -> x^-1
|
|
3264
3862
|
return [pow];
|
|
3265
3863
|
} else {
|
|
3864
|
+
// e.g. 2 / x -> 2*x^-1
|
|
3266
3865
|
return [a, pow];
|
|
3267
3866
|
}
|
|
3268
3867
|
}
|
|
3269
3868
|
};
|
|
3270
3869
|
if (left instanceof Mul) {
|
|
3271
|
-
|
|
3272
|
-
|
|
3870
|
+
// NOTE(kevinb): `terms` should always have at least two
|
|
3871
|
+
// elements so getting the last element is safe to do.
|
|
3872
|
+
var divided = divide(___default.default.last(left.terms), right);
|
|
3873
|
+
return new Mul(___default.default.initial(left.terms).concat(divided));
|
|
3273
3874
|
} else {
|
|
3274
3875
|
var divided = divide(left, right);
|
|
3275
3876
|
return new Mul(divided).flatten();
|
|
3276
3877
|
}
|
|
3277
3878
|
}
|
|
3879
|
+
|
|
3880
|
+
// fold negative signs into numbers if possible
|
|
3881
|
+
// negative signs are not the same as multiplying by negative one!
|
|
3882
|
+
// e.g. -x -> -1*x simplified
|
|
3883
|
+
// e.g. -2*x -> -2*x simplified
|
|
3884
|
+
// e.g. -x*2 -> -1*x*2 not simplified -> x*-2 simplified
|
|
3885
|
+
// e.g. -1*x*2 -> -1*x*2 not simplified
|
|
3886
|
+
|
|
3887
|
+
// also fold multiplicative terms into open Trig and Log nodes
|
|
3888
|
+
// e.g. (sin x)*x -> sin(x)*x
|
|
3889
|
+
// e.g. sin(x)*x -> sin(x)*x
|
|
3890
|
+
// e.g. sin(x)*(x) -> sin(x)*x
|
|
3891
|
+
// e.g. sin(x)*sin(y) -> sin(x)*sin(y)
|
|
3278
3892
|
static fold(expr) {
|
|
3279
3893
|
if (expr instanceof Mul) {
|
|
3280
|
-
|
|
3894
|
+
// assuming that this will be second to last
|
|
3895
|
+
var trigLog = ___default.default.find(___default.default.initial(expr.terms), term => {
|
|
3281
3896
|
return (term instanceof Trig || term instanceof Log) && Boolean(term.hints.open);
|
|
3282
3897
|
});
|
|
3283
3898
|
if (trigLog) {
|
|
3284
|
-
|
|
3899
|
+
// expr.terms should always have at least two terms
|
|
3900
|
+
const last = ___default.default.last(expr.terms);
|
|
3285
3901
|
if (trigLog.hints.parens || last.hints.parens || last.has(Trig) || last.has(Log)) {
|
|
3286
3902
|
trigLog.hints.open = false;
|
|
3287
3903
|
} else {
|
|
3288
3904
|
const newTrigLog = trigLog instanceof Trig ? Trig.create([trigLog.type, trigLog.exp], Mul.createOrAppend(trigLog.arg, last).fold()) : Log.create(trigLog.base, Mul.createOrAppend(trigLog.power, last).fold());
|
|
3289
|
-
const index =
|
|
3905
|
+
const index = ___default.default.indexOf(expr.terms, trigLog);
|
|
3290
3906
|
if (index === 0) {
|
|
3291
3907
|
return newTrigLog;
|
|
3292
3908
|
} else {
|
|
@@ -3302,21 +3918,32 @@ class Mul extends Seq {
|
|
|
3302
3918
|
var neg = num => {
|
|
3303
3919
|
return num.n === -1 && Boolean(num.hints.negate);
|
|
3304
3920
|
};
|
|
3305
|
-
var posOrNeg = function
|
|
3921
|
+
var posOrNeg = function (num) {
|
|
3306
3922
|
return pos(num) || neg(num);
|
|
3307
3923
|
};
|
|
3924
|
+
|
|
3925
|
+
// @ts-expect-error: Type 'Expr' is not assignable to type 'Num'.
|
|
3308
3926
|
const posNum = numbers.find(pos);
|
|
3927
|
+
// @ts-expect-error: Type 'Expr' is not assignable to type 'Num'.
|
|
3309
3928
|
const negNum = numbers.find(neg);
|
|
3310
|
-
if (numbers.length > 1 && negNum && posNum &&
|
|
3311
|
-
|
|
3312
|
-
|
|
3929
|
+
if (numbers.length > 1 && negNum && posNum &&
|
|
3930
|
+
// @ts-expect-error: Type 'Expr' is not assignable to type 'Num'.
|
|
3931
|
+
___default.default.every(numbers, posOrNeg)) {
|
|
3932
|
+
var firstNeg = ___default.default.indexOf(expr.terms, negNum);
|
|
3933
|
+
var firstNum = ___default.default.indexOf(expr.terms, posNum);
|
|
3934
|
+
|
|
3935
|
+
// e.g. -x*2 -> x*-2
|
|
3313
3936
|
if (firstNeg < firstNum) {
|
|
3314
3937
|
return expr.replace(firstNum, expr.terms[firstNum].negate()).remove(firstNeg);
|
|
3315
3938
|
}
|
|
3316
3939
|
}
|
|
3317
3940
|
}
|
|
3941
|
+
|
|
3942
|
+
// in all other cases, make no change
|
|
3318
3943
|
return expr;
|
|
3319
3944
|
}
|
|
3945
|
+
|
|
3946
|
+
// create a new sequence unless left is already one (returns a copy)
|
|
3320
3947
|
static createOrAppend(left, right) {
|
|
3321
3948
|
if (left instanceof Mul) {
|
|
3322
3949
|
return new Mul(left.terms.concat(right));
|
|
@@ -3325,23 +3952,39 @@ class Mul extends Seq {
|
|
|
3325
3952
|
}
|
|
3326
3953
|
}
|
|
3327
3954
|
}
|
|
3955
|
+
|
|
3956
|
+
/* exponentiation */
|
|
3328
3957
|
class Pow extends Expr {
|
|
3958
|
+
base;
|
|
3959
|
+
exp;
|
|
3329
3960
|
constructor(base, exp) {
|
|
3330
3961
|
super();
|
|
3331
|
-
this.base = void 0;
|
|
3332
|
-
this.exp = void 0;
|
|
3333
|
-
this.func = Pow;
|
|
3334
3962
|
this.base = base;
|
|
3335
3963
|
this.exp = exp;
|
|
3336
3964
|
}
|
|
3965
|
+
func = Pow;
|
|
3337
3966
|
args() {
|
|
3338
3967
|
return [this.base, this.exp];
|
|
3339
3968
|
}
|
|
3340
|
-
eval(
|
|
3969
|
+
eval() {
|
|
3970
|
+
let vars = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
3971
|
+
let options = arguments.length > 1 ? arguments[1] : undefined;
|
|
3341
3972
|
var evaledBase = this.base.eval(vars, options);
|
|
3342
3973
|
var evaledExp = this.exp.eval(vars, options);
|
|
3974
|
+
|
|
3975
|
+
// Math.pow unequivocally returns NaN when provided with both a
|
|
3976
|
+
// negative base and a fractional exponent. However, in some cases, we
|
|
3977
|
+
// know that our exponent is actually valid for use with negative
|
|
3978
|
+
// bases (e.g., (-5)^(1/3)).
|
|
3979
|
+
//
|
|
3980
|
+
// Here, we explicitly check for such cases. We really only handle a
|
|
3981
|
+
// limited subset (by requiring that the exponent is rational with an
|
|
3982
|
+
// odd denominator), but it's still useful.
|
|
3983
|
+
// See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/pow
|
|
3343
3984
|
if (evaledBase < 0) {
|
|
3344
3985
|
var simplifiedExp = this.exp.simplify();
|
|
3986
|
+
|
|
3987
|
+
// If Float, convert to a Rational to enable the logic below
|
|
3345
3988
|
if (simplifiedExp instanceof Float) {
|
|
3346
3989
|
var num = simplifiedExp.n;
|
|
3347
3990
|
var decimals = (num - +num.toFixed()).toString().length - 2;
|
|
@@ -3364,6 +4007,7 @@ class Pow extends Expr {
|
|
|
3364
4007
|
return this.base.getUnits().map(unit => {
|
|
3365
4008
|
return {
|
|
3366
4009
|
unit: unit.unit,
|
|
4010
|
+
// Exponents in units should always be integers
|
|
3367
4011
|
pow: unit.pow * this.exp.n
|
|
3368
4012
|
};
|
|
3369
4013
|
});
|
|
@@ -3380,24 +4024,31 @@ class Pow extends Expr {
|
|
|
3380
4024
|
}
|
|
3381
4025
|
tex() {
|
|
3382
4026
|
if (this.isDivide()) {
|
|
4027
|
+
// e.g. x ^ -1 w/hint -> 1/x
|
|
3383
4028
|
return "\\frac{1}{" + this.asDivide().tex() + "}";
|
|
3384
4029
|
} else if (this.isRoot() && isRational(this.exp)) {
|
|
3385
4030
|
if (this.exp.n !== 1) {
|
|
3386
4031
|
throw new Error("Node marked with hint 'root' does not have exponent " + "of form 1/x.");
|
|
3387
4032
|
}
|
|
3388
4033
|
if (this.exp.d === 2) {
|
|
4034
|
+
// e.g. x ^ 1/2 w/hint -> sqrt{x}
|
|
3389
4035
|
return "\\sqrt{" + this.base.tex() + "}";
|
|
3390
4036
|
} else {
|
|
4037
|
+
// e.g. x ^ 1/y w/hint -> sqrt[y]{x}
|
|
3391
4038
|
return "\\sqrt[" + this.exp.d + "]{" + this.base.tex() + "}";
|
|
3392
4039
|
}
|
|
3393
4040
|
} else if (this.base instanceof Trig && !this.base.isInverse() && this.exp instanceof Num && this.exp.isSimple() && this.exp.eval() >= 0) {
|
|
4041
|
+
// e.g sin(x) ^ 2 -> sin^2(x)
|
|
3394
4042
|
var split = this.base.texSplit();
|
|
3395
4043
|
return split[0] + "^{" + this.exp.tex() + "}" + split[1];
|
|
3396
4044
|
} else {
|
|
4045
|
+
// e.g. x ^ y -> x^y
|
|
3397
4046
|
var base = this.base.tex();
|
|
3398
4047
|
if (this.base instanceof Seq || this.base instanceof Pow || this.base instanceof Num && !this.base.isSimple()) {
|
|
4048
|
+
// e.g. a+b ^ c -> (a+b)^c
|
|
3399
4049
|
base = "(" + base + ")";
|
|
3400
4050
|
} else if (this.base instanceof Trig || this.base instanceof Log) {
|
|
4051
|
+
// e.g. ln(x) ^ 2 -> [ln(x)]^2
|
|
3401
4052
|
base = "[" + base + "]";
|
|
3402
4053
|
}
|
|
3403
4054
|
return base + "^{" + this.exp.tex() + "}";
|
|
@@ -3409,30 +4060,43 @@ class Pow extends Expr {
|
|
|
3409
4060
|
expand() {
|
|
3410
4061
|
var pow = this.recurse("expand");
|
|
3411
4062
|
if (pow.base instanceof Mul) {
|
|
3412
|
-
|
|
4063
|
+
// e.g. (ab)^c -> a^c*b^c
|
|
4064
|
+
|
|
4065
|
+
var terms = ___default.default.map(pow.base.terms, term => {
|
|
3413
4066
|
return new Pow(term, pow.exp);
|
|
3414
4067
|
});
|
|
3415
4068
|
return new Mul(terms).expand();
|
|
3416
4069
|
} else if (pow.base instanceof Add && pow.exp instanceof Int && pow.exp.abs().eval() > 1) {
|
|
4070
|
+
// e.g. (a+b)^2 -> a*a+a*b+a*b+b*b
|
|
4071
|
+
// e.g. (a+b)^-2 -> (a*a+a*b+a*b+b*b)^-1
|
|
4072
|
+
|
|
3417
4073
|
var positive = pow.exp.eval() > 0;
|
|
3418
4074
|
var n = pow.exp.abs().eval();
|
|
3419
|
-
var signed = function
|
|
4075
|
+
var signed = function (mul) {
|
|
3420
4076
|
return positive ? mul : new Pow(mul, NumDiv);
|
|
3421
4077
|
};
|
|
4078
|
+
|
|
4079
|
+
// compute and cache powers of 2 up to n
|
|
3422
4080
|
const cache = {
|
|
3423
4081
|
1: pow.base
|
|
3424
4082
|
};
|
|
3425
4083
|
for (var i = 2; i <= n; i *= 2) {
|
|
3426
|
-
const
|
|
3427
|
-
cache[i] =
|
|
4084
|
+
const mul = new Mul(cache[i / 2], cache[i / 2]);
|
|
4085
|
+
cache[i] = mul.expand().collect();
|
|
3428
4086
|
}
|
|
4087
|
+
|
|
4088
|
+
// if n is a power of 2, you're done!
|
|
3429
4089
|
if (n in cache) {
|
|
3430
4090
|
return signed(cache[n]);
|
|
3431
4091
|
}
|
|
3432
|
-
|
|
4092
|
+
|
|
4093
|
+
// otherwise decompose n into powers of 2 ...
|
|
4094
|
+
let indices = ___default.default.map(n.toString(2).split(""), function (str, i, list) {
|
|
3433
4095
|
return Number(str) * Math.pow(2, list.length - i - 1);
|
|
3434
4096
|
});
|
|
3435
|
-
indices =
|
|
4097
|
+
indices = ___default.default.without(indices, 0);
|
|
4098
|
+
|
|
4099
|
+
// ... then combine
|
|
3436
4100
|
const factors = [];
|
|
3437
4101
|
for (const index of indices) {
|
|
3438
4102
|
if (index in cache) {
|
|
@@ -3442,10 +4106,13 @@ class Pow extends Expr {
|
|
|
3442
4106
|
const mul = new Mul(factors).expand().collect();
|
|
3443
4107
|
return signed(mul);
|
|
3444
4108
|
} else if (pow.exp instanceof Add) {
|
|
3445
|
-
|
|
4109
|
+
// DEFINITELY want behind super-simplify() flag
|
|
4110
|
+
// e.g. x^(a+b) -> x^a*x^b
|
|
4111
|
+
|
|
4112
|
+
const terms = ___default.default.map(pow.exp.terms, term => {
|
|
3446
4113
|
return new Pow(pow.base, term).expand();
|
|
3447
4114
|
});
|
|
3448
|
-
return new Mul(
|
|
4115
|
+
return new Mul(terms).expand();
|
|
3449
4116
|
} else {
|
|
3450
4117
|
return pow;
|
|
3451
4118
|
}
|
|
@@ -3453,8 +4120,10 @@ class Pow extends Expr {
|
|
|
3453
4120
|
factor() {
|
|
3454
4121
|
var pow = this.recurse("factor");
|
|
3455
4122
|
if (pow.base instanceof Mul) {
|
|
3456
|
-
var terms =
|
|
4123
|
+
var terms = ___default.default.map(pow.base.terms, term => {
|
|
3457
4124
|
if (term instanceof Int && pow.exp.equals(NumDiv)) {
|
|
4125
|
+
// Anything that can be a Rational should be a Rational
|
|
4126
|
+
// e.g. 2^(-1) -> 1/2
|
|
3458
4127
|
return new Rational(1, term.n);
|
|
3459
4128
|
} else {
|
|
3460
4129
|
return new Pow(term, pow.exp);
|
|
@@ -3467,54 +4136,86 @@ class Pow extends Expr {
|
|
|
3467
4136
|
}
|
|
3468
4137
|
collect(options) {
|
|
3469
4138
|
if (this.base instanceof Pow) {
|
|
4139
|
+
// collect this first to avoid having to deal with float precision
|
|
4140
|
+
// e.g. sqrt(2)^2 -> 2, not 2.0000000000000004
|
|
4141
|
+
// e.g. (x^y)^z -> x^(yz)
|
|
3470
4142
|
const base = this.base.base;
|
|
3471
4143
|
const exp = Mul.createOrAppend(this.base.exp, this.exp);
|
|
3472
4144
|
return new Pow(base, exp).collect(options);
|
|
3473
4145
|
}
|
|
3474
4146
|
const pow = this.recurse("collect", options);
|
|
3475
|
-
const isSimilarLog = function
|
|
4147
|
+
const isSimilarLog = function (term) {
|
|
3476
4148
|
return term instanceof Log && term.base.equals(pow.base);
|
|
3477
4149
|
};
|
|
3478
4150
|
if (pow.exp instanceof Num && pow.exp.eval() === 0) {
|
|
4151
|
+
// e.g. x^0 -> 1
|
|
3479
4152
|
return NumOne;
|
|
3480
4153
|
} else if (pow.exp instanceof Num && pow.exp.eval() === 1) {
|
|
4154
|
+
// e.g. x^1 -> x
|
|
3481
4155
|
return pow.base;
|
|
3482
4156
|
} else if (isSimilarLog(pow.exp)) {
|
|
4157
|
+
// e.g. b^(log_b(x)) -> x
|
|
3483
4158
|
return pow.exp.power;
|
|
3484
|
-
} else if (pow.exp instanceof Mul &&
|
|
4159
|
+
} else if (pow.exp instanceof Mul && ___default.default.any(pow.exp.terms, isSimilarLog)) {
|
|
4160
|
+
// e.g. b^(2*y*log_b(x)) -> x^(2*y)
|
|
4161
|
+
// `log` will always be defined here because of the
|
|
4162
|
+
// `_.any(pow.exp.terms, isSimilarLog)` check above.
|
|
3485
4163
|
const log = pow.exp.terms.find(isSimilarLog);
|
|
3486
4164
|
const base = log.power;
|
|
3487
4165
|
const exp = pow.exp.remove(log).flatten();
|
|
3488
4166
|
return new Pow(base, exp).collect(options);
|
|
3489
4167
|
} else if (pow.base instanceof Num && pow.exp instanceof Num) {
|
|
4168
|
+
// TODO(alex): Consider encapsualting this logic (and similar logic
|
|
4169
|
+
// elsewhere) into a separate Decimal class for user-entered floats
|
|
3490
4170
|
if (options && options.preciseFloats) {
|
|
4171
|
+
// Avoid creating an imprecise float
|
|
4172
|
+
// e.g. 23^1.5 -> 12167^0.5, not ~110.304
|
|
4173
|
+
|
|
4174
|
+
// If you take the root as specified by the denominator and
|
|
4175
|
+
// end up with more digits after the decimal point,
|
|
4176
|
+
// the result is imprecise. This works for rationals as well
|
|
4177
|
+
// as floats, but ideally rationals should be pre-processed
|
|
4178
|
+
// e.g. (1/27)^(1/3) -> 1/3 to avoid most cases.
|
|
4179
|
+
// TODO(alex): Catch such cases and avoid converting to floats.
|
|
3491
4180
|
const exp = pow.exp.asRational();
|
|
3492
4181
|
const decimalsInBase = pow.base.getDecimalPlaces();
|
|
3493
4182
|
const root = new Pow(pow.base, new Rational(1, exp.d));
|
|
3494
|
-
const decimalsInRoot = root.collect()
|
|
4183
|
+
const decimalsInRoot = root.collect()
|
|
4184
|
+
// @ts-expect-error: we assume that `root.collect()` returns
|
|
4185
|
+
// a Num here but tbh I'm not sure how this code isn't causing
|
|
4186
|
+
// an infinite loop.
|
|
4187
|
+
.getDecimalPlaces();
|
|
3495
4188
|
if (decimalsInRoot > decimalsInBase) {
|
|
4189
|
+
// Collecting over this denominator would result in an
|
|
4190
|
+
// imprecise float, so avoid doing so.
|
|
3496
4191
|
const newBase = new Pow(pow.base, new Int(exp.n)).collect();
|
|
3497
4192
|
return new Pow(newBase, new Rational(1, exp.d));
|
|
3498
4193
|
}
|
|
3499
4194
|
}
|
|
4195
|
+
|
|
4196
|
+
// e.g. 4^1.5 -> 8
|
|
3500
4197
|
return pow.base.raiseToThe(pow.exp, options);
|
|
3501
4198
|
} else {
|
|
3502
4199
|
return pow;
|
|
3503
4200
|
}
|
|
3504
4201
|
}
|
|
4202
|
+
|
|
4203
|
+
// checks whether this Pow represents user-entered division
|
|
3505
4204
|
isDivide() {
|
|
3506
|
-
var isDiv = function
|
|
4205
|
+
var isDiv = function (arg) {
|
|
3507
4206
|
return arg instanceof Num && Boolean(arg.hints.divide);
|
|
3508
4207
|
};
|
|
3509
|
-
return isDiv(this.exp) || this.exp instanceof Mul &&
|
|
4208
|
+
return isDiv(this.exp) || this.exp instanceof Mul && ___default.default.any(this.exp.terms, isDiv);
|
|
3510
4209
|
}
|
|
4210
|
+
|
|
4211
|
+
// assuming this Pow represents user-entered division, returns the denominator
|
|
3511
4212
|
asDivide() {
|
|
3512
4213
|
if (this.exp instanceof Num) {
|
|
3513
4214
|
if (this.exp.eval() === -1) {
|
|
3514
4215
|
return this.base;
|
|
3515
4216
|
} else {
|
|
3516
4217
|
var negated = this.exp.negate();
|
|
3517
|
-
negated.hints =
|
|
4218
|
+
negated.hints = ___default.default.clone(this.exp.hints);
|
|
3518
4219
|
negated.hints.divide = false;
|
|
3519
4220
|
return new Pow(this.base, negated);
|
|
3520
4221
|
}
|
|
@@ -3530,6 +4231,9 @@ class Pow extends Expr {
|
|
|
3530
4231
|
isSquaredTrig() {
|
|
3531
4232
|
return this.base instanceof Trig && !this.base.isInverse() && this.exp instanceof Num && this.exp.eval() === 2;
|
|
3532
4233
|
}
|
|
4234
|
+
|
|
4235
|
+
// extract whatever denominator makes sense, ignoring hints
|
|
4236
|
+
// if negative exponent, will recursively include the base's denominator as well
|
|
3533
4237
|
getDenominator() {
|
|
3534
4238
|
if (this.exp instanceof Num && this.exp.eval() === -1) {
|
|
3535
4239
|
return Mul.createOrAppend(this.base, this.base.getDenominator()).flatten();
|
|
@@ -3544,17 +4248,27 @@ class Pow extends Expr {
|
|
|
3544
4248
|
}
|
|
3545
4249
|
findGCD(factor) {
|
|
3546
4250
|
const [base, exp] = factor instanceof Pow ? [factor.base, factor.exp] : [factor, NumOne];
|
|
4251
|
+
|
|
4252
|
+
// GCD is only relevant if same base
|
|
3547
4253
|
if (this.base.equals(base)) {
|
|
3548
4254
|
if (this.exp.equals(exp)) {
|
|
4255
|
+
// exact match
|
|
4256
|
+
// e.g. GCD(x^y^z, x^y^z) -> x^y^z
|
|
3549
4257
|
return this;
|
|
3550
4258
|
} else if (this.exp instanceof Num && exp instanceof Num) {
|
|
4259
|
+
// two numerical exponents
|
|
4260
|
+
// e.g. GCD(x^3, x^2) -> x^2
|
|
3551
4261
|
return new Pow(this.base, Num.min(this.exp, exp)).collect();
|
|
3552
4262
|
} else if (this.exp instanceof Num || exp instanceof Num) {
|
|
4263
|
+
// one numerical exponent
|
|
4264
|
+
// e.g. GCD(x^2, x^y) -> 1
|
|
3553
4265
|
return NumOne;
|
|
3554
4266
|
}
|
|
3555
4267
|
var expA = this.exp.asMul().partition();
|
|
3556
4268
|
var expB = exp.asMul().partition();
|
|
3557
4269
|
if (expA[1].equals(expB[1])) {
|
|
4270
|
+
// exponents match except for coefficient
|
|
4271
|
+
// e.g. GCD(x^3y, x^y) -> x^y
|
|
3558
4272
|
var coefficient = Num.min(expA[0].reduce(), expB[0].reduce());
|
|
3559
4273
|
var mul = new Mul(coefficient, expA[1].flatten()).flatten();
|
|
3560
4274
|
return new Pow(base, mul).collect();
|
|
@@ -3577,8 +4291,10 @@ class Pow extends Expr {
|
|
|
3577
4291
|
if (exp instanceof Int) {
|
|
3578
4292
|
var n = exp.eval();
|
|
3579
4293
|
if (n > 2) {
|
|
4294
|
+
// e.g. x^3 -> x^2
|
|
3580
4295
|
return new Pow(this.base, new Int(n - 1));
|
|
3581
4296
|
} else if (n < -2) {
|
|
4297
|
+
// e.g. x^-3 -> x^-2
|
|
3582
4298
|
return new Pow(this.base, new Int(n + 1));
|
|
3583
4299
|
}
|
|
3584
4300
|
}
|
|
@@ -3588,28 +4304,40 @@ class Pow extends Expr {
|
|
|
3588
4304
|
static sqrt(arg) {
|
|
3589
4305
|
return new Pow(arg, NumSqrt);
|
|
3590
4306
|
}
|
|
4307
|
+
|
|
4308
|
+
// NOTE(kevinb): nthroot is used as a constructor so we need to
|
|
4309
|
+
// define it as a static property instead of a static method.
|
|
4310
|
+
// TODO(kevinb): update parser-generator.ts to call nthrooth
|
|
4311
|
+
// without using `new`.
|
|
4312
|
+
static nthroot = function (radicand, degree) {
|
|
4313
|
+
var exp = Mul.fold(Mul.handleDivide(new Int(1), degree));
|
|
4314
|
+
|
|
4315
|
+
// FIXME(johnsullivan): If oneOverDegree ends up being a pow object,
|
|
4316
|
+
// this "root" hint is lost between here and when tex() is called.
|
|
4317
|
+
return new Pow(radicand, exp.addHint("root"));
|
|
4318
|
+
};
|
|
3591
4319
|
}
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
var exp = Mul.fold(Mul.handleDivide(new Int(1), degree));
|
|
3595
|
-
return new _class5(radicand, exp.addHint("root"));
|
|
3596
|
-
};
|
|
4320
|
+
|
|
4321
|
+
/* logarithm */
|
|
3597
4322
|
class Log extends Expr {
|
|
4323
|
+
base;
|
|
4324
|
+
power;
|
|
3598
4325
|
constructor(base, power) {
|
|
3599
4326
|
super();
|
|
3600
|
-
this.base = void 0;
|
|
3601
|
-
this.power = void 0;
|
|
3602
|
-
this.func = Log;
|
|
3603
4327
|
this.base = base;
|
|
3604
4328
|
this.power = power;
|
|
3605
|
-
this.hints =
|
|
4329
|
+
this.hints = {
|
|
4330
|
+
...this.hints,
|
|
3606
4331
|
open: false
|
|
3607
|
-
}
|
|
4332
|
+
};
|
|
3608
4333
|
}
|
|
4334
|
+
func = Log;
|
|
3609
4335
|
args() {
|
|
3610
4336
|
return [this.base, this.power];
|
|
3611
4337
|
}
|
|
3612
|
-
eval(
|
|
4338
|
+
eval() {
|
|
4339
|
+
let vars = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
4340
|
+
let options = arguments.length > 1 ? arguments[1] : undefined;
|
|
3613
4341
|
return Math.log(this.power.eval(vars, options)) / Math.log(this.base.eval(vars, options));
|
|
3614
4342
|
}
|
|
3615
4343
|
codegen() {
|
|
@@ -3634,10 +4362,13 @@ class Log extends Expr {
|
|
|
3634
4362
|
collect(options) {
|
|
3635
4363
|
var log = this.recurse("collect", options);
|
|
3636
4364
|
if (log.power instanceof Num && log.power.eval() === 1) {
|
|
4365
|
+
// e.g. ln(1) -> 0
|
|
3637
4366
|
return NumZero;
|
|
3638
4367
|
} else if (log.base.equals(log.power)) {
|
|
4368
|
+
// e.g. log_b(b) -> 1
|
|
3639
4369
|
return NumOne;
|
|
3640
4370
|
} else if (log.power instanceof Pow && log.power.base.equals(log.base)) {
|
|
4371
|
+
// e.g. log_b(b^x) -> x
|
|
3641
4372
|
return log.power.exp;
|
|
3642
4373
|
} else {
|
|
3643
4374
|
return log;
|
|
@@ -3646,13 +4377,21 @@ class Log extends Expr {
|
|
|
3646
4377
|
expand() {
|
|
3647
4378
|
var log = this.recurse("expand");
|
|
3648
4379
|
if (log.power instanceof Mul) {
|
|
3649
|
-
|
|
4380
|
+
// might want behind super-simplify() flag
|
|
4381
|
+
// e.g. ln(xy) -> ln(x) + ln(y)
|
|
4382
|
+
|
|
4383
|
+
var terms = ___default.default.map(log.power.terms, term => {
|
|
4384
|
+
// need to expand again in case new log powers are Pows
|
|
3650
4385
|
return new Log(log.base, term).expand();
|
|
3651
4386
|
});
|
|
3652
4387
|
return new Add(terms);
|
|
3653
4388
|
} else if (log.power instanceof Pow) {
|
|
4389
|
+
// e.g. ln(x^y) -> y*ln(x)
|
|
4390
|
+
|
|
3654
4391
|
return new Mul(log.power.exp, new Log(log.base, log.power.base).expand()).flatten();
|
|
3655
4392
|
} else if (!log.isNatural()) {
|
|
4393
|
+
// e.g. log_b(x) -> ln(x)/ln(b)
|
|
4394
|
+
|
|
3656
4395
|
return Mul.handleDivide(new Log(Const.e, log.power), new Log(Const.e, log.base));
|
|
3657
4396
|
} else {
|
|
3658
4397
|
return log;
|
|
@@ -3686,172 +4425,178 @@ class Log extends Expr {
|
|
|
3686
4425
|
return log;
|
|
3687
4426
|
}
|
|
3688
4427
|
}
|
|
4428
|
+
/* trigonometric functions */
|
|
3689
4429
|
class Trig extends Expr {
|
|
3690
|
-
|
|
4430
|
+
type; // TODO(kevinb): Use a union type for this
|
|
4431
|
+
arg;
|
|
4432
|
+
exp;
|
|
4433
|
+
constructor(type, arg) {
|
|
3691
4434
|
super();
|
|
3692
|
-
this.type =
|
|
3693
|
-
this.arg =
|
|
3694
|
-
this.
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
4435
|
+
this.type = type;
|
|
4436
|
+
this.arg = arg;
|
|
4437
|
+
this.hints = {
|
|
4438
|
+
...this.hints,
|
|
4439
|
+
open: false
|
|
4440
|
+
};
|
|
4441
|
+
}
|
|
4442
|
+
func = Trig;
|
|
4443
|
+
args() {
|
|
4444
|
+
return [this.type, this.arg];
|
|
4445
|
+
}
|
|
4446
|
+
|
|
4447
|
+
// TODO(kevinb): Use union type for the function names.
|
|
4448
|
+
functions = {
|
|
4449
|
+
sin: {
|
|
4450
|
+
eval: Math.sin,
|
|
4451
|
+
codegen: "Math.sin((",
|
|
4452
|
+
tex: "\\sin",
|
|
4453
|
+
expand: () => this
|
|
4454
|
+
},
|
|
4455
|
+
cos: {
|
|
4456
|
+
eval: Math.cos,
|
|
4457
|
+
codegen: "Math.cos((",
|
|
4458
|
+
tex: "\\cos",
|
|
4459
|
+
expand: () => this
|
|
4460
|
+
},
|
|
4461
|
+
tan: {
|
|
4462
|
+
eval: Math.tan,
|
|
4463
|
+
codegen: "Math.tan((",
|
|
4464
|
+
tex: "\\tan",
|
|
4465
|
+
expand: () => Mul.handleDivide(Trig.sin(this.arg), Trig.cos(this.arg))
|
|
4466
|
+
},
|
|
4467
|
+
csc: {
|
|
4468
|
+
eval: arg => {
|
|
4469
|
+
return 1 / Math.sin(arg);
|
|
3702
4470
|
},
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
4471
|
+
codegen: "(1/Math.sin(",
|
|
4472
|
+
tex: "\\csc",
|
|
4473
|
+
expand: () => Mul.handleDivide(NumOne, Trig.sin(this.arg))
|
|
4474
|
+
},
|
|
4475
|
+
sec: {
|
|
4476
|
+
eval: arg => {
|
|
4477
|
+
return 1 / Math.cos(arg);
|
|
3708
4478
|
},
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
4479
|
+
codegen: "(1/Math.cos(",
|
|
4480
|
+
tex: "\\sec",
|
|
4481
|
+
expand: () => Mul.handleDivide(NumOne, Trig.cos(this.arg))
|
|
4482
|
+
},
|
|
4483
|
+
cot: {
|
|
4484
|
+
eval: arg => {
|
|
4485
|
+
return 1 / Math.tan(arg);
|
|
3714
4486
|
},
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
4487
|
+
codegen: "(1/Math.tan(",
|
|
4488
|
+
tex: "\\cot",
|
|
4489
|
+
expand: () => Mul.handleDivide(Trig.cos(this.arg), Trig.sin(this.arg))
|
|
4490
|
+
},
|
|
4491
|
+
arcsin: {
|
|
4492
|
+
eval: Math.asin,
|
|
4493
|
+
codegen: "Math.asin((",
|
|
4494
|
+
tex: "\\arcsin"
|
|
4495
|
+
},
|
|
4496
|
+
arccos: {
|
|
4497
|
+
eval: Math.acos,
|
|
4498
|
+
codegen: "Math.acos((",
|
|
4499
|
+
tex: "\\arccos"
|
|
4500
|
+
},
|
|
4501
|
+
arctan: {
|
|
4502
|
+
eval: Math.atan,
|
|
4503
|
+
codegen: "Math.atan((",
|
|
4504
|
+
tex: "\\arctan"
|
|
4505
|
+
},
|
|
4506
|
+
arccsc: {
|
|
4507
|
+
eval: arg => {
|
|
4508
|
+
return Math.asin(1 / arg);
|
|
3722
4509
|
},
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
expand: () => Mul.handleDivide(NumOne, Trig.cos(this.arg))
|
|
4510
|
+
codegen: "Math.asin(1/(",
|
|
4511
|
+
tex: "\\operatorname{arccsc}"
|
|
4512
|
+
},
|
|
4513
|
+
arcsec: {
|
|
4514
|
+
eval: arg => {
|
|
4515
|
+
return Math.acos(1 / arg);
|
|
3730
4516
|
},
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
expand: () => Mul.handleDivide(Trig.cos(this.arg), Trig.sin(this.arg))
|
|
4517
|
+
codegen: "Math.acos(1/(",
|
|
4518
|
+
tex: "\\operatorname{arcsec}"
|
|
4519
|
+
},
|
|
4520
|
+
arccot: {
|
|
4521
|
+
eval: arg => {
|
|
4522
|
+
return Math.atan(1 / arg);
|
|
3738
4523
|
},
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
4524
|
+
codegen: "Math.atan(1/(",
|
|
4525
|
+
tex: "\\operatorname{arccot}"
|
|
4526
|
+
},
|
|
4527
|
+
sinh: {
|
|
4528
|
+
eval: arg => {
|
|
4529
|
+
return (Math.exp(arg) - Math.exp(-arg)) / 2;
|
|
3743
4530
|
},
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
codegen: "Math.acos((",
|
|
3747
|
-
tex: "\\arccos"
|
|
4531
|
+
codegen: argStr => {
|
|
4532
|
+
return "((Math.exp(" + argStr + ") - Math.exp(-(" + argStr + "))) / 2)";
|
|
3748
4533
|
},
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
4534
|
+
tex: "\\sinh",
|
|
4535
|
+
expand: () => this
|
|
4536
|
+
},
|
|
4537
|
+
cosh: {
|
|
4538
|
+
eval: arg => {
|
|
4539
|
+
return (Math.exp(arg) + Math.exp(-arg)) / 2;
|
|
3753
4540
|
},
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
return Math.asin(1 / arg);
|
|
3757
|
-
},
|
|
3758
|
-
codegen: "Math.asin(1/(",
|
|
3759
|
-
tex: "\\operatorname{arccsc}"
|
|
4541
|
+
codegen: argStr => {
|
|
4542
|
+
return "((Math.exp(" + argStr + ") + Math.exp(-(" + argStr + "))) / 2)";
|
|
3760
4543
|
},
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
4544
|
+
tex: "\\cosh",
|
|
4545
|
+
expand: () => this
|
|
4546
|
+
},
|
|
4547
|
+
tanh: {
|
|
4548
|
+
eval: arg => {
|
|
4549
|
+
return (Math.exp(arg) - Math.exp(-arg)) / (Math.exp(arg) + Math.exp(-arg));
|
|
3767
4550
|
},
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
return Math.atan(1 / arg);
|
|
3771
|
-
},
|
|
3772
|
-
codegen: "Math.atan(1/(",
|
|
3773
|
-
tex: "\\operatorname{arccot}"
|
|
4551
|
+
codegen: argStr => {
|
|
4552
|
+
return "(" + "(Math.exp(" + argStr + ") - Math.exp(-(" + argStr + ")))" + " / " + "(Math.exp(" + argStr + ") + Math.exp(-(" + argStr + ")))" + ")";
|
|
3774
4553
|
},
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
},
|
|
3782
|
-
tex: "\\sinh",
|
|
3783
|
-
expand: () => this
|
|
4554
|
+
tex: "\\tanh",
|
|
4555
|
+
expand: () => Mul.handleDivide(Trig.sinh(this.arg), Trig.cosh(this.arg))
|
|
4556
|
+
},
|
|
4557
|
+
csch: {
|
|
4558
|
+
eval: arg => {
|
|
4559
|
+
return 2 / (Math.exp(arg) - Math.exp(-arg));
|
|
3784
4560
|
},
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
return (Math.exp(arg) + Math.exp(-arg)) / 2;
|
|
3788
|
-
},
|
|
3789
|
-
codegen: argStr => {
|
|
3790
|
-
return "((Math.exp(" + argStr + ") + Math.exp(-(" + argStr + "))) / 2)";
|
|
3791
|
-
},
|
|
3792
|
-
tex: "\\cosh",
|
|
3793
|
-
expand: () => this
|
|
4561
|
+
codegen: argStr => {
|
|
4562
|
+
return "(2 / (Math.exp(" + argStr + ") - Math.exp(-(" + argStr + "))))";
|
|
3794
4563
|
},
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
},
|
|
3802
|
-
tex: "\\tanh",
|
|
3803
|
-
expand: () => Mul.handleDivide(Trig.sinh(this.arg), Trig.cosh(this.arg))
|
|
4564
|
+
tex: "\\csch",
|
|
4565
|
+
expand: () => Mul.handleDivide(NumOne, Trig.sinh(this.arg))
|
|
4566
|
+
},
|
|
4567
|
+
sech: {
|
|
4568
|
+
eval: arg => {
|
|
4569
|
+
return 2 / (Math.exp(arg) + Math.exp(-arg));
|
|
3804
4570
|
},
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
return 2 / (Math.exp(arg) - Math.exp(-arg));
|
|
3808
|
-
},
|
|
3809
|
-
codegen: argStr => {
|
|
3810
|
-
return "(2 / (Math.exp(" + argStr + ") - Math.exp(-(" + argStr + "))))";
|
|
3811
|
-
},
|
|
3812
|
-
tex: "\\csch",
|
|
3813
|
-
expand: () => Mul.handleDivide(NumOne, Trig.sinh(this.arg))
|
|
4571
|
+
codegen: argStr => {
|
|
4572
|
+
return "(2 / (Math.exp(" + argStr + ") + Math.exp(-(" + argStr + "))))";
|
|
3814
4573
|
},
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
},
|
|
3822
|
-
tex: "\\sech",
|
|
3823
|
-
expand: () => Mul.handleDivide(NumOne, Trig.cosh(this.arg))
|
|
4574
|
+
tex: "\\sech",
|
|
4575
|
+
expand: () => Mul.handleDivide(NumOne, Trig.cosh(this.arg))
|
|
4576
|
+
},
|
|
4577
|
+
coth: {
|
|
4578
|
+
eval: arg => {
|
|
4579
|
+
return (Math.exp(arg) + Math.exp(-arg)) / (Math.exp(arg) - Math.exp(-arg));
|
|
3824
4580
|
},
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
tex: "\\coth",
|
|
3833
|
-
expand: () => Mul.handleDivide(Trig.cosh(this.arg), Trig.sinh(this.arg))
|
|
3834
|
-
}
|
|
3835
|
-
};
|
|
3836
|
-
this.type = type;
|
|
3837
|
-
this.arg = _arg;
|
|
3838
|
-
this.hints = _extends({}, this.hints, {
|
|
3839
|
-
open: false
|
|
3840
|
-
});
|
|
3841
|
-
}
|
|
3842
|
-
args() {
|
|
3843
|
-
return [this.type, this.arg];
|
|
3844
|
-
}
|
|
4581
|
+
codegen: argStr => {
|
|
4582
|
+
return "(" + "(Math.exp(" + argStr + ") + Math.exp(-(" + argStr + ")))" + " / " + "(Math.exp(" + argStr + ") - Math.exp(-(" + argStr + ")))" + ")";
|
|
4583
|
+
},
|
|
4584
|
+
tex: "\\coth",
|
|
4585
|
+
expand: () => Mul.handleDivide(Trig.cosh(this.arg), Trig.sinh(this.arg))
|
|
4586
|
+
}
|
|
4587
|
+
};
|
|
3845
4588
|
isEven() {
|
|
3846
|
-
return
|
|
4589
|
+
return ___default.default.contains(["cos", "sec"], this.type);
|
|
3847
4590
|
}
|
|
3848
4591
|
isInverse() {
|
|
3849
4592
|
return this.type.indexOf("arc") === 0;
|
|
3850
4593
|
}
|
|
3851
4594
|
isBasic() {
|
|
3852
|
-
return
|
|
4595
|
+
return ___default.default.contains(["sin", "cos"], this.type);
|
|
3853
4596
|
}
|
|
3854
|
-
eval(
|
|
4597
|
+
eval() {
|
|
4598
|
+
let vars = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
4599
|
+
let options = arguments.length > 1 ? arguments[1] : undefined;
|
|
3855
4600
|
var func = this.functions[this.type].eval;
|
|
3856
4601
|
var arg = this.arg.eval(vars, options);
|
|
3857
4602
|
return func(arg);
|
|
@@ -3896,14 +4641,18 @@ class Trig extends Expr {
|
|
|
3896
4641
|
return this;
|
|
3897
4642
|
}
|
|
3898
4643
|
}
|
|
4644
|
+
|
|
4645
|
+
// TODO(alex): does every new node type need to redefine these?
|
|
3899
4646
|
needsExplicitMul() {
|
|
3900
4647
|
return false;
|
|
3901
4648
|
}
|
|
3902
4649
|
expand() {
|
|
3903
4650
|
var trig = this.recurse("expand");
|
|
3904
4651
|
if (!trig.isInverse()) {
|
|
4652
|
+
// e.g. tan(x) -> sin(x)/cos(x)
|
|
4653
|
+
// NOTE(kevinb): All non-inverse trig functions have an expand property.
|
|
3905
4654
|
var expand = trig.functions[trig.type].expand;
|
|
3906
|
-
return
|
|
4655
|
+
return ___default.default.bind(expand, trig)();
|
|
3907
4656
|
} else {
|
|
3908
4657
|
return trig;
|
|
3909
4658
|
}
|
|
@@ -3913,8 +4662,10 @@ class Trig extends Expr {
|
|
|
3913
4662
|
if (!trig.isInverse() && trig.arg.isNegative()) {
|
|
3914
4663
|
const arg = trig.arg instanceof Num ? trig.arg.abs() : Mul.handleDivide(trig.arg, NumNeg).collect(options);
|
|
3915
4664
|
if (trig.isEven()) {
|
|
4665
|
+
// e.g. cos(-x) -> cos(x)
|
|
3916
4666
|
return new Trig(trig.type, arg);
|
|
3917
4667
|
} else {
|
|
4668
|
+
// e.g. sin(-x) -> -sin(x)
|
|
3918
4669
|
return new Mul(NumNeg, new Trig(trig.type, arg));
|
|
3919
4670
|
}
|
|
3920
4671
|
} else {
|
|
@@ -3925,6 +4676,7 @@ class Trig extends Expr {
|
|
|
3925
4676
|
var type = pair[0];
|
|
3926
4677
|
var exp = pair[1];
|
|
3927
4678
|
if (exp && exp.equals(NumNeg)) {
|
|
4679
|
+
// e.g. sin^-1(x) -> arcsin(x)
|
|
3928
4680
|
type = "arc" + type;
|
|
3929
4681
|
exp = undefined;
|
|
3930
4682
|
}
|
|
@@ -3951,16 +4703,18 @@ class Trig extends Expr {
|
|
|
3951
4703
|
}
|
|
3952
4704
|
}
|
|
3953
4705
|
class Abs extends Expr {
|
|
4706
|
+
arg;
|
|
3954
4707
|
constructor(arg) {
|
|
3955
4708
|
super();
|
|
3956
|
-
this.arg = void 0;
|
|
3957
|
-
this.func = Abs;
|
|
3958
4709
|
this.arg = arg;
|
|
3959
4710
|
}
|
|
4711
|
+
func = Abs;
|
|
3960
4712
|
args() {
|
|
3961
4713
|
return [this.arg];
|
|
3962
4714
|
}
|
|
3963
|
-
eval(
|
|
4715
|
+
eval() {
|
|
4716
|
+
let vars = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
4717
|
+
let options = arguments.length > 1 ? arguments[1] : undefined;
|
|
3964
4718
|
return Math.abs(this.arg.eval(vars, options));
|
|
3965
4719
|
}
|
|
3966
4720
|
codegen() {
|
|
@@ -3975,11 +4729,14 @@ class Abs extends Expr {
|
|
|
3975
4729
|
collect(options) {
|
|
3976
4730
|
var abs = this.recurse("collect", options);
|
|
3977
4731
|
if (abs.arg.isPositive()) {
|
|
4732
|
+
// e.g. |2^x| -> 2^x
|
|
3978
4733
|
return abs.arg;
|
|
3979
4734
|
} else if (abs.arg instanceof Num) {
|
|
4735
|
+
// e.g. |-2| -> 2
|
|
3980
4736
|
return abs.arg.abs();
|
|
3981
4737
|
} else if (abs.arg instanceof Mul) {
|
|
3982
|
-
|
|
4738
|
+
// e.g. |-2*pi*x| -> 2*pi*|x|
|
|
4739
|
+
var terms = ___default.default.groupBy(abs.arg.terms, term => {
|
|
3983
4740
|
if (term.isPositive()) {
|
|
3984
4741
|
return "positive";
|
|
3985
4742
|
} else if (term instanceof Num) {
|
|
@@ -3988,7 +4745,9 @@ class Abs extends Expr {
|
|
|
3988
4745
|
return "other";
|
|
3989
4746
|
}
|
|
3990
4747
|
});
|
|
3991
|
-
var positives = terms.positive.concat(
|
|
4748
|
+
var positives = terms.positive.concat(___default.default.invoke(terms.number, "abs"));
|
|
4749
|
+
|
|
4750
|
+
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
|
3992
4751
|
if (terms.other.length) {
|
|
3993
4752
|
positives.push(new Abs(new Mul(terms.other).flatten()));
|
|
3994
4753
|
}
|
|
@@ -3997,10 +4756,13 @@ class Abs extends Expr {
|
|
|
3997
4756
|
return abs;
|
|
3998
4757
|
}
|
|
3999
4758
|
}
|
|
4759
|
+
|
|
4760
|
+
// this should definitely be behind a super-simplify flag
|
|
4000
4761
|
expand() {
|
|
4001
4762
|
var abs = this.recurse("expand");
|
|
4002
4763
|
if (abs.arg instanceof Mul) {
|
|
4003
|
-
|
|
4764
|
+
// e.g. |xyz| -> |x|*|y|*|z|
|
|
4765
|
+
var terms = ___default.default.map(abs.arg.terms, term => {
|
|
4004
4766
|
return new Abs(term);
|
|
4005
4767
|
});
|
|
4006
4768
|
return new Mul(terms);
|
|
@@ -4012,25 +4774,19 @@ class Abs extends Expr {
|
|
|
4012
4774
|
return true;
|
|
4013
4775
|
}
|
|
4014
4776
|
}
|
|
4777
|
+
|
|
4778
|
+
/* equation */
|
|
4015
4779
|
class Eq extends Expr {
|
|
4780
|
+
left;
|
|
4781
|
+
type; // TODO(kevinb): use an enum for this
|
|
4782
|
+
right;
|
|
4016
4783
|
constructor(left, type, right) {
|
|
4017
4784
|
super();
|
|
4018
|
-
this.left = void 0;
|
|
4019
|
-
this.type = void 0;
|
|
4020
|
-
this.right = void 0;
|
|
4021
|
-
this.func = Eq;
|
|
4022
|
-
this.signs = {
|
|
4023
|
-
"=": " = ",
|
|
4024
|
-
"<": " < ",
|
|
4025
|
-
">": " > ",
|
|
4026
|
-
"<>": " \\ne ",
|
|
4027
|
-
"<=": " \\le ",
|
|
4028
|
-
">=": " \\ge "
|
|
4029
|
-
};
|
|
4030
4785
|
this.left = left;
|
|
4031
4786
|
this.type = type;
|
|
4032
4787
|
this.right = right;
|
|
4033
4788
|
}
|
|
4789
|
+
func = Eq;
|
|
4034
4790
|
args() {
|
|
4035
4791
|
return [this.left, this.type, this.right];
|
|
4036
4792
|
}
|
|
@@ -4040,24 +4796,41 @@ class Eq extends Expr {
|
|
|
4040
4796
|
print() {
|
|
4041
4797
|
return this.left.print() + this.type + this.right.print();
|
|
4042
4798
|
}
|
|
4799
|
+
signs = {
|
|
4800
|
+
"=": " = ",
|
|
4801
|
+
"<": " < ",
|
|
4802
|
+
">": " > ",
|
|
4803
|
+
"<>": " \\ne ",
|
|
4804
|
+
"<=": " \\le ",
|
|
4805
|
+
">=": " \\ge "
|
|
4806
|
+
};
|
|
4043
4807
|
tex() {
|
|
4044
4808
|
return this.left.tex() + this.signs[this.type] + this.right.tex();
|
|
4045
4809
|
}
|
|
4046
4810
|
normalize() {
|
|
4047
4811
|
var eq = this.recurse("normalize");
|
|
4048
|
-
if (
|
|
4812
|
+
if (___default.default.contains([">", ">="], eq.type)) {
|
|
4813
|
+
// inequalities should have the smaller side on the left
|
|
4049
4814
|
return new Eq(eq.right, eq.type.replace(">", "<"), eq.left);
|
|
4050
4815
|
} else {
|
|
4051
4816
|
return eq;
|
|
4052
4817
|
}
|
|
4053
4818
|
}
|
|
4054
|
-
|
|
4819
|
+
|
|
4820
|
+
// convert this equation to an expression set to zero
|
|
4821
|
+
// the expression is normalized to a canonical form
|
|
4822
|
+
// e.g. y/2=x/4 -> y/2-x/4(=0) -> 2y-x(=0)
|
|
4823
|
+
// unless unfactored is specified, will then divide through
|
|
4824
|
+
asExpr() {
|
|
4825
|
+
let unfactored = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
|
|
4055
4826
|
var isZero = expr => {
|
|
4056
4827
|
return expr instanceof Num && expr.isSimple() && expr.eval() === 0;
|
|
4057
4828
|
};
|
|
4829
|
+
|
|
4830
|
+
// first convert to a sequence of additive terms
|
|
4058
4831
|
let terms = [];
|
|
4059
4832
|
if (this.left instanceof Add) {
|
|
4060
|
-
terms =
|
|
4833
|
+
terms = ___default.default.clone(this.left.terms);
|
|
4061
4834
|
} else if (!isZero(this.left)) {
|
|
4062
4835
|
terms = [this.left];
|
|
4063
4836
|
}
|
|
@@ -4067,16 +4840,26 @@ class Eq extends Expr {
|
|
|
4067
4840
|
terms.push(this.right.negate());
|
|
4068
4841
|
}
|
|
4069
4842
|
var isInequality = !this.isEquality();
|
|
4070
|
-
|
|
4843
|
+
|
|
4844
|
+
// Collect over each term individually to transform simple expressions
|
|
4845
|
+
// into numbers that might have denominators, taking into account
|
|
4846
|
+
// float precision. We have to be very careful to not introduce any
|
|
4847
|
+
// irrational floats before asExpr() returns, because by definition
|
|
4848
|
+
// they do not have exact denominators...
|
|
4849
|
+
terms = ___default.default.invoke(terms, "collect", {
|
|
4071
4850
|
preciseFloats: true
|
|
4072
4851
|
});
|
|
4852
|
+
|
|
4853
|
+
// ...and we multiply through by every denominator.
|
|
4073
4854
|
for (var i = 0; i < terms.length; i++) {
|
|
4074
4855
|
var denominator = terms[i].getDenominator();
|
|
4856
|
+
|
|
4857
|
+
// Can't multiply inequalities by non 100% positive factors
|
|
4075
4858
|
if (isInequality && !denominator.isPositive()) {
|
|
4076
4859
|
denominator = denominator.asPositiveFactor();
|
|
4077
4860
|
}
|
|
4078
4861
|
if (!denominator.equals(NumOne)) {
|
|
4079
|
-
terms =
|
|
4862
|
+
terms = ___default.default.map(terms, term => {
|
|
4080
4863
|
return Mul.createOrAppend(term, denominator).simplify({
|
|
4081
4864
|
once: true,
|
|
4082
4865
|
preciseFloats: true
|
|
@@ -4087,6 +4870,11 @@ class Eq extends Expr {
|
|
|
4087
4870
|
var add = new Add(terms).flatten();
|
|
4088
4871
|
return unfactored ? add : this.divideThrough(add);
|
|
4089
4872
|
}
|
|
4873
|
+
|
|
4874
|
+
// divide through by every common factor in the expression
|
|
4875
|
+
// e.g. 2y-4x(=0) -> y-2x(=0)
|
|
4876
|
+
// TODO(alex): Make it an option to only divide by variables/expressions
|
|
4877
|
+
// guaranteed to be nonzero
|
|
4090
4878
|
divideThrough(expr) {
|
|
4091
4879
|
const isInequality = !this.isEquality();
|
|
4092
4880
|
const simplified = expr.simplify({
|
|
@@ -4099,24 +4887,41 @@ class Eq extends Expr {
|
|
|
4099
4887
|
return expr;
|
|
4100
4888
|
}
|
|
4101
4889
|
const terms = factored.terms;
|
|
4890
|
+
|
|
4891
|
+
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
|
4102
4892
|
const hasVar = term => !!term.getVars().length;
|
|
4103
4893
|
const isOne = term => term.equals(NumOne);
|
|
4104
4894
|
const [adds, others] = partition(terms, isAdd);
|
|
4895
|
+
|
|
4896
|
+
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
|
4105
4897
|
if (adds.length && this.isEquality()) {
|
|
4898
|
+
// keep only Adds
|
|
4899
|
+
// e.g. 2xy(z+1)(=0) -> z+1(=0)
|
|
4106
4900
|
return new Mul(adds).flatten();
|
|
4107
4901
|
}
|
|
4108
4902
|
let denominator = others;
|
|
4903
|
+
|
|
4904
|
+
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
|
4109
4905
|
if (!adds.length) {
|
|
4110
|
-
|
|
4906
|
+
// if no Adds, keep all variable terms to preserve meaning
|
|
4907
|
+
// e.g. 42xyz(=0) -> xyz(=0)
|
|
4908
|
+
denominator = ___default.default.reject(denominator, hasVar);
|
|
4111
4909
|
}
|
|
4112
4910
|
if (isInequality) {
|
|
4113
|
-
|
|
4911
|
+
// can't divide inequalities by non 100% positive factors
|
|
4912
|
+
// e.g. 42x^2y(z+1)(=0) -> y(z+1)(=0)
|
|
4913
|
+
denominator = ___default.default.invoke(denominator, "asPositiveFactor");
|
|
4114
4914
|
}
|
|
4115
|
-
|
|
4116
|
-
|
|
4915
|
+
|
|
4916
|
+
// don't need to divide by one
|
|
4917
|
+
denominator = ___default.default.reject(denominator, isOne);
|
|
4918
|
+
denominator = ___default.default.map(denominator, term => {
|
|
4117
4919
|
return new Pow(term, NumDiv);
|
|
4118
4920
|
});
|
|
4119
4921
|
const dividedResult = new Mul(terms.concat(denominator)).collect();
|
|
4922
|
+
|
|
4923
|
+
// If the end result is the same as the original factoring,
|
|
4924
|
+
// rollback the factoring and discard all intermediate steps.
|
|
4120
4925
|
if (dividedResult.equals(factored)) {
|
|
4121
4926
|
return simplified;
|
|
4122
4927
|
} else {
|
|
@@ -4124,9 +4929,10 @@ class Eq extends Expr {
|
|
|
4124
4929
|
}
|
|
4125
4930
|
}
|
|
4126
4931
|
isEquality() {
|
|
4127
|
-
return
|
|
4932
|
+
return ___default.default.contains(["=", "<>"], this.type);
|
|
4128
4933
|
}
|
|
4129
4934
|
compare(other) {
|
|
4935
|
+
// expression comparisons are handled by Expr.compare()
|
|
4130
4936
|
if (!(other instanceof Eq)) {
|
|
4131
4937
|
return false;
|
|
4132
4938
|
}
|
|
@@ -4135,36 +4941,48 @@ class Eq extends Expr {
|
|
|
4135
4941
|
if (eq1.type !== eq2.type) {
|
|
4136
4942
|
return false;
|
|
4137
4943
|
}
|
|
4138
|
-
|
|
4139
|
-
|
|
4944
|
+
|
|
4945
|
+
// need to collect to properly factor out common factors
|
|
4946
|
+
// e.g x+2x=6 -> 3x=6 -> 3x-6(=0) -> x-2(=0)
|
|
4947
|
+
var expr1 = eq1.divideThrough(eq1.asExpr(/* unfactored */true).collect());
|
|
4948
|
+
var expr2 = eq2.divideThrough(eq2.asExpr(/* unfactored */true).collect());
|
|
4140
4949
|
if (eq1.isEquality()) {
|
|
4950
|
+
// equals and not-equals can be subtracted either way
|
|
4141
4951
|
return expr1.compare(expr2) || expr1.compare(Mul.handleNegative(expr2));
|
|
4142
4952
|
} else {
|
|
4143
4953
|
return expr1.compare(expr2);
|
|
4144
4954
|
}
|
|
4145
4955
|
}
|
|
4956
|
+
|
|
4957
|
+
// should only be done after compare() returns true to avoid false positives
|
|
4146
4958
|
sameForm(other) {
|
|
4147
4959
|
var eq1 = this.normalize();
|
|
4148
4960
|
var eq2 = other.normalize();
|
|
4149
4961
|
var same = eq1.left.sameForm(eq2.left) && eq1.right.sameForm(eq2.right);
|
|
4150
4962
|
if (eq1.isEquality()) {
|
|
4963
|
+
// equals and not-equals can be commutative with respect to the sign
|
|
4151
4964
|
return same || eq1.left.sameForm(eq2.right) && eq1.right.sameForm(eq2.left);
|
|
4152
4965
|
} else {
|
|
4153
4966
|
return same;
|
|
4154
4967
|
}
|
|
4155
4968
|
}
|
|
4969
|
+
|
|
4970
|
+
// we don't want to override collect because it would turn y=x into y-x(=0)
|
|
4971
|
+
// instead, we ask if the equation was in that form, would it be simplified?
|
|
4156
4972
|
isSimplified() {
|
|
4157
|
-
var expr = this.asExpr(true);
|
|
4973
|
+
var expr = this.asExpr(/* unfactored */true);
|
|
4158
4974
|
var simplified = this.divideThrough(expr).simplify();
|
|
4159
4975
|
return expr.equals(simplified) && this.left.isSimplified() && this.right.isSimplified();
|
|
4160
4976
|
}
|
|
4977
|
+
|
|
4978
|
+
// Assumptions: Expression is of the form a+bx, and we solve for x
|
|
4161
4979
|
solveLinearEquationForVariable(variable) {
|
|
4162
4980
|
var expr = this.asExpr();
|
|
4163
4981
|
if (!(expr instanceof Add) || expr.terms.length !== 2) {
|
|
4164
4982
|
throw new Error("Can only handle linear equations of the form " + "a + bx (= 0)");
|
|
4165
4983
|
}
|
|
4166
4984
|
var hasVar = term => {
|
|
4167
|
-
return term.has(Var) &&
|
|
4985
|
+
return term.has(Var) && ___default.default.contains(term.getVars(), variable.symbol);
|
|
4168
4986
|
};
|
|
4169
4987
|
const termHasVar = hasVar(expr.terms[0]);
|
|
4170
4988
|
const a = termHasVar ? Mul.handleNegative(expr.terms[1]) : Mul.handleNegative(expr.terms[0]);
|
|
@@ -4172,6 +4990,8 @@ class Eq extends Expr {
|
|
|
4172
4990
|
return Mul.handleDivide(a, b).simplify();
|
|
4173
4991
|
}
|
|
4174
4992
|
}
|
|
4993
|
+
|
|
4994
|
+
/* abstract symbol node */
|
|
4175
4995
|
class Sym extends Expr {
|
|
4176
4996
|
needsExplicitMul() {
|
|
4177
4997
|
return false;
|
|
@@ -4184,15 +5004,17 @@ class Sym extends Expr {
|
|
|
4184
5004
|
}
|
|
4185
5005
|
}
|
|
4186
5006
|
}
|
|
5007
|
+
|
|
5008
|
+
/* function variable */
|
|
4187
5009
|
class Func extends Sym {
|
|
5010
|
+
symbol;
|
|
5011
|
+
arg;
|
|
4188
5012
|
constructor(symbol, arg) {
|
|
4189
5013
|
super();
|
|
4190
|
-
this.symbol = void 0;
|
|
4191
|
-
this.arg = void 0;
|
|
4192
|
-
this.func = Func;
|
|
4193
5014
|
this.symbol = symbol;
|
|
4194
5015
|
this.arg = arg;
|
|
4195
5016
|
}
|
|
5017
|
+
func = Func;
|
|
4196
5018
|
args() {
|
|
4197
5019
|
return [this.symbol, this.arg];
|
|
4198
5020
|
}
|
|
@@ -4202,16 +5024,19 @@ class Func extends Sym {
|
|
|
4202
5024
|
tex() {
|
|
4203
5025
|
return this.symbol + "(" + this.arg.tex() + ")";
|
|
4204
5026
|
}
|
|
4205
|
-
eval(
|
|
5027
|
+
eval() {
|
|
5028
|
+
let vars = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
5029
|
+
let options = arguments.length > 1 ? arguments[1] : undefined;
|
|
4206
5030
|
var arg = this.arg;
|
|
4207
5031
|
var func = vars[this.symbol];
|
|
4208
|
-
var newVars =
|
|
5032
|
+
var newVars = ___default.default.extend(___default.default.clone(vars), {
|
|
4209
5033
|
x: arg.eval(vars, options)
|
|
4210
5034
|
});
|
|
4211
5035
|
var parsedFunc = parse(func, options);
|
|
4212
5036
|
if (parsedFunc.parsed) {
|
|
4213
5037
|
return parsedFunc.expr.eval(newVars, options);
|
|
4214
5038
|
}
|
|
5039
|
+
// If parsedFunc isn't actually parsed, return its error
|
|
4215
5040
|
return parsedFunc;
|
|
4216
5041
|
}
|
|
4217
5042
|
codegen() {
|
|
@@ -4224,22 +5049,24 @@ class Func extends Sym {
|
|
|
4224
5049
|
if (excludeFunc) {
|
|
4225
5050
|
return this.arg.getVars();
|
|
4226
5051
|
} else {
|
|
4227
|
-
return
|
|
5052
|
+
return ___default.default.union(this.arg.getVars(), [this.symbol]).sort();
|
|
4228
5053
|
}
|
|
4229
5054
|
}
|
|
4230
5055
|
getConsts() {
|
|
4231
5056
|
return this.arg.getConsts();
|
|
4232
5057
|
}
|
|
4233
5058
|
}
|
|
5059
|
+
|
|
5060
|
+
/* variable */
|
|
4234
5061
|
class Var extends Sym {
|
|
5062
|
+
symbol;
|
|
5063
|
+
subscript;
|
|
4235
5064
|
constructor(symbol, subscript) {
|
|
4236
5065
|
super();
|
|
4237
|
-
this.symbol = void 0;
|
|
4238
|
-
this.subscript = void 0;
|
|
4239
|
-
this.func = Var;
|
|
4240
5066
|
this.symbol = symbol;
|
|
4241
5067
|
this.subscript = subscript;
|
|
4242
5068
|
}
|
|
5069
|
+
func = Var;
|
|
4243
5070
|
args() {
|
|
4244
5071
|
return [this.symbol, this.subscript];
|
|
4245
5072
|
}
|
|
@@ -4256,6 +5083,9 @@ class Var extends Sym {
|
|
|
4256
5083
|
}
|
|
4257
5084
|
return this.symbol + sub;
|
|
4258
5085
|
}
|
|
5086
|
+
|
|
5087
|
+
// Provide a way to easily evalate expressions with the common case,
|
|
5088
|
+
// subscripts that consist of a single number or symbol e.g. x_a or x_42
|
|
4259
5089
|
prettyPrint() {
|
|
4260
5090
|
var sub = this.subscript;
|
|
4261
5091
|
if (sub && (sub instanceof Num || sub instanceof Sym)) {
|
|
@@ -4275,7 +5105,12 @@ class Var extends Sym {
|
|
|
4275
5105
|
repr() {
|
|
4276
5106
|
return "Var(" + this.print() + ")";
|
|
4277
5107
|
}
|
|
4278
|
-
eval(
|
|
5108
|
+
eval() {
|
|
5109
|
+
let vars = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
5110
|
+
// @ts-expect-error: values is Vars are strings, but here
|
|
5111
|
+
// we expect them to be numbers. We should probably store
|
|
5112
|
+
// Var and Func entries separately in the Vars type so that
|
|
5113
|
+
// they can be typed correctly.
|
|
4279
5114
|
return vars[this.prettyPrint()];
|
|
4280
5115
|
}
|
|
4281
5116
|
codegen() {
|
|
@@ -4288,25 +5123,28 @@ class Var extends Sym {
|
|
|
4288
5123
|
return false;
|
|
4289
5124
|
}
|
|
4290
5125
|
}
|
|
5126
|
+
|
|
5127
|
+
/* constant */
|
|
4291
5128
|
class Const extends Sym {
|
|
5129
|
+
symbol;
|
|
4292
5130
|
constructor(symbol) {
|
|
4293
5131
|
super();
|
|
4294
|
-
this.symbol = void 0;
|
|
4295
|
-
this.func = Const;
|
|
4296
5132
|
this.symbol = symbol;
|
|
4297
5133
|
}
|
|
5134
|
+
func = Const;
|
|
4298
5135
|
args() {
|
|
4299
5136
|
return [this.symbol];
|
|
4300
5137
|
}
|
|
4301
5138
|
recurse() {
|
|
4302
5139
|
return this;
|
|
4303
5140
|
}
|
|
4304
|
-
eval(
|
|
5141
|
+
eval() {
|
|
4305
5142
|
if (this.symbol === "pi") {
|
|
4306
5143
|
return Math.PI;
|
|
4307
5144
|
} else if (this.symbol === "e") {
|
|
4308
5145
|
return Math.E;
|
|
4309
5146
|
} else {
|
|
5147
|
+
// @ts-expect-error: should we throw an error here?
|
|
4310
5148
|
return undefined;
|
|
4311
5149
|
}
|
|
4312
5150
|
}
|
|
@@ -4316,6 +5154,7 @@ class Const extends Sym {
|
|
|
4316
5154
|
} else if (this.symbol === "e") {
|
|
4317
5155
|
return "Math.E";
|
|
4318
5156
|
} else {
|
|
5157
|
+
// @ts-expect-error: should we throw an error here?
|
|
4319
5158
|
return undefined;
|
|
4320
5159
|
}
|
|
4321
5160
|
}
|
|
@@ -4328,6 +5167,7 @@ class Const extends Sym {
|
|
|
4328
5167
|
} else if (this.symbol === "e") {
|
|
4329
5168
|
return "e";
|
|
4330
5169
|
} else {
|
|
5170
|
+
// @ts-expect-error: should we return this.symbol here?
|
|
4331
5171
|
return undefined;
|
|
4332
5172
|
}
|
|
4333
5173
|
}
|
|
@@ -4344,22 +5184,25 @@ class Const extends Sym {
|
|
|
4344
5184
|
getConsts() {
|
|
4345
5185
|
return [this.print()];
|
|
4346
5186
|
}
|
|
5187
|
+
static e = new Const("e");
|
|
5188
|
+
static pi = new Const("pi");
|
|
4347
5189
|
}
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
Const.pi = new _class12("pi");
|
|
5190
|
+
|
|
5191
|
+
/* abstract number node */
|
|
4351
5192
|
class Num extends Expr {
|
|
5193
|
+
n = 0;
|
|
4352
5194
|
constructor() {
|
|
4353
5195
|
super();
|
|
4354
|
-
|
|
4355
|
-
this.hints =
|
|
5196
|
+
// hints for interpreting and rendering user input
|
|
5197
|
+
this.hints = {
|
|
5198
|
+
...this.hints,
|
|
4356
5199
|
negate: false,
|
|
4357
5200
|
subtract: false,
|
|
4358
5201
|
divide: false,
|
|
4359
5202
|
root: false,
|
|
4360
5203
|
fraction: false,
|
|
4361
5204
|
entered: false
|
|
4362
|
-
}
|
|
5205
|
+
};
|
|
4363
5206
|
}
|
|
4364
5207
|
repr() {
|
|
4365
5208
|
return this.print();
|
|
@@ -4373,9 +5216,17 @@ class Num extends Expr {
|
|
|
4373
5216
|
codegen() {
|
|
4374
5217
|
return this.print();
|
|
4375
5218
|
}
|
|
5219
|
+
|
|
5220
|
+
// takes another Num and returns a new Num
|
|
5221
|
+
|
|
5222
|
+
// returns this Num's additive inverse
|
|
5223
|
+
|
|
4376
5224
|
isSubtract() {
|
|
4377
5225
|
return Boolean(this.hints.subtract);
|
|
4378
5226
|
}
|
|
5227
|
+
|
|
5228
|
+
// return the absolute value of the number
|
|
5229
|
+
|
|
4379
5230
|
needsExplicitMul() {
|
|
4380
5231
|
return true;
|
|
4381
5232
|
}
|
|
@@ -4388,10 +5239,19 @@ class Num extends Expr {
|
|
|
4388
5239
|
asPositiveFactor() {
|
|
4389
5240
|
return this.isPositive() ? this : this.abs();
|
|
4390
5241
|
}
|
|
5242
|
+
|
|
5243
|
+
// whether a number is considered simple (one term)
|
|
5244
|
+
// e.g. for reals, ints and floats are simple
|
|
5245
|
+
|
|
5246
|
+
// Based on http://stackoverflow.com/a/10454560/2571482
|
|
4391
5247
|
getDecimalPlaces() {
|
|
4392
5248
|
var match = ("" + this.n).match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/);
|
|
4393
5249
|
if (match) {
|
|
4394
|
-
return Math.max(0,
|
|
5250
|
+
return Math.max(0,
|
|
5251
|
+
// Number of digits right of decimal point
|
|
5252
|
+
(match[1] ? match[1].length : 0) - (
|
|
5253
|
+
// Adjust for scientific notation
|
|
5254
|
+
match[2] ? +match[2] : 0));
|
|
4395
5255
|
} else {
|
|
4396
5256
|
return 0;
|
|
4397
5257
|
}
|
|
@@ -4405,10 +5265,16 @@ class Num extends Expr {
|
|
|
4405
5265
|
return NumNeg;
|
|
4406
5266
|
}
|
|
4407
5267
|
}
|
|
5268
|
+
|
|
5269
|
+
// find the greatest common denominator
|
|
4408
5270
|
static findGCD(a, b) {
|
|
4409
5271
|
var mod;
|
|
4410
5272
|
a = Math.abs(a);
|
|
4411
5273
|
b = Math.abs(b);
|
|
5274
|
+
|
|
5275
|
+
// Euclid's method doesn't handle non-integers very well. For now
|
|
5276
|
+
// we just say we can't pull out a common factor. It might be
|
|
5277
|
+
// reasonable to do better than this in the future.
|
|
4412
5278
|
if (a !== Math.floor(a) || b !== Math.floor(b)) {
|
|
4413
5279
|
return 1;
|
|
4414
5280
|
}
|
|
@@ -4419,19 +5285,26 @@ class Num extends Expr {
|
|
|
4419
5285
|
}
|
|
4420
5286
|
return a;
|
|
4421
5287
|
}
|
|
4422
|
-
static min(
|
|
4423
|
-
|
|
5288
|
+
static min() {
|
|
5289
|
+
for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
|
|
5290
|
+
args[_key3] = arguments[_key3];
|
|
5291
|
+
}
|
|
5292
|
+
return ___default.default.min(args, num => num.eval());
|
|
4424
5293
|
}
|
|
4425
|
-
static max(
|
|
4426
|
-
|
|
5294
|
+
static max() {
|
|
5295
|
+
for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
|
|
5296
|
+
args[_key4] = arguments[_key4];
|
|
5297
|
+
}
|
|
5298
|
+
return ___default.default.max(args, num => num.eval());
|
|
4427
5299
|
}
|
|
4428
5300
|
}
|
|
5301
|
+
|
|
5302
|
+
/* rational number (n: numerator, d: denominator) */
|
|
4429
5303
|
class Rational extends Num {
|
|
5304
|
+
n;
|
|
5305
|
+
d;
|
|
4430
5306
|
constructor(numerator, denominator) {
|
|
4431
5307
|
super();
|
|
4432
|
-
this.n = void 0;
|
|
4433
|
-
this.d = void 0;
|
|
4434
|
-
this.func = Rational;
|
|
4435
5308
|
var n = numerator;
|
|
4436
5309
|
var d = denominator;
|
|
4437
5310
|
if (d < 0) {
|
|
@@ -4441,6 +5314,7 @@ class Rational extends Num {
|
|
|
4441
5314
|
this.n = n;
|
|
4442
5315
|
this.d = d;
|
|
4443
5316
|
}
|
|
5317
|
+
func = Rational;
|
|
4444
5318
|
args() {
|
|
4445
5319
|
return [this.n, this.d];
|
|
4446
5320
|
}
|
|
@@ -4485,9 +5359,14 @@ class Rational extends Num {
|
|
|
4485
5359
|
return new Rational(Math.abs(this.n), this.d);
|
|
4486
5360
|
}
|
|
4487
5361
|
findGCD(factor) {
|
|
5362
|
+
// Attempt to factor out common numerators and denominators to return
|
|
5363
|
+
// a Rational instead of a Float
|
|
4488
5364
|
if (factor instanceof Rational) {
|
|
5365
|
+
// For more background, see
|
|
5366
|
+
// http://math.stackexchange.com/questions/151081/gcd-of-rationals
|
|
4489
5367
|
var numerator = Num.findGCD(this.n * factor.d, factor.n * this.d);
|
|
4490
5368
|
var denominator = this.d * factor.d;
|
|
5369
|
+
// Create the rational, then call .collect() to simplify it
|
|
4491
5370
|
return new Rational(numerator, denominator).collect();
|
|
4492
5371
|
} else if (factor instanceof Int) {
|
|
4493
5372
|
return new Rational(Num.findGCD(this.n, factor.n), this.d);
|
|
@@ -4495,6 +5374,8 @@ class Rational extends Num {
|
|
|
4495
5374
|
return factor.findGCD(this);
|
|
4496
5375
|
}
|
|
4497
5376
|
}
|
|
5377
|
+
|
|
5378
|
+
// for now, assuming that exp is a Num
|
|
4498
5379
|
raiseToThe(exp) {
|
|
4499
5380
|
if (exp instanceof Int) {
|
|
4500
5381
|
var positive = exp.eval() > 0;
|
|
@@ -4520,11 +5401,13 @@ class Rational extends Num {
|
|
|
4520
5401
|
return this;
|
|
4521
5402
|
}
|
|
4522
5403
|
}
|
|
5404
|
+
|
|
5405
|
+
/* integer (n: numerator/number) */
|
|
4523
5406
|
class Int extends Rational {
|
|
4524
5407
|
constructor(number) {
|
|
4525
5408
|
super(number, 1);
|
|
4526
|
-
this.func = Int;
|
|
4527
5409
|
}
|
|
5410
|
+
func = Int;
|
|
4528
5411
|
args() {
|
|
4529
5412
|
return [this.n];
|
|
4530
5413
|
}
|
|
@@ -4554,19 +5437,24 @@ class Int extends Rational {
|
|
|
4554
5437
|
return new Int(n).addHint("entered");
|
|
4555
5438
|
}
|
|
4556
5439
|
}
|
|
5440
|
+
|
|
5441
|
+
/* float (n: number) */
|
|
4557
5442
|
class Float extends Num {
|
|
5443
|
+
n;
|
|
4558
5444
|
constructor(number) {
|
|
4559
5445
|
super();
|
|
4560
|
-
this.n = void 0;
|
|
4561
|
-
this.func = Float;
|
|
4562
5446
|
this.n = number;
|
|
4563
5447
|
}
|
|
5448
|
+
func = Float;
|
|
4564
5449
|
args() {
|
|
4565
5450
|
return [this.n];
|
|
4566
5451
|
}
|
|
4567
5452
|
eval() {
|
|
4568
5453
|
return this.n;
|
|
4569
5454
|
}
|
|
5455
|
+
|
|
5456
|
+
// TODO(alex): when we internationalize number parsing/display
|
|
5457
|
+
// we should make sure to use the appropriate decimal mark here
|
|
4570
5458
|
print() {
|
|
4571
5459
|
return this.n.toString();
|
|
4572
5460
|
}
|
|
@@ -4588,6 +5476,8 @@ class Float extends Num {
|
|
|
4588
5476
|
}
|
|
4589
5477
|
}
|
|
4590
5478
|
collect(options) {
|
|
5479
|
+
// We used to simplify Floats to Ints here whenever possible, but no
|
|
5480
|
+
// longer do so in order to preserve significant figures.
|
|
4591
5481
|
return this;
|
|
4592
5482
|
}
|
|
4593
5483
|
negate() {
|
|
@@ -4603,6 +5493,8 @@ class Float extends Num {
|
|
|
4603
5493
|
return factor.findGCD(this);
|
|
4604
5494
|
}
|
|
4605
5495
|
}
|
|
5496
|
+
|
|
5497
|
+
// for now, assuming that exp is a Num
|
|
4606
5498
|
raiseToThe(exp, options) {
|
|
4607
5499
|
if (options && options.preciseFloats && exp instanceof Int && exp.n > 1) {
|
|
4608
5500
|
return Float.toDecimalPlaces(new Pow(this, exp).eval(), this.getDecimalPlaces() * exp.n);
|
|
@@ -4610,6 +5502,8 @@ class Float extends Num {
|
|
|
4610
5502
|
return new Float(new Pow(this, exp).eval()).collect();
|
|
4611
5503
|
}
|
|
4612
5504
|
}
|
|
5505
|
+
|
|
5506
|
+
// only to be used on non-repeating decimals (e.g. user-provided)
|
|
4613
5507
|
asRational() {
|
|
4614
5508
|
var parts = this.n.toString().split(".");
|
|
4615
5509
|
if (parts.length === 1) {
|
|
@@ -4629,6 +5523,9 @@ class Float extends Num {
|
|
|
4629
5523
|
static create(n) {
|
|
4630
5524
|
return new Float(n).addHint("entered");
|
|
4631
5525
|
}
|
|
5526
|
+
|
|
5527
|
+
// Account for floating point imprecision by explicitly controlling the
|
|
5528
|
+
// number of decimal places in common operations (e.g. +, *, ^)
|
|
4632
5529
|
static toDecimalPlaces(n, places) {
|
|
4633
5530
|
return new Float(+n.toFixed(Math.min(places, 20))).collect();
|
|
4634
5531
|
}
|
|
@@ -4640,9 +5537,13 @@ const NumSqrt = new Rational(1, 2).addHint("root");
|
|
|
4640
5537
|
const NumZero = new Int(0);
|
|
4641
5538
|
const NumOne = new Int(1);
|
|
4642
5539
|
const NumTen = new Int(10);
|
|
4643
|
-
var parseError = function
|
|
5540
|
+
var parseError = function (str, hash) {
|
|
5541
|
+
// return int location of parsing error
|
|
4644
5542
|
throw new Error(hash.loc.first_column);
|
|
4645
5543
|
};
|
|
5544
|
+
|
|
5545
|
+
// expose concrete nodes to parser scope
|
|
5546
|
+
// see http://zaach.github.io/jison/docs/#sharing-scope
|
|
4646
5547
|
parser.yy = {
|
|
4647
5548
|
Add: Add,
|
|
4648
5549
|
Mul: Mul,
|
|
@@ -4659,22 +5560,28 @@ parser.yy = {
|
|
|
4659
5560
|
parseError: parseError,
|
|
4660
5561
|
constants: ["e"],
|
|
4661
5562
|
symbolLexer: function (symbol) {
|
|
4662
|
-
if (
|
|
5563
|
+
if (___default.default.contains(parser.yy.constants, symbol)) {
|
|
4663
5564
|
return "CONST";
|
|
4664
|
-
} else if (
|
|
5565
|
+
} else if (___default.default.contains(parser.yy.functions, symbol)) {
|
|
4665
5566
|
return "FUNC";
|
|
4666
5567
|
} else {
|
|
4667
5568
|
return "VAR";
|
|
4668
5569
|
}
|
|
4669
5570
|
}
|
|
4670
5571
|
};
|
|
4671
|
-
const parse = function
|
|
5572
|
+
const parse = function (input, options) {
|
|
4672
5573
|
try {
|
|
4673
5574
|
if (options && options.functions) {
|
|
4674
|
-
|
|
5575
|
+
// reserve the symbol "i" for complex numbers
|
|
5576
|
+
parser.yy.functions = ___default.default.without(options.functions, "i");
|
|
4675
5577
|
} else {
|
|
4676
5578
|
parser.yy.functions = [];
|
|
4677
5579
|
}
|
|
5580
|
+
|
|
5581
|
+
// If ',' is the decimal dividor in your country, replace any ','s
|
|
5582
|
+
// with '.'s.
|
|
5583
|
+
// This isn't perfect, since the output will all still have '.'s.
|
|
5584
|
+
// TODO(jack): Fix the output to have ','s in this case
|
|
4678
5585
|
if (options && options.decimal_separator) {
|
|
4679
5586
|
input = input.split(options.decimal_separator).join(".");
|
|
4680
5587
|
}
|
|
@@ -4690,20 +5597,29 @@ const parse = function parse(input, options) {
|
|
|
4690
5597
|
};
|
|
4691
5598
|
}
|
|
4692
5599
|
};
|
|
5600
|
+
|
|
5601
|
+
/* unit */
|
|
4693
5602
|
class Unit extends Sym {
|
|
5603
|
+
symbol;
|
|
4694
5604
|
constructor(symbol) {
|
|
4695
5605
|
super();
|
|
4696
|
-
this.symbol = void 0;
|
|
4697
|
-
this.func = Unit;
|
|
4698
5606
|
this.symbol = symbol;
|
|
4699
5607
|
}
|
|
5608
|
+
func = Unit;
|
|
4700
5609
|
args() {
|
|
4701
5610
|
return [this.symbol];
|
|
4702
5611
|
}
|
|
4703
5612
|
recurse() {
|
|
4704
5613
|
return this;
|
|
4705
5614
|
}
|
|
4706
|
-
eval(
|
|
5615
|
+
eval() {
|
|
5616
|
+
// This is called when comparing units. A unit doesn't affect the
|
|
5617
|
+
// numerical value of its coefficient, so this needs to be 1.
|
|
5618
|
+
//
|
|
5619
|
+
// On the other hand, things must not evaluate to the same thing if
|
|
5620
|
+
// they don't have the same type. I believe that's also true - form is
|
|
5621
|
+
// checked before numerical equivalence. I do not know where, though.
|
|
5622
|
+
// However, there are a couple tests checking this.
|
|
4707
5623
|
return 1;
|
|
4708
5624
|
}
|
|
4709
5625
|
getUnits() {
|
|
@@ -4721,26 +5637,42 @@ class Unit extends Sym {
|
|
|
4721
5637
|
tex() {
|
|
4722
5638
|
return this.symbol;
|
|
4723
5639
|
}
|
|
5640
|
+
|
|
5641
|
+
// Simplify units by replacing prefixes with multiplication
|
|
4724
5642
|
collect(options) {
|
|
4725
|
-
if (
|
|
5643
|
+
if (___default.default(baseUnits).has(this.symbol)) {
|
|
4726
5644
|
return this;
|
|
4727
|
-
} else if (
|
|
5645
|
+
} else if (___default.default(derivedUnits).has(this.symbol)) {
|
|
4728
5646
|
return derivedUnits[this.symbol].conversion;
|
|
4729
5647
|
} else {
|
|
4730
5648
|
throw new Error("could not understand unit: " + this.symbol);
|
|
4731
5649
|
}
|
|
4732
5650
|
}
|
|
4733
5651
|
}
|
|
4734
|
-
|
|
4735
|
-
|
|
5652
|
+
|
|
5653
|
+
// If possible, replace unit prefixes with a multiplication.
|
|
5654
|
+
//
|
|
5655
|
+
// "g" -> Unit("g")
|
|
5656
|
+
// "kg" -> 1000 * Unit("g")
|
|
5657
|
+
var unprefixify = function (symbol) {
|
|
5658
|
+
if (___default.default(baseUnits).has(symbol) || ___default.default(derivedUnits).has(symbol)) {
|
|
4736
5659
|
return new Unit(symbol);
|
|
4737
5660
|
}
|
|
4738
|
-
|
|
5661
|
+
|
|
5662
|
+
// check for prefix
|
|
5663
|
+
var prefix = ___default.default(___default.default(siPrefixes).keys()).find(testPrefix => {
|
|
4739
5664
|
return new RegExp("^" + testPrefix).test(symbol);
|
|
4740
5665
|
});
|
|
4741
5666
|
if (prefix) {
|
|
4742
5667
|
var base = symbol.replace(new RegExp("^" + prefix), "");
|
|
4743
|
-
|
|
5668
|
+
|
|
5669
|
+
// It's okay to be here if either:
|
|
5670
|
+
// * `base` is a base unit (the seven units listed in baseUnits)
|
|
5671
|
+
// * `base` is a derived unit which allows prefixes
|
|
5672
|
+
//
|
|
5673
|
+
// Otherwise, we're trying to parse a unit label which is not
|
|
5674
|
+
// allowed (mwk, mBTU, etc).
|
|
5675
|
+
if (___default.default(baseUnits).has(base) || derivedUnits[base] && derivedUnits[base].prefixes === hasPrefixes) {
|
|
4744
5676
|
return new Mul(siPrefixes[prefix], new Unit(base));
|
|
4745
5677
|
} else {
|
|
4746
5678
|
throw new Error(base + " does not allow prefixes");
|
|
@@ -4749,18 +5681,36 @@ var unprefixify = function unprefixify(symbol) {
|
|
|
4749
5681
|
return new Unit(symbol);
|
|
4750
5682
|
}
|
|
4751
5683
|
};
|
|
4752
|
-
const unitParse = function
|
|
5684
|
+
const unitParse = function (input) {
|
|
4753
5685
|
try {
|
|
4754
5686
|
var parseResult = unitParser.parse(input);
|
|
5687
|
+
|
|
5688
|
+
// parseResult looks like:
|
|
5689
|
+
// {
|
|
5690
|
+
// magnitude: "5",
|
|
5691
|
+
// unit: {
|
|
5692
|
+
// num: [
|
|
5693
|
+
// { name: "s", pow: 2 }
|
|
5694
|
+
// ],
|
|
5695
|
+
// denom: [
|
|
5696
|
+
// { name: "kg", pow: 1 }
|
|
5697
|
+
// ]
|
|
5698
|
+
// }
|
|
5699
|
+
// }
|
|
5700
|
+
//
|
|
5701
|
+
// denom is optionally null
|
|
5702
|
+
|
|
4755
5703
|
const unitArray = [];
|
|
4756
|
-
|
|
5704
|
+
___default.default(parseResult.unit.num).each(unitSpec => {
|
|
4757
5705
|
unitArray.push(new Pow(unprefixify(unitSpec.name), new Int(unitSpec.pow)));
|
|
4758
5706
|
});
|
|
4759
|
-
|
|
5707
|
+
___default.default(parseResult.unit.denom).each(unitSpec => {
|
|
4760
5708
|
unitArray.push(new Pow(unprefixify(unitSpec.name), new Int(-1 * unitSpec.pow)));
|
|
4761
5709
|
});
|
|
4762
5710
|
var unit = new Mul(unitArray).flatten();
|
|
4763
5711
|
if (parseResult.type === "unitMagnitude") {
|
|
5712
|
+
// in the first case we have a magnitude coefficient as well as the
|
|
5713
|
+
// unit itself.
|
|
4764
5714
|
var coefArray = [new Float(+parseResult.magnitude), ...unitArray];
|
|
4765
5715
|
var expr = new Mul(coefArray);
|
|
4766
5716
|
return {
|
|
@@ -4771,6 +5721,7 @@ const unitParse = function unitParse(input) {
|
|
|
4771
5721
|
type: parseResult.type
|
|
4772
5722
|
};
|
|
4773
5723
|
} else {
|
|
5724
|
+
// in the second case it's just the unit with no magnitude.
|
|
4774
5725
|
return {
|
|
4775
5726
|
parsed: true,
|
|
4776
5727
|
unit: unit,
|
|
@@ -4786,6 +5737,7 @@ const unitParse = function unitParse(input) {
|
|
|
4786
5737
|
};
|
|
4787
5738
|
var baseUnits = {
|
|
4788
5739
|
m: new Unit("m"),
|
|
5740
|
+
// Note: kg is the SI base unit but we use g for consistency
|
|
4789
5741
|
g: new Unit("g"),
|
|
4790
5742
|
s: new Unit("s"),
|
|
4791
5743
|
A: new Unit("A"),
|
|
@@ -4810,11 +5762,14 @@ var siPrefixes = {
|
|
|
4810
5762
|
T: new Pow(new Int(10), new Int(12)),
|
|
4811
5763
|
P: new Pow(new Int(10), new Int(15)),
|
|
4812
5764
|
E: new Pow(new Int(10), new Int(18)),
|
|
5765
|
+
// http://en.wikipedia.org/wiki/Metric_prefix#.22Hella.22_prefix_proposal
|
|
4813
5766
|
hella: new Pow(new Int(10), new Int(27))
|
|
4814
5767
|
};
|
|
5768
|
+
|
|
5769
|
+
// Use these two values to mark a unit as either SI-prefixable or not.
|
|
4815
5770
|
const hasPrefixes = "hasPrefixes";
|
|
4816
5771
|
const hasntPrefixes = "hasntPrefixes";
|
|
4817
|
-
const makeAlias = function
|
|
5772
|
+
const makeAlias = function (str, prefixes) {
|
|
4818
5773
|
var splits = str.split("|");
|
|
4819
5774
|
var coefficientStr = splits[0].trim();
|
|
4820
5775
|
var unitsStr = splits[1].trim();
|
|
@@ -4835,9 +5790,35 @@ const makeAlias = function makeAlias(str, prefixes) {
|
|
|
4835
5790
|
prefixes: prefixes
|
|
4836
5791
|
};
|
|
4837
5792
|
};
|
|
5793
|
+
|
|
5794
|
+
// This is a mapping of derived units (or different names for a unit) to their
|
|
5795
|
+
// definitions. For example, an inch is defined as 0.0254 m.
|
|
5796
|
+
//
|
|
5797
|
+
// Definitions don't need to be in terms of base units. For example, tsp is
|
|
5798
|
+
// defined in terms of tbsp (which is defined in terms of cup -> gal -> L ->
|
|
5799
|
+
// m^3). However, units must get simpler. I.e. there's no loop checking.
|
|
5800
|
+
//
|
|
5801
|
+
// makeAlias takes two parameters:
|
|
5802
|
+
// * a string specifying the simplification to perform
|
|
5803
|
+
// - a required pipe separates the constant factor from the base units
|
|
5804
|
+
// - the constant factor is parsed by KAS
|
|
5805
|
+
// - the base units are in a simple format which disallows exponents and
|
|
5806
|
+
// requires multiplicands to be space-separated ("m m" rather than "m^2)
|
|
5807
|
+
// with an optional "/" separating numerator and denominator
|
|
5808
|
+
// - prefixes are not allowed to be used in the converted to units
|
|
5809
|
+
// (note that this restriction, the format of the string, and the choice to
|
|
5810
|
+
// use a string in the first place are made out of laziness to minimize
|
|
5811
|
+
// both typing and parsing)
|
|
5812
|
+
// * a boolean specifying whether or not it's acceptable to use SI units
|
|
5813
|
+
//
|
|
5814
|
+
// Where possible, these units are taken from "The International System of
|
|
5815
|
+
// Units (SI)" 8th edition (2006).
|
|
4838
5816
|
var derivedUnits = {
|
|
5817
|
+
// mass
|
|
5818
|
+
// The atomic mass unit / dalton.
|
|
4839
5819
|
Da: makeAlias("1.6605388628 x 10^-24 | g", hasPrefixes),
|
|
4840
5820
|
u: makeAlias("| Da", hasntPrefixes),
|
|
5821
|
+
// length
|
|
4841
5822
|
meter: makeAlias("| m", hasntPrefixes),
|
|
4842
5823
|
meters: makeAlias("| m", hasntPrefixes),
|
|
4843
5824
|
in: makeAlias("254 / 10000 | m", hasntPrefixes),
|
|
@@ -4848,32 +5829,43 @@ var derivedUnits = {
|
|
|
4848
5829
|
nmi: makeAlias("1852 | m", hasntPrefixes),
|
|
4849
5830
|
Å: makeAlias("10^-10 | m", hasntPrefixes),
|
|
4850
5831
|
pc: makeAlias("3.0857 x 10^16 | m", hasntPrefixes),
|
|
5832
|
+
// time
|
|
4851
5833
|
min: makeAlias("60 | s", hasntPrefixes),
|
|
4852
5834
|
hr: makeAlias("3600 | s", hasntPrefixes),
|
|
4853
5835
|
sec: makeAlias("| s", hasntPrefixes),
|
|
5836
|
+
// TODO(joel) make day work
|
|
4854
5837
|
day: makeAlias("86400 | s", hasntPrefixes),
|
|
4855
5838
|
wk: makeAlias("604800 | s", hasntPrefixes),
|
|
4856
5839
|
fortnight: makeAlias("14 | day", hasntPrefixes),
|
|
4857
5840
|
shake: makeAlias("10^-8 | s", hasntPrefixes),
|
|
4858
5841
|
olympiad: makeAlias("126200000 | s", hasntPrefixes),
|
|
5842
|
+
// temperature
|
|
4859
5843
|
"°C": makeAlias("1 | K", hasntPrefixes),
|
|
4860
5844
|
"°F": makeAlias("5/9 | K", hasntPrefixes),
|
|
4861
5845
|
"°R": makeAlias("5/9 | K", hasntPrefixes),
|
|
5846
|
+
// electric charge
|
|
4862
5847
|
e: makeAlias("1.6021765314 x 10^-19 | C", hasntPrefixes),
|
|
5848
|
+
// speed
|
|
4863
5849
|
c: makeAlias("299792458 | m / s", hasntPrefixes),
|
|
4864
5850
|
kn: makeAlias("514/1000 | m / s", hasntPrefixes),
|
|
4865
5851
|
kt: makeAlias("| kn", hasntPrefixes),
|
|
4866
5852
|
knot: makeAlias("| kn", hasntPrefixes),
|
|
5853
|
+
// energy
|
|
4867
5854
|
J: makeAlias("| N m", hasPrefixes),
|
|
4868
5855
|
BTU: makeAlias("1060 | J", hasntPrefixes),
|
|
4869
5856
|
cal: makeAlias("4184 / 1000 | J", hasPrefixes),
|
|
4870
5857
|
eV: makeAlias("1.602176514 x 10^-19 | J", hasPrefixes),
|
|
4871
5858
|
erg: makeAlias("10^−7 | J", hasPrefixes),
|
|
5859
|
+
// power
|
|
4872
5860
|
W: makeAlias("| J / s", hasPrefixes),
|
|
4873
5861
|
"H-e": makeAlias("80 | W", hasntPrefixes),
|
|
5862
|
+
// force
|
|
4874
5863
|
N: makeAlias("1000 | g m / s s", hasPrefixes),
|
|
5864
|
+
// "lb": makeAlias("4448 / 1000 | N", hasntPrefixes),
|
|
5865
|
+
// 4.4482216152605
|
|
4875
5866
|
lb: makeAlias("4448221615 / 1000000000 | N", hasntPrefixes),
|
|
4876
5867
|
dyn: makeAlias("10^-5 | N", hasntPrefixes),
|
|
5868
|
+
// pressure
|
|
4877
5869
|
Pa: makeAlias("1 | N / m m m", hasPrefixes),
|
|
4878
5870
|
bar: makeAlias("10^5 | Pa", hasPrefixes),
|
|
4879
5871
|
"㏔": makeAlias("1/1000 | bar", hasntPrefixes),
|
|
@@ -4881,6 +5873,7 @@ var derivedUnits = {
|
|
|
4881
5873
|
atm: makeAlias("101325 | Pa", hasntPrefixes),
|
|
4882
5874
|
Torr: makeAlias("1/760 | atm", hasntPrefixes),
|
|
4883
5875
|
mmHg: makeAlias("| Torr", hasntPrefixes),
|
|
5876
|
+
// area
|
|
4884
5877
|
ha: makeAlias("10^4 | m m", hasntPrefixes),
|
|
4885
5878
|
b: makeAlias("10^−28 | m m", hasPrefixes),
|
|
4886
5879
|
barn: makeAlias("| b", hasPrefixes),
|
|
@@ -4888,6 +5881,7 @@ var derivedUnits = {
|
|
|
4888
5881
|
skilodge: makeAlias("10^-31 | m m", hasntPrefixes),
|
|
4889
5882
|
outhouse: makeAlias("10^-34 | m m", hasntPrefixes),
|
|
4890
5883
|
shed: makeAlias("10^-52 | m m", hasntPrefixes),
|
|
5884
|
+
// volume
|
|
4891
5885
|
L: makeAlias("1/1000 | m m m", hasPrefixes),
|
|
4892
5886
|
gal: makeAlias("3785/1000 | L", hasPrefixes),
|
|
4893
5887
|
cup: makeAlias("1/16 | gal", hasntPrefixes),
|
|
@@ -4900,17 +5894,25 @@ var derivedUnits = {
|
|
|
4900
5894
|
"fl. oz.": makeAlias("1/8 | cup", hasntPrefixes),
|
|
4901
5895
|
tbsp: makeAlias("1/16 | cup", hasntPrefixes),
|
|
4902
5896
|
tsp: makeAlias("1/3 | tbsp", hasntPrefixes),
|
|
5897
|
+
// rotational
|
|
5898
|
+
// "rad":
|
|
4903
5899
|
rev: makeAlias("2 pi | rad", hasntPrefixes),
|
|
4904
5900
|
deg: makeAlias("180 pi | rad", hasntPrefixes),
|
|
4905
5901
|
"°": makeAlias("| deg", hasntPrefixes),
|
|
4906
5902
|
arcminute: makeAlias("1/60 | deg", hasntPrefixes),
|
|
4907
5903
|
arcsec: makeAlias("1/3600 | deg", hasntPrefixes),
|
|
5904
|
+
// dimensionless
|
|
5905
|
+
// "B": makeAlias("10 | dB", hasntPrefixes), // XXX danger - logarithmic
|
|
5906
|
+
// "dB"
|
|
5907
|
+
// "nP"
|
|
4908
5908
|
Hu: makeAlias("1000 | dB", hasPrefixes),
|
|
4909
5909
|
dozen: makeAlias("12 |", hasntPrefixes),
|
|
5910
|
+
// XXX
|
|
4910
5911
|
mol: makeAlias("6.0221412927 x 10^23 |", hasPrefixes),
|
|
4911
5912
|
"%": makeAlias("1/100 |", hasntPrefixes),
|
|
4912
5913
|
percent: makeAlias("| %", hasntPrefixes),
|
|
4913
5914
|
ppm: makeAlias("1/1000000 |", hasntPrefixes),
|
|
5915
|
+
// electric / magnetic
|
|
4914
5916
|
V: makeAlias("1000 | g m m / s s C", hasPrefixes),
|
|
4915
5917
|
C: makeAlias("| A s", hasPrefixes),
|
|
4916
5918
|
ampere: makeAlias("| A", hasntPrefixes),
|
|
@@ -4920,6 +5922,8 @@ var derivedUnits = {
|
|
|
4920
5922
|
H: makeAlias("| ohm s", hasPrefixes),
|
|
4921
5923
|
T: makeAlias("1000 | g / C s", hasPrefixes),
|
|
4922
5924
|
Wb: makeAlias("1000 | g m m / C s", hasPrefixes),
|
|
5925
|
+
// photometry
|
|
5926
|
+
// TODO not sure this is right
|
|
4923
5927
|
lm: makeAlias("pi x 10^4 | cd / m m", hasntPrefixes),
|
|
4924
5928
|
lx: makeAlias("| lm / m m", hasntPrefixes),
|
|
4925
5929
|
nit: makeAlias("| cd / m m", hasntPrefixes),
|
|
@@ -4933,17 +5937,38 @@ var derivedUnits = {
|
|
|
4933
5937
|
sk: makeAlias("10^-7 | lm", hasntPrefixes),
|
|
4934
5938
|
skot: makeAlias("| sk", hasntPrefixes),
|
|
4935
5939
|
bril: makeAlias("10^-11 | lm", hasntPrefixes),
|
|
5940
|
+
// other
|
|
4936
5941
|
Hz: makeAlias("| / s", hasPrefixes)
|
|
4937
5942
|
};
|
|
4938
5943
|
const Zero = NumZero;
|
|
4939
5944
|
const One = NumOne;
|
|
4940
5945
|
|
|
4941
|
-
|
|
5946
|
+
/**
|
|
5947
|
+
* Compares two expressions for equality.
|
|
5948
|
+
*
|
|
5949
|
+
* Assumes that both expressions have already been parsed.
|
|
5950
|
+
*/
|
|
5951
|
+
// TODO(alex): be able to pass a random() function to compare()
|
|
5952
|
+
const compare = function (expr1, expr2) {
|
|
5953
|
+
let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
|
|
4942
5954
|
const defaults = {
|
|
4943
5955
|
form: false,
|
|
4944
5956
|
simplify: false
|
|
4945
5957
|
};
|
|
4946
|
-
|
|
5958
|
+
|
|
5959
|
+
/* Options that could be added in the future:
|
|
5960
|
+
* - Allow ratios: e.g. 3/1 and 3 should both be accepted for something
|
|
5961
|
+
* like slope
|
|
5962
|
+
* - Allow student to choose their own variable names
|
|
5963
|
+
*/
|
|
5964
|
+
const optionsWithDefaults = {
|
|
5965
|
+
...defaults,
|
|
5966
|
+
...options
|
|
5967
|
+
};
|
|
5968
|
+
|
|
5969
|
+
// TODO(CP-1614): Figure out how to make these messages translatable
|
|
5970
|
+
|
|
5971
|
+
// Variable check
|
|
4947
5972
|
const vars = expr1.sameVars(expr2);
|
|
4948
5973
|
if (!vars.equal) {
|
|
4949
5974
|
let message;
|
|
@@ -4959,18 +5984,24 @@ const compare = function compare(expr1, expr2, options = {}) {
|
|
|
4959
5984
|
message: message
|
|
4960
5985
|
};
|
|
4961
5986
|
}
|
|
5987
|
+
|
|
5988
|
+
// Semantic check
|
|
4962
5989
|
if (!expr1.compare(expr2)) {
|
|
4963
5990
|
return {
|
|
4964
5991
|
equal: false,
|
|
4965
5992
|
message: null
|
|
4966
5993
|
};
|
|
4967
5994
|
}
|
|
5995
|
+
|
|
5996
|
+
// Syntactic check
|
|
4968
5997
|
if (optionsWithDefaults.form && !expr1.sameForm(expr2)) {
|
|
4969
5998
|
return {
|
|
4970
5999
|
equal: false,
|
|
4971
6000
|
message: "Your answer is not in the correct form."
|
|
4972
6001
|
};
|
|
4973
6002
|
}
|
|
6003
|
+
|
|
6004
|
+
// Syntactic check
|
|
4974
6005
|
if (optionsWithDefaults.simplify && !expr1.isSimplified()) {
|
|
4975
6006
|
return {
|
|
4976
6007
|
equal: false,
|
|
@@ -4983,5 +6014,24 @@ const compare = function compare(expr1, expr2, options = {}) {
|
|
|
4983
6014
|
};
|
|
4984
6015
|
};
|
|
4985
6016
|
|
|
4986
|
-
|
|
6017
|
+
exports.Abs = Abs;
|
|
6018
|
+
exports.Add = Add;
|
|
6019
|
+
exports.Const = Const;
|
|
6020
|
+
exports.Eq = Eq;
|
|
6021
|
+
exports.Float = Float;
|
|
6022
|
+
exports.Func = Func;
|
|
6023
|
+
exports.Int = Int;
|
|
6024
|
+
exports.Log = Log;
|
|
6025
|
+
exports.Mul = Mul;
|
|
6026
|
+
exports.One = One;
|
|
6027
|
+
exports.Pow = Pow;
|
|
6028
|
+
exports.Rational = Rational;
|
|
6029
|
+
exports.Trig = Trig;
|
|
6030
|
+
exports.Unit = Unit;
|
|
6031
|
+
exports.Var = Var;
|
|
6032
|
+
exports.Zero = Zero;
|
|
6033
|
+
exports.compare = compare;
|
|
6034
|
+
exports.libVersion = libVersion;
|
|
6035
|
+
exports.parse = parse;
|
|
6036
|
+
exports.unitParse = unitParse;
|
|
4987
6037
|
//# sourceMappingURL=index.js.map
|