@khanacademy/kas 0.3.13 → 0.3.15
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 +1269 -1221
- package/dist/es/index.js.map +1 -1
- package/dist/index.js +1182 -1109
- package/dist/index.js.map +1 -1
- package/dist/nodes.d.ts +446 -0
- package/package.json +2 -2
package/dist/es/index.js
CHANGED
|
@@ -3,9 +3,19 @@ import _ from 'underscore';
|
|
|
3
3
|
|
|
4
4
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
5
5
|
const libName = "@khanacademy/kas";
|
|
6
|
-
const libVersion = "0.3.
|
|
6
|
+
const libVersion = "0.3.15";
|
|
7
7
|
addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
8
8
|
|
|
9
|
+
function _extends() {
|
|
10
|
+
return _extends = Object.assign ? Object.assign.bind() : function (n) {
|
|
11
|
+
for (var e = 1; e < arguments.length; e++) {
|
|
12
|
+
var t = arguments[e];
|
|
13
|
+
for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]);
|
|
14
|
+
}
|
|
15
|
+
return n;
|
|
16
|
+
}, _extends.apply(null, arguments);
|
|
17
|
+
}
|
|
18
|
+
|
|
9
19
|
// this is a @generated file
|
|
10
20
|
|
|
11
21
|
/* parser generated by jison 0.4.15 */
|
|
@@ -2615,7 +2625,7 @@ var parser = function () {
|
|
|
2615
2625
|
return new Parser();
|
|
2616
2626
|
}();
|
|
2617
2627
|
|
|
2618
|
-
|
|
2628
|
+
var _class5, _class12;
|
|
2619
2629
|
|
|
2620
2630
|
/* The node hierarcy is as follows:
|
|
2621
2631
|
|
|
@@ -2628,7 +2638,7 @@ var parser = function () {
|
|
|
2628
2638
|
Eq 2 children
|
|
2629
2639
|
Trig 1 child
|
|
2630
2640
|
Abs 1 child
|
|
2631
|
-
(
|
|
2641
|
+
(Sym)
|
|
2632
2642
|
Func 1 child e.g. f(x)
|
|
2633
2643
|
Var leaf node e.g. x, x_n
|
|
2634
2644
|
Const leaf node e.g. pi, e, <i>
|
|
@@ -2648,27 +2658,14 @@ var parser = function () {
|
|
|
2648
2658
|
|
|
2649
2659
|
/* non user-facing functions */
|
|
2650
2660
|
|
|
2651
|
-
// assert that all abstract methods have been overridden
|
|
2652
|
-
var abstract = function abstract() {
|
|
2653
|
-
// Try to give people a bit of information when this happens
|
|
2654
|
-
throw new Error("Abstract method - must override for expr: " +
|
|
2655
|
-
// eslint-disable-next-line @babel/no-invalid-this
|
|
2656
|
-
this.print());
|
|
2657
|
-
};
|
|
2658
|
-
|
|
2659
|
-
// throw an error that is meant to be caught by the test suite (not user facing)
|
|
2660
|
-
var error = function error(message) {
|
|
2661
|
-
throw new Error(message);
|
|
2662
|
-
};
|
|
2663
|
-
|
|
2664
2661
|
// reliably detect NaN
|
|
2665
|
-
|
|
2662
|
+
const isNaN = function isNaN(object) {
|
|
2666
2663
|
return object !== object;
|
|
2667
2664
|
};
|
|
2668
2665
|
|
|
2669
2666
|
// return a random float between min (inclusive) and max (exclusive),
|
|
2670
2667
|
// not that inclusivity means much, probabilistically, on floats
|
|
2671
|
-
|
|
2668
|
+
const randomFloat = function randomFloat(min, max) {
|
|
2672
2669
|
var extent = max - min;
|
|
2673
2670
|
return Math.random() * extent + min;
|
|
2674
2671
|
};
|
|
@@ -2677,53 +2674,113 @@ var randomFloat = function randomFloat(min, max) {
|
|
|
2677
2674
|
var ITERATIONS = 12;
|
|
2678
2675
|
var TOLERANCE = 9; // decimal places
|
|
2679
2676
|
|
|
2677
|
+
// NOTE(kevinb): _.partition exists in a more recent version of underscore.
|
|
2678
|
+
// To avoid having to update underscore I've added a hacky version of this
|
|
2679
|
+
// method here.
|
|
2680
|
+
function partition(list, iteratee) {
|
|
2681
|
+
const a = [];
|
|
2682
|
+
const b = [];
|
|
2683
|
+
_.forEach(list, (elem, key, ctx) => {
|
|
2684
|
+
if (iteratee(elem, key, ctx)) {
|
|
2685
|
+
a.push(elem);
|
|
2686
|
+
} else {
|
|
2687
|
+
b.push(elem);
|
|
2688
|
+
}
|
|
2689
|
+
});
|
|
2690
|
+
return [a, b];
|
|
2691
|
+
}
|
|
2692
|
+
function isExpr(arg) {
|
|
2693
|
+
return arg instanceof Expr;
|
|
2694
|
+
}
|
|
2695
|
+
const isAdd = function isAdd(term) {
|
|
2696
|
+
return term instanceof Add;
|
|
2697
|
+
};
|
|
2698
|
+
function isRational(arg) {
|
|
2699
|
+
return arg instanceof Rational;
|
|
2700
|
+
}
|
|
2701
|
+
function getFactors(expr) {
|
|
2702
|
+
if (expr instanceof Mul) {
|
|
2703
|
+
return expr.terms;
|
|
2704
|
+
} else {
|
|
2705
|
+
return [expr];
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2680
2708
|
/* abstract base expression node */
|
|
2681
|
-
|
|
2682
|
-
|
|
2709
|
+
class Expr {
|
|
2710
|
+
constructor() {
|
|
2711
|
+
this.hints = void 0;
|
|
2712
|
+
this.hints = {
|
|
2713
|
+
parens: false
|
|
2714
|
+
};
|
|
2715
|
+
}
|
|
2716
|
+
|
|
2683
2717
|
// this node's immediate constructor
|
|
2684
|
-
|
|
2718
|
+
// The `new (...args: any[]): any;` part of the type is a
|
|
2719
|
+
// "construct" signature. It indicates that `func` is a class.
|
|
2720
|
+
// See https://www.typescriptlang.org/docs/handbook/2/functions.html#construct-signatures.
|
|
2685
2721
|
// an array of the arguments to this node's immediate constructor
|
|
2686
|
-
args: abstract,
|
|
2687
2722
|
// make a new node with the given arguments
|
|
2688
|
-
construct
|
|
2689
|
-
|
|
2690
|
-
|
|
2723
|
+
construct(args) {
|
|
2724
|
+
const func = this.func;
|
|
2725
|
+
const instance = new func(...args);
|
|
2726
|
+
if (typeof instance === "undefined") {
|
|
2727
|
+
throw new Error("constructor function returning undefined");
|
|
2728
|
+
}
|
|
2691
2729
|
return instance;
|
|
2692
|
-
}
|
|
2730
|
+
}
|
|
2731
|
+
|
|
2693
2732
|
// an abstraction for chainable, bottom-up recursion
|
|
2694
|
-
|
|
2695
|
-
|
|
2733
|
+
// NOTE(kevinb): This method is highly dynamic. It's possible that it
|
|
2734
|
+
// could be made more type-safe using overload signatures.
|
|
2735
|
+
recurse(method, ...passed) {
|
|
2696
2736
|
var args = _.map(this.args(), function (arg) {
|
|
2697
|
-
return _.isString(arg) ? arg : arg[method].apply(arg, passed);
|
|
2737
|
+
return _.isString(arg) || _.isNumber(arg) ? arg : arg[method].apply(arg, passed);
|
|
2698
2738
|
});
|
|
2699
2739
|
return this.construct(args);
|
|
2700
|
-
}
|
|
2740
|
+
}
|
|
2741
|
+
|
|
2701
2742
|
// evaluate numerically with given variable mapping
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2743
|
+
// NOTE(kevin): This could made into an abstract method but
|
|
2744
|
+
// Eq doesn't implement it. This indicates that we probably
|
|
2745
|
+
// need to introduce another class in our hierarchy.
|
|
2746
|
+
eval(vars = {}, options) {
|
|
2747
|
+
throw new Error("Abstract method - must override for expr: " +
|
|
2748
|
+
// eslint-disable-next-line @babel/no-invalid-this
|
|
2749
|
+
this.print());
|
|
2750
|
+
}
|
|
2751
|
+
|
|
2752
|
+
// NOTE(kevin): This could made into an abstract method but
|
|
2753
|
+
// Eq doesn't implement it. This indicates that we probably
|
|
2754
|
+
// need to introduce another class in our hierarchy.
|
|
2755
|
+
codegen() {
|
|
2756
|
+
throw new Error("Abstract method - must override for expr: " +
|
|
2757
|
+
// eslint-disable-next-line @babel/no-invalid-this
|
|
2758
|
+
this.print());
|
|
2759
|
+
}
|
|
2760
|
+
compile() {
|
|
2705
2761
|
var code = this.codegen();
|
|
2706
2762
|
try {
|
|
2763
|
+
// @ts-expect-error: TypeScript doesn't want to unify
|
|
2764
|
+
// `Function` with the `compile`'s return type.
|
|
2707
2765
|
return new Function("vars", "return " + code + ";");
|
|
2708
2766
|
} catch (e) {
|
|
2709
2767
|
throw new Error("Function did not compile: " + code);
|
|
2710
2768
|
}
|
|
2711
|
-
}
|
|
2769
|
+
}
|
|
2770
|
+
|
|
2712
2771
|
// returns a string unambiguously representing the expression
|
|
2713
2772
|
// should be valid as input
|
|
2714
2773
|
// e.g. this.equals(parse(this.print())) === true
|
|
2715
|
-
print: abstract,
|
|
2716
2774
|
// returns a TeX string representing the expression
|
|
2717
|
-
tex: abstract,
|
|
2718
2775
|
// returns a TeX string, modified by the given options
|
|
2719
|
-
asTex
|
|
2776
|
+
asTex(options) {
|
|
2720
2777
|
options = options || {};
|
|
2721
2778
|
_.defaults(options, {
|
|
2722
2779
|
display: true,
|
|
2723
2780
|
dynamic: true,
|
|
2724
2781
|
times: false
|
|
2725
2782
|
});
|
|
2726
|
-
|
|
2783
|
+
let tex = this.tex();
|
|
2727
2784
|
if (options.display) {
|
|
2728
2785
|
tex = "\\displaystyle " + tex;
|
|
2729
2786
|
}
|
|
@@ -2735,49 +2792,54 @@ _.extend(Expr.prototype, {
|
|
|
2735
2792
|
tex = tex.replace(/\\cdot/g, "\\times");
|
|
2736
2793
|
}
|
|
2737
2794
|
return tex;
|
|
2738
|
-
}
|
|
2795
|
+
}
|
|
2796
|
+
|
|
2739
2797
|
// returns the name of this expression's constructor as a string
|
|
2740
|
-
// only used for testing and debugging
|
|
2741
|
-
name
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
return this.func.toString().match(/^function\s*([^\s(]+)/)[1];
|
|
2746
|
-
}
|
|
2747
|
-
},
|
|
2798
|
+
// only used for testing and debugging
|
|
2799
|
+
name() {
|
|
2800
|
+
return this.func.name;
|
|
2801
|
+
}
|
|
2802
|
+
|
|
2748
2803
|
// returns a string representing current node structure
|
|
2749
|
-
repr
|
|
2804
|
+
repr() {
|
|
2750
2805
|
return this.name() + "(" + _.map(this.args(), function (arg) {
|
|
2751
|
-
return _.isString(arg) ? arg : arg.repr();
|
|
2806
|
+
return _.isString(arg) || _.isNumber(arg) ? arg : arg.repr();
|
|
2752
2807
|
}).join(",") + ")";
|
|
2753
|
-
}
|
|
2808
|
+
}
|
|
2809
|
+
|
|
2754
2810
|
// removes all negative signs
|
|
2755
|
-
strip
|
|
2811
|
+
strip() {
|
|
2756
2812
|
return this.recurse("strip");
|
|
2757
|
-
}
|
|
2813
|
+
}
|
|
2814
|
+
|
|
2758
2815
|
// canonically reorders all commutative elements
|
|
2759
|
-
normalize
|
|
2816
|
+
normalize() {
|
|
2760
2817
|
return this.recurse("normalize");
|
|
2761
|
-
}
|
|
2818
|
+
}
|
|
2819
|
+
|
|
2762
2820
|
// expands the expression
|
|
2763
|
-
expand
|
|
2821
|
+
expand() {
|
|
2764
2822
|
return this.recurse("expand");
|
|
2765
|
-
}
|
|
2823
|
+
}
|
|
2824
|
+
|
|
2766
2825
|
// naively factors out like terms
|
|
2767
|
-
factor
|
|
2826
|
+
factor(options) {
|
|
2768
2827
|
return this.recurse("factor", options);
|
|
2769
|
-
}
|
|
2828
|
+
}
|
|
2829
|
+
|
|
2770
2830
|
// collect all like terms
|
|
2771
|
-
collect
|
|
2831
|
+
collect(options) {
|
|
2772
2832
|
return this.recurse("collect", options);
|
|
2773
|
-
}
|
|
2833
|
+
}
|
|
2834
|
+
|
|
2774
2835
|
// strict syntactic equality check
|
|
2775
|
-
equals
|
|
2836
|
+
equals(other) {
|
|
2776
2837
|
return this.normalize().print() === other.normalize().print();
|
|
2777
|
-
}
|
|
2838
|
+
}
|
|
2839
|
+
|
|
2778
2840
|
// expand and collect until the expression no longer changes
|
|
2779
|
-
simplify
|
|
2780
|
-
options =
|
|
2841
|
+
simplify(options) {
|
|
2842
|
+
options = _extends({
|
|
2781
2843
|
once: false
|
|
2782
2844
|
}, options);
|
|
2783
2845
|
|
|
@@ -2791,7 +2853,7 @@ _.extend(Expr.prototype, {
|
|
|
2791
2853
|
}
|
|
2792
2854
|
|
|
2793
2855
|
// Attempt to expand and collect
|
|
2794
|
-
var step3 = step2.expand(
|
|
2856
|
+
var step3 = step2.expand();
|
|
2795
2857
|
var step4 = step3.collect(options);
|
|
2796
2858
|
|
|
2797
2859
|
// Rollback if collect didn't do anything
|
|
@@ -2806,62 +2868,69 @@ _.extend(Expr.prototype, {
|
|
|
2806
2868
|
} else {
|
|
2807
2869
|
return simplified.simplify(options);
|
|
2808
2870
|
}
|
|
2809
|
-
}
|
|
2871
|
+
}
|
|
2872
|
+
|
|
2810
2873
|
// check whether this expression is simplified
|
|
2811
|
-
isSimplified
|
|
2874
|
+
isSimplified() {
|
|
2812
2875
|
return this.equals(this.simplify());
|
|
2813
|
-
}
|
|
2876
|
+
}
|
|
2877
|
+
|
|
2814
2878
|
// return the child nodes of this node
|
|
2815
|
-
exprArgs
|
|
2816
|
-
return
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
},
|
|
2879
|
+
exprArgs() {
|
|
2880
|
+
return this.args().filter(isExpr);
|
|
2881
|
+
}
|
|
2882
|
+
|
|
2820
2883
|
// return the variables (function and non) within the expression
|
|
2821
|
-
getVars
|
|
2884
|
+
getVars(excludeFunc) {
|
|
2822
2885
|
return _.uniq(_.flatten(_.invoke(this.exprArgs(), "getVars", excludeFunc))).sort();
|
|
2823
|
-
}
|
|
2824
|
-
getConsts
|
|
2886
|
+
}
|
|
2887
|
+
getConsts() {
|
|
2825
2888
|
return _.uniq(_.flatten(_.invoke(this.exprArgs(), "getConsts"))).sort();
|
|
2826
|
-
}
|
|
2827
|
-
getUnits
|
|
2889
|
+
}
|
|
2890
|
+
getUnits() {
|
|
2828
2891
|
return _.flatten(_.invoke(this.exprArgs(), "getUnits"));
|
|
2829
|
-
}
|
|
2892
|
+
}
|
|
2893
|
+
|
|
2830
2894
|
// check whether this expression node is of a particular type
|
|
2831
|
-
is
|
|
2895
|
+
is(func) {
|
|
2832
2896
|
return this instanceof func;
|
|
2833
|
-
}
|
|
2897
|
+
}
|
|
2898
|
+
|
|
2834
2899
|
// check whether this expression has a particular node type
|
|
2835
|
-
has
|
|
2900
|
+
has(func) {
|
|
2836
2901
|
if (this instanceof func) {
|
|
2837
2902
|
return true;
|
|
2838
2903
|
}
|
|
2839
2904
|
return _.any(this.exprArgs(), function (arg) {
|
|
2840
2905
|
return arg.has(func);
|
|
2841
2906
|
});
|
|
2842
|
-
}
|
|
2907
|
+
}
|
|
2908
|
+
|
|
2843
2909
|
// raise this expression to a given exponent
|
|
2844
2910
|
// most useful for eventually implementing i^3 = -i, etc.
|
|
2845
|
-
raiseToThe
|
|
2911
|
+
raiseToThe(exp, options) {
|
|
2846
2912
|
return new Pow(this, exp);
|
|
2847
|
-
}
|
|
2913
|
+
}
|
|
2914
|
+
|
|
2848
2915
|
// does this expression have a specific rendering hint?
|
|
2849
2916
|
// rendering hints are picked up while parsing, but are lost during transformations
|
|
2850
|
-
isSubtract
|
|
2917
|
+
isSubtract() {
|
|
2851
2918
|
return false;
|
|
2852
|
-
}
|
|
2853
|
-
isDivide
|
|
2919
|
+
}
|
|
2920
|
+
isDivide() {
|
|
2854
2921
|
return false;
|
|
2855
|
-
}
|
|
2856
|
-
isRoot
|
|
2922
|
+
}
|
|
2923
|
+
isRoot() {
|
|
2857
2924
|
return false;
|
|
2858
|
-
}
|
|
2925
|
+
}
|
|
2926
|
+
|
|
2859
2927
|
// whether this node needs an explicit multiplication sign if following a Num
|
|
2860
|
-
needsExplicitMul
|
|
2861
|
-
return this.
|
|
2862
|
-
}
|
|
2928
|
+
needsExplicitMul() {
|
|
2929
|
+
return this.exprArgs()[0].needsExplicitMul();
|
|
2930
|
+
}
|
|
2931
|
+
|
|
2863
2932
|
// check that the variables in both expressions are the same
|
|
2864
|
-
sameVars
|
|
2933
|
+
sameVars(other) {
|
|
2865
2934
|
var vars1 = this.getVars();
|
|
2866
2935
|
var vars2 = other.getVars();
|
|
2867
2936
|
|
|
@@ -2879,11 +2948,12 @@ _.extend(Expr.prototype, {
|
|
|
2879
2948
|
equal: equal,
|
|
2880
2949
|
equalIgnoringCase: equalIgnoringCase
|
|
2881
2950
|
};
|
|
2882
|
-
}
|
|
2951
|
+
}
|
|
2952
|
+
|
|
2883
2953
|
// semantic equality check, call after sameVars() to avoid potential false positives
|
|
2884
2954
|
// plug in random numbers for the variables in both expressions
|
|
2885
2955
|
// if they both consistently evaluate the same, then they're the same
|
|
2886
|
-
compare
|
|
2956
|
+
compare(other) {
|
|
2887
2957
|
// equation comparisons are handled by Eq.compare()
|
|
2888
2958
|
if (other instanceof Eq) {
|
|
2889
2959
|
return false;
|
|
@@ -2954,14 +3024,14 @@ _.extend(Expr.prototype, {
|
|
|
2954
3024
|
_.each(varList, function (v) {
|
|
2955
3025
|
vars[v] = useFloats ? randomFloat(-range, range) : _.random(-range, range);
|
|
2956
3026
|
});
|
|
2957
|
-
|
|
3027
|
+
let equal;
|
|
2958
3028
|
if (expr1.has(Func) || expr2.has(Func) || expr1.has(Unit) || expr2.has(Unit)) {
|
|
2959
|
-
|
|
2960
|
-
|
|
3029
|
+
const result1 = expr1.partialEval(vars);
|
|
3030
|
+
const result2 = expr2.partialEval(vars);
|
|
2961
3031
|
equal = result1.simplify().equals(result2.simplify());
|
|
2962
3032
|
} else {
|
|
2963
|
-
|
|
2964
|
-
|
|
3033
|
+
const result1 = expr1.eval(vars);
|
|
3034
|
+
const result2 = expr2.eval(vars);
|
|
2965
3035
|
equal = equalNumbers(result1, result2);
|
|
2966
3036
|
}
|
|
2967
3037
|
if (!equal) {
|
|
@@ -2969,52 +3039,65 @@ _.extend(Expr.prototype, {
|
|
|
2969
3039
|
}
|
|
2970
3040
|
}
|
|
2971
3041
|
return true;
|
|
2972
|
-
}
|
|
3042
|
+
}
|
|
3043
|
+
|
|
2973
3044
|
// evaluate as much of the expression as possible
|
|
2974
|
-
partialEval
|
|
3045
|
+
partialEval(vars) {
|
|
2975
3046
|
if (this instanceof Unit) {
|
|
2976
3047
|
return this;
|
|
2977
3048
|
} else if (!this.has(Func)) {
|
|
2978
|
-
return new Float(this.eval(vars).toFixed(TOLERANCE)).collect();
|
|
3049
|
+
return new Float(+this.eval(vars).toFixed(TOLERANCE)).collect();
|
|
2979
3050
|
} else if (this instanceof Func) {
|
|
2980
3051
|
return new Func(this.symbol, this.arg.partialEval(vars));
|
|
2981
3052
|
} else {
|
|
2982
3053
|
return this.recurse("partialEval", vars);
|
|
2983
3054
|
}
|
|
2984
|
-
}
|
|
3055
|
+
}
|
|
3056
|
+
|
|
2985
3057
|
// check that the structure of both expressions is the same
|
|
2986
3058
|
// all negative signs are stripped and the expressions are converted to
|
|
2987
3059
|
// a canonical commutative form
|
|
2988
3060
|
// should only be done after compare() returns true to avoid false positives
|
|
2989
|
-
sameForm
|
|
3061
|
+
sameForm(other) {
|
|
2990
3062
|
return this.strip().equals(other.strip());
|
|
2991
|
-
}
|
|
3063
|
+
}
|
|
3064
|
+
|
|
2992
3065
|
// returns the GCD of this expression and the given factor
|
|
2993
|
-
findGCD
|
|
2994
|
-
return this.equals(factor) ? factor :
|
|
2995
|
-
}
|
|
3066
|
+
findGCD(factor) {
|
|
3067
|
+
return this.equals(factor) ? factor : NumOne;
|
|
3068
|
+
}
|
|
3069
|
+
|
|
2996
3070
|
// return this expression's denominator
|
|
2997
|
-
getDenominator
|
|
2998
|
-
return
|
|
2999
|
-
}
|
|
3071
|
+
getDenominator() {
|
|
3072
|
+
return NumOne;
|
|
3073
|
+
}
|
|
3074
|
+
|
|
3000
3075
|
// return this expression as a Mul
|
|
3001
|
-
asMul
|
|
3002
|
-
return new Mul(
|
|
3003
|
-
}
|
|
3076
|
+
asMul() {
|
|
3077
|
+
return new Mul(NumOne, this);
|
|
3078
|
+
}
|
|
3079
|
+
|
|
3004
3080
|
// TODO(alex): rename to isDefinitePositive or similar?
|
|
3005
3081
|
// return whether this expression is 100% positive
|
|
3006
|
-
isPositive
|
|
3082
|
+
isPositive() {
|
|
3083
|
+
throw new Error("Abstract method - must override for expr: " +
|
|
3084
|
+
// eslint-disable-next-line @babel/no-invalid-this
|
|
3085
|
+
this.print());
|
|
3086
|
+
}
|
|
3087
|
+
|
|
3007
3088
|
// TODO(alex): rename to hasNegativeSign or similar?
|
|
3008
3089
|
// return whether this expression has a negative sign
|
|
3009
|
-
isNegative
|
|
3090
|
+
isNegative() {
|
|
3010
3091
|
return false;
|
|
3011
|
-
}
|
|
3092
|
+
}
|
|
3093
|
+
|
|
3012
3094
|
// return a factor of this expression that is 100% positive
|
|
3013
|
-
asPositiveFactor
|
|
3014
|
-
return this.isPositive() ? this :
|
|
3015
|
-
}
|
|
3095
|
+
asPositiveFactor() {
|
|
3096
|
+
return this.isPositive() ? this : NumOne;
|
|
3097
|
+
}
|
|
3098
|
+
|
|
3016
3099
|
// return a copy of the expression with a new hint set (preserves hints)
|
|
3017
|
-
addHint
|
|
3100
|
+
addHint(hint) {
|
|
3018
3101
|
if (!hint) {
|
|
3019
3102
|
return this;
|
|
3020
3103
|
}
|
|
@@ -3022,141 +3105,139 @@ _.extend(Expr.prototype, {
|
|
|
3022
3105
|
expr.hints = _.clone(this.hints);
|
|
3023
3106
|
expr.hints[hint] = true;
|
|
3024
3107
|
return expr;
|
|
3025
|
-
}
|
|
3026
|
-
|
|
3027
|
-
parens: false
|
|
3028
|
-
},
|
|
3029
|
-
// currently unused!
|
|
3030
|
-
asExpr: function () {
|
|
3031
|
-
return this;
|
|
3032
|
-
},
|
|
3108
|
+
}
|
|
3109
|
+
|
|
3033
3110
|
// complete parse by performing a few necessary transformations
|
|
3034
|
-
completeParse
|
|
3111
|
+
completeParse() {
|
|
3035
3112
|
return this.recurse("completeParse");
|
|
3036
|
-
},
|
|
3037
|
-
abs: abstract,
|
|
3038
|
-
negate: function () {
|
|
3039
|
-
return new Mul(Num.Neg, this);
|
|
3040
3113
|
}
|
|
3041
|
-
|
|
3114
|
+
abs() {
|
|
3115
|
+
throw new Error("Abstract method - must override for expr: " +
|
|
3116
|
+
// eslint-disable-next-line @babel/no-invalid-this
|
|
3117
|
+
this.print());
|
|
3118
|
+
}
|
|
3119
|
+
negate() {
|
|
3120
|
+
return new Mul(NumNeg, this);
|
|
3121
|
+
}
|
|
3122
|
+
}
|
|
3042
3123
|
|
|
3043
3124
|
/* abstract sequence node */
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3125
|
+
class Seq extends Expr {
|
|
3126
|
+
// TODO(kevinb): Update this use `...args: Expr[]`
|
|
3127
|
+
constructor(...args) {
|
|
3128
|
+
super();
|
|
3129
|
+
// This should always have at least two terms.
|
|
3130
|
+
// TODO(kevinb): Try enforcing this at the type-level using [T, T, T[]]
|
|
3131
|
+
this.terms = void 0;
|
|
3132
|
+
if (args.length === 1) {
|
|
3133
|
+
this.terms = args[0];
|
|
3134
|
+
} else {
|
|
3135
|
+
this.terms = args;
|
|
3136
|
+
}
|
|
3137
|
+
}
|
|
3138
|
+
args() {
|
|
3048
3139
|
return this.terms;
|
|
3049
|
-
}
|
|
3050
|
-
normalize
|
|
3051
|
-
var terms = _.sortBy(_.invoke(this.terms, "normalize"),
|
|
3140
|
+
}
|
|
3141
|
+
normalize() {
|
|
3142
|
+
var terms = _.sortBy(_.invoke(this.terms, "normalize"), term => {
|
|
3052
3143
|
return term.print();
|
|
3053
3144
|
});
|
|
3054
3145
|
return new this.func(terms);
|
|
3055
|
-
}
|
|
3056
|
-
expand
|
|
3146
|
+
}
|
|
3147
|
+
expand() {
|
|
3057
3148
|
return this.recurse("expand").flatten();
|
|
3058
|
-
}
|
|
3149
|
+
}
|
|
3150
|
+
|
|
3059
3151
|
// partition the sequence into its numeric and non-numeric parts
|
|
3060
3152
|
// makes no guarantees about the validity of either part!
|
|
3061
|
-
partition
|
|
3062
|
-
var
|
|
3153
|
+
partition() {
|
|
3154
|
+
var [numbers, others] = partition(this.terms, term => {
|
|
3063
3155
|
return term instanceof Num;
|
|
3064
3156
|
});
|
|
3065
|
-
|
|
3066
|
-
// XXX using a boolean as a key just converts it to a string. I don't
|
|
3067
|
-
// think this code was written with that in mind. Probably doesn't
|
|
3068
|
-
// matter except for readability.
|
|
3069
|
-
var numbers = terms[true] || [];
|
|
3070
|
-
var others = terms[false] || [];
|
|
3071
3157
|
return [new this.func(numbers), new this.func(others)];
|
|
3072
|
-
}
|
|
3158
|
+
}
|
|
3159
|
+
|
|
3073
3160
|
// ensure that sequences have 2+ terms and no nested sequences of the same type
|
|
3074
3161
|
// this is a shallow flattening and will return a non-Seq if terms.length <= 1
|
|
3075
|
-
flatten
|
|
3162
|
+
flatten() {
|
|
3076
3163
|
var type = this;
|
|
3077
|
-
var terms = _.reject(this.terms,
|
|
3164
|
+
var terms = _.reject(this.terms, term => {
|
|
3165
|
+
// @ts-expect-error: `identity` is defined on Add and Mul but doesn't
|
|
3166
|
+
// exist on Seq itself.
|
|
3078
3167
|
return term.equals(type.identity);
|
|
3079
3168
|
});
|
|
3080
3169
|
if (terms.length === 0) {
|
|
3170
|
+
// @ts-expect-error: `identity` is defined on Add and Mul but doesn't
|
|
3171
|
+
// exist on Seq itself.
|
|
3081
3172
|
return type.identity;
|
|
3082
3173
|
}
|
|
3083
3174
|
if (terms.length === 1) {
|
|
3084
3175
|
return terms[0];
|
|
3085
3176
|
}
|
|
3086
|
-
var grouped = _.groupBy(terms, function (term) {
|
|
3087
|
-
return term instanceof type.func;
|
|
3088
|
-
});
|
|
3089
3177
|
|
|
3090
3178
|
// same contains the children which are Seqs of the same type as this Seq
|
|
3091
|
-
|
|
3092
|
-
|
|
3179
|
+
const [same, others] = partition(terms, term => {
|
|
3180
|
+
return term instanceof type.func;
|
|
3181
|
+
});
|
|
3093
3182
|
var flattened = others.concat(_.flatten(_.pluck(same, "terms"), /* shallow: */true));
|
|
3094
3183
|
return new type.func(flattened);
|
|
3095
|
-
}
|
|
3096
|
-
|
|
3097
|
-
identity: undefined,
|
|
3184
|
+
}
|
|
3185
|
+
|
|
3098
3186
|
// reduce a numeric sequence to a Num
|
|
3099
|
-
|
|
3100
|
-
isPositive
|
|
3187
|
+
|
|
3188
|
+
isPositive() {
|
|
3101
3189
|
var terms = _.invoke(this.terms, "collect");
|
|
3102
3190
|
return _.all(_.invoke(terms, "isPositive"));
|
|
3103
|
-
}
|
|
3191
|
+
}
|
|
3192
|
+
|
|
3104
3193
|
// return a new Seq with a given term replaced by a different term
|
|
3105
3194
|
// (or array of terms). given term can be passed directly, or by index
|
|
3106
3195
|
// if no new term is provided, the old one is simply removed
|
|
3107
|
-
replace
|
|
3108
|
-
|
|
3109
|
-
if (oldTerm instanceof Expr) {
|
|
3110
|
-
index = _.indexOf(this.terms, oldTerm);
|
|
3111
|
-
} else {
|
|
3112
|
-
index = oldTerm;
|
|
3113
|
-
}
|
|
3196
|
+
replace(oldTerm, newTerm) {
|
|
3197
|
+
const index = oldTerm instanceof Expr ? _.indexOf(this.terms, oldTerm) : oldTerm;
|
|
3114
3198
|
var newTerms = [];
|
|
3115
|
-
if (
|
|
3199
|
+
if (Array.isArray(newTerm)) {
|
|
3116
3200
|
newTerms = newTerm;
|
|
3117
3201
|
} else if (newTerm) {
|
|
3118
3202
|
newTerms = [newTerm];
|
|
3119
3203
|
}
|
|
3120
3204
|
var terms = this.terms.slice(0, index).concat(newTerms).concat(this.terms.slice(index + 1));
|
|
3121
3205
|
return new this.func(terms);
|
|
3122
|
-
}
|
|
3206
|
+
}
|
|
3207
|
+
|
|
3123
3208
|
// syntactic sugar for replace()
|
|
3124
|
-
remove
|
|
3209
|
+
remove(term) {
|
|
3125
3210
|
return this.replace(term);
|
|
3126
|
-
}
|
|
3127
|
-
getDenominator
|
|
3211
|
+
}
|
|
3212
|
+
getDenominator() {
|
|
3128
3213
|
// TODO(alex): find and return LCM
|
|
3129
3214
|
return new Mul(_.invoke(this.terms, "getDenominator")).flatten();
|
|
3130
3215
|
}
|
|
3131
|
-
}
|
|
3216
|
+
}
|
|
3132
3217
|
|
|
3133
3218
|
/* sequence of additive terms */
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
this.
|
|
3219
|
+
class Add extends Seq {
|
|
3220
|
+
constructor(...args) {
|
|
3221
|
+
super(...args);
|
|
3222
|
+
this.identity = NumZero;
|
|
3223
|
+
this.func = Add;
|
|
3139
3224
|
}
|
|
3140
|
-
}
|
|
3141
|
-
|
|
3142
|
-
_.extend(Add.prototype, {
|
|
3143
|
-
func: Add,
|
|
3144
|
-
eval: function (vars, options) {
|
|
3145
|
-
return _.reduce(this.terms, function (memo, term) {
|
|
3225
|
+
eval(vars = {}, options) {
|
|
3226
|
+
return _.reduce(this.terms, (memo, term) => {
|
|
3146
3227
|
return memo + term.eval(vars, options);
|
|
3147
3228
|
}, 0);
|
|
3148
|
-
}
|
|
3149
|
-
codegen
|
|
3150
|
-
return _.map(this.terms,
|
|
3229
|
+
}
|
|
3230
|
+
codegen() {
|
|
3231
|
+
return _.map(this.terms, term => {
|
|
3151
3232
|
return "(" + term.codegen() + ")";
|
|
3152
3233
|
}).join(" + ") || "0";
|
|
3153
|
-
}
|
|
3154
|
-
print
|
|
3234
|
+
}
|
|
3235
|
+
print() {
|
|
3155
3236
|
return _.invoke(this.terms, "print").join("+");
|
|
3156
|
-
}
|
|
3157
|
-
tex
|
|
3158
|
-
|
|
3159
|
-
_.each(this.terms,
|
|
3237
|
+
}
|
|
3238
|
+
tex() {
|
|
3239
|
+
let tex = "";
|
|
3240
|
+
_.each(this.terms, term => {
|
|
3160
3241
|
if (!tex || term.isSubtract()) {
|
|
3161
3242
|
tex += term.tex();
|
|
3162
3243
|
} else {
|
|
@@ -3164,28 +3245,26 @@ _.extend(Add.prototype, {
|
|
|
3164
3245
|
}
|
|
3165
3246
|
});
|
|
3166
3247
|
return tex;
|
|
3167
|
-
}
|
|
3168
|
-
collect
|
|
3248
|
+
}
|
|
3249
|
+
collect(options) {
|
|
3169
3250
|
var terms = _.invoke(this.terms, "collect", options);
|
|
3170
|
-
|
|
3171
|
-
// [Expr expr, Num coefficient]
|
|
3172
3251
|
var pairs = [];
|
|
3173
|
-
_.each(terms,
|
|
3252
|
+
_.each(terms, term => {
|
|
3174
3253
|
if (term instanceof Mul) {
|
|
3175
3254
|
var muls = term.partition();
|
|
3176
3255
|
pairs.push([muls[1].flatten(), muls[0].reduce(options)]);
|
|
3177
3256
|
} else if (term instanceof Num) {
|
|
3178
|
-
pairs.push([
|
|
3257
|
+
pairs.push([NumOne, term]);
|
|
3179
3258
|
} else {
|
|
3180
|
-
pairs.push([term,
|
|
3259
|
+
pairs.push([term, NumOne]);
|
|
3181
3260
|
}
|
|
3182
3261
|
});
|
|
3183
3262
|
|
|
3184
3263
|
// { (Expr expr).print(): [[Expr expr, Num coefficient]] }
|
|
3185
|
-
var grouped = _.groupBy(pairs,
|
|
3264
|
+
var grouped = _.groupBy(pairs, pair => {
|
|
3186
3265
|
return pair[0].normalize().print();
|
|
3187
3266
|
});
|
|
3188
|
-
var collected = _.compact(_.map(grouped,
|
|
3267
|
+
var collected = _.compact(_.map(grouped, pairs => {
|
|
3189
3268
|
var expr = pairs[0][0];
|
|
3190
3269
|
var sum = new Add(_.zip.apply(_, pairs)[1]);
|
|
3191
3270
|
var coefficient = sum.reduce(options);
|
|
@@ -3196,90 +3275,94 @@ _.extend(Add.prototype, {
|
|
|
3196
3275
|
// e.g. x*sin^2(y) + x*cos^2(y) -> x
|
|
3197
3276
|
|
|
3198
3277
|
return new Add(collected).flatten();
|
|
3199
|
-
}
|
|
3278
|
+
}
|
|
3279
|
+
|
|
3200
3280
|
// naively factor out anything that is common to all terms
|
|
3201
3281
|
// if options.keepNegative is specified, won't factor out a common -1
|
|
3202
|
-
factor
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
var factors;
|
|
3282
|
+
factor(options = {
|
|
3283
|
+
keepNegative: false
|
|
3284
|
+
}) {
|
|
3285
|
+
const terms = this.terms.map(term => term.collect());
|
|
3286
|
+
let factors;
|
|
3208
3287
|
if (terms[0] instanceof Mul) {
|
|
3209
3288
|
factors = terms[0].terms;
|
|
3210
3289
|
} else {
|
|
3211
3290
|
factors = [terms[0]];
|
|
3212
3291
|
}
|
|
3213
|
-
_.each(_.rest(this.terms),
|
|
3214
|
-
factors = _.map(factors,
|
|
3292
|
+
_.each(_.rest(this.terms), term => {
|
|
3293
|
+
factors = _.map(factors, factor => {
|
|
3215
3294
|
return term.findGCD(factor);
|
|
3216
3295
|
});
|
|
3217
3296
|
});
|
|
3218
3297
|
if (!options.keepNegative && this.isNegative()) {
|
|
3219
|
-
factors.push(
|
|
3298
|
+
factors.push(NumNeg);
|
|
3220
3299
|
}
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
reduce: function (options) {
|
|
3229
|
-
return _.reduce(this.terms, function (memo, term) {
|
|
3300
|
+
const left = new Mul(factors).flatten().collect();
|
|
3301
|
+
const remainder = terms.map(term => Mul.handleDivide(term, left).simplify());
|
|
3302
|
+
const right = new Add(remainder).flatten();
|
|
3303
|
+
return Mul.createOrAppend(left, right).flatten();
|
|
3304
|
+
}
|
|
3305
|
+
reduce(options) {
|
|
3306
|
+
return _.reduce(this.terms, (memo, term) => {
|
|
3230
3307
|
return memo.add(term, options);
|
|
3231
3308
|
}, this.identity);
|
|
3232
|
-
}
|
|
3233
|
-
needsExplicitMul
|
|
3309
|
+
}
|
|
3310
|
+
needsExplicitMul() {
|
|
3234
3311
|
return false;
|
|
3235
|
-
}
|
|
3236
|
-
isNegative
|
|
3312
|
+
}
|
|
3313
|
+
isNegative() {
|
|
3237
3314
|
var terms = _.invoke(this.terms, "collect");
|
|
3238
3315
|
return _.all(_.invoke(terms, "isNegative"));
|
|
3239
|
-
}
|
|
3240
|
-
negate
|
|
3316
|
+
}
|
|
3317
|
+
negate() {
|
|
3241
3318
|
return new Add(_.invoke(this.terms, "negate"));
|
|
3242
3319
|
}
|
|
3243
|
-
});
|
|
3244
3320
|
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3321
|
+
// create a new sequence unless left is already one (returns a copy)
|
|
3322
|
+
static createOrAppend(left, right) {
|
|
3323
|
+
if (left instanceof Add) {
|
|
3324
|
+
return new Add(left.terms.concat(right));
|
|
3325
|
+
} else {
|
|
3326
|
+
return new Add(left, right);
|
|
3327
|
+
}
|
|
3251
3328
|
}
|
|
3252
3329
|
}
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3330
|
+
|
|
3331
|
+
/* sequence of multiplicative terms */
|
|
3332
|
+
class Mul extends Seq {
|
|
3333
|
+
constructor(...args) {
|
|
3334
|
+
super(...args);
|
|
3335
|
+
this.identity = NumOne;
|
|
3336
|
+
this.func = Mul;
|
|
3337
|
+
}
|
|
3338
|
+
eval(vars = {}, options) {
|
|
3339
|
+
return _.reduce(this.terms, (memo, term) => {
|
|
3258
3340
|
return memo * term.eval(vars, options);
|
|
3259
3341
|
}, 1);
|
|
3260
|
-
}
|
|
3261
|
-
codegen
|
|
3262
|
-
return _.map(this.terms,
|
|
3342
|
+
}
|
|
3343
|
+
codegen() {
|
|
3344
|
+
return _.map(this.terms, term => {
|
|
3263
3345
|
return "(" + term.codegen() + ")";
|
|
3264
3346
|
}).join(" * ") || "0";
|
|
3265
|
-
}
|
|
3266
|
-
print
|
|
3267
|
-
return _.map(this.terms,
|
|
3347
|
+
}
|
|
3348
|
+
print() {
|
|
3349
|
+
return _.map(this.terms, term => {
|
|
3268
3350
|
return term instanceof Add ? "(" + term.print() + ")" : term.print();
|
|
3269
3351
|
}).join("*");
|
|
3270
|
-
}
|
|
3271
|
-
getUnits
|
|
3272
|
-
var tmUnits = _(this.terms).chain().map(
|
|
3352
|
+
}
|
|
3353
|
+
getUnits() {
|
|
3354
|
+
var tmUnits = _(this.terms).chain().map(term => {
|
|
3273
3355
|
return term.getUnits();
|
|
3274
3356
|
}).flatten().value();
|
|
3275
3357
|
tmUnits.sort((a, b) => a.unit.localeCompare(b.unit));
|
|
3276
3358
|
return tmUnits;
|
|
3277
|
-
}
|
|
3359
|
+
}
|
|
3360
|
+
|
|
3278
3361
|
// since we don't care about commutativity, we can render a Mul any way we choose
|
|
3279
3362
|
// so we follow convention: first any negatives, then any numbers, then everything else
|
|
3280
|
-
tex
|
|
3363
|
+
tex() {
|
|
3281
3364
|
var cdot = " \\cdot ";
|
|
3282
|
-
var terms = _.groupBy(this.terms,
|
|
3365
|
+
var terms = _.groupBy(this.terms, term => {
|
|
3283
3366
|
if (term.isDivide()) {
|
|
3284
3367
|
return "inverse";
|
|
3285
3368
|
} else if (term instanceof Num) {
|
|
@@ -3306,12 +3389,11 @@ _.extend(Mul.prototype, {
|
|
|
3306
3389
|
return numbers[i].tex() + new Mul(newTerms).tex();
|
|
3307
3390
|
}
|
|
3308
3391
|
}
|
|
3309
|
-
numbers = _.compact(_.map(numbers,
|
|
3310
|
-
var hasDenom = term instanceof Rational && !(term instanceof Int);
|
|
3392
|
+
numbers = _.compact(_.map(numbers, term => {
|
|
3311
3393
|
var shouldPushDown = !term.hints.fraction || inverses.length > 0;
|
|
3312
|
-
if (
|
|
3394
|
+
if (term instanceof Rational && !(term instanceof Int) && shouldPushDown) {
|
|
3313
3395
|
// e.g. 3x/4 -> 3/4*x (internally) -> 3x/4 (rendered)
|
|
3314
|
-
inverses.push(new Pow(new Int(term.d),
|
|
3396
|
+
inverses.push(new Pow(new Int(term.d), NumDiv));
|
|
3315
3397
|
var number = new Int(term.n);
|
|
3316
3398
|
number.hints = term.hints;
|
|
3317
3399
|
return _.any(term.hints) ? number : null;
|
|
@@ -3324,7 +3406,7 @@ _.extend(Mul.prototype, {
|
|
|
3324
3406
|
numerator = others[0].tex();
|
|
3325
3407
|
} else {
|
|
3326
3408
|
var tex = "";
|
|
3327
|
-
_.each(numbers,
|
|
3409
|
+
_.each(numbers, term => {
|
|
3328
3410
|
if (term.hints.subtract && term.hints.entered) {
|
|
3329
3411
|
negatives += "-";
|
|
3330
3412
|
tex += (tex ? cdot : "") + term.abs().tex();
|
|
@@ -3337,7 +3419,7 @@ _.extend(Mul.prototype, {
|
|
|
3337
3419
|
tex += (tex ? cdot : "") + term.tex();
|
|
3338
3420
|
}
|
|
3339
3421
|
});
|
|
3340
|
-
_.each(others,
|
|
3422
|
+
_.each(others, term => {
|
|
3341
3423
|
if (term.needsExplicitMul()) {
|
|
3342
3424
|
// e.g. 2*2^3 -> 2(dot)2^3
|
|
3343
3425
|
tex += (tex ? cdot : "") + term.tex();
|
|
@@ -3357,76 +3439,69 @@ _.extend(Mul.prototype, {
|
|
|
3357
3439
|
var denominator = new Mul(_.invoke(inverses, "asDivide")).flatten().tex();
|
|
3358
3440
|
return negatives + "\\frac{" + numerator + "}{" + denominator + "}";
|
|
3359
3441
|
}
|
|
3360
|
-
}
|
|
3361
|
-
strip
|
|
3362
|
-
var terms = _.map(this.terms,
|
|
3442
|
+
}
|
|
3443
|
+
strip() {
|
|
3444
|
+
var terms = _.map(this.terms, term => {
|
|
3363
3445
|
return term instanceof Num ? term.abs() : term.strip();
|
|
3364
3446
|
});
|
|
3365
3447
|
return new Mul(terms).flatten();
|
|
3366
|
-
}
|
|
3448
|
+
}
|
|
3449
|
+
|
|
3367
3450
|
// expand numerator and denominator separately
|
|
3368
|
-
expand
|
|
3369
|
-
|
|
3370
|
-
return term instanceof Add;
|
|
3371
|
-
};
|
|
3372
|
-
var isInverse = function isInverse(term) {
|
|
3451
|
+
expand() {
|
|
3452
|
+
const isInverse = function isInverse(term) {
|
|
3373
3453
|
return term instanceof Pow && term.exp.isNegative();
|
|
3374
3454
|
};
|
|
3375
|
-
|
|
3455
|
+
const isInverseAdd = function isInverseAdd(term) {
|
|
3376
3456
|
return isInverse(term) && isAdd(term.base);
|
|
3377
3457
|
};
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3458
|
+
const mul = this.recurse("expand").flatten();
|
|
3459
|
+
const factors = getFactors(mul);
|
|
3460
|
+
const hasAdd = _.any(factors, isAdd);
|
|
3461
|
+
const hasInverseAdd = _.any(factors, isInverseAdd);
|
|
3381
3462
|
if (!(hasAdd || hasInverseAdd)) {
|
|
3382
3463
|
return mul;
|
|
3383
3464
|
}
|
|
3384
|
-
|
|
3385
|
-
var normals = terms[false] || [];
|
|
3386
|
-
var inverses = terms[true] || [];
|
|
3465
|
+
let [inverses, normals] = partition(factors, isInverse);
|
|
3387
3466
|
if (hasAdd) {
|
|
3388
|
-
|
|
3389
|
-
var adds = grouped[true] || [];
|
|
3390
|
-
var others = grouped[false] || [];
|
|
3467
|
+
const [adds, others] = partition(normals, isAdd);
|
|
3391
3468
|
|
|
3392
3469
|
// loop over each additive sequence
|
|
3393
|
-
|
|
3470
|
+
const expanded = _.reduce(adds, function (expanded, add) {
|
|
3394
3471
|
// loop over each expanded array of terms
|
|
3395
3472
|
return _.reduce(expanded, function (temp, array) {
|
|
3396
3473
|
// loop over each additive sequence's terms
|
|
3397
|
-
return temp.concat(_.map(add.terms,
|
|
3398
|
-
return array.concat(term);
|
|
3399
|
-
}));
|
|
3474
|
+
return temp.concat(_.map(add.terms, term => array.concat(term)));
|
|
3400
3475
|
}, []);
|
|
3401
3476
|
}, [[]]);
|
|
3402
3477
|
|
|
3403
3478
|
// join each fully expanded array of factors with remaining multiplicative factors
|
|
3404
|
-
|
|
3479
|
+
const muls = _.map(expanded, function (array) {
|
|
3405
3480
|
return new Mul(others.concat(array)).flatten();
|
|
3406
3481
|
});
|
|
3407
3482
|
normals = [new Add(muls)];
|
|
3408
3483
|
}
|
|
3409
3484
|
if (hasInverseAdd) {
|
|
3410
|
-
|
|
3411
|
-
inverses = [new Pow(denominator.expand(),
|
|
3485
|
+
const denominator = new Mul(_.invoke(inverses, "getDenominator")).flatten();
|
|
3486
|
+
inverses = [new Pow(denominator.expand(), NumDiv)];
|
|
3412
3487
|
}
|
|
3413
3488
|
return new Mul(normals.concat(inverses)).flatten();
|
|
3414
|
-
}
|
|
3415
|
-
factor
|
|
3489
|
+
}
|
|
3490
|
+
factor(options) {
|
|
3416
3491
|
var factored = this.recurse("factor", options).flatten();
|
|
3417
3492
|
if (!(factored instanceof Mul)) {
|
|
3418
3493
|
return factored;
|
|
3419
3494
|
}
|
|
3420
3495
|
|
|
3421
3496
|
// Combine any factored out Rationals into one, but don't collect
|
|
3422
|
-
var
|
|
3497
|
+
var [rationals, others] = partition(factored.terms, term => {
|
|
3423
3498
|
return term instanceof Rational;
|
|
3424
3499
|
});
|
|
3425
3500
|
|
|
3426
3501
|
// Could also accomplish this by passing a new option
|
|
3427
3502
|
// e.g. return memo.mul(term, {autocollect: false});
|
|
3428
3503
|
// TODO(alex): Decide whether this is a good use of options or not
|
|
3429
|
-
|
|
3504
|
+
const ratObj = _.reduce(rationals, (memo, term) => {
|
|
3430
3505
|
return {
|
|
3431
3506
|
n: memo.n * term.n,
|
|
3432
3507
|
d: memo.d * term.d
|
|
@@ -3435,47 +3510,39 @@ _.extend(Mul.prototype, {
|
|
|
3435
3510
|
n: 1,
|
|
3436
3511
|
d: 1
|
|
3437
3512
|
});
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
}
|
|
3443
|
-
return new Mul((grouped[false] || []).concat(rational)).flatten();
|
|
3444
|
-
},
|
|
3445
|
-
collect: function (options) {
|
|
3513
|
+
const rational = ratObj.d === 1 ? new Int(ratObj.n) : new Rational(ratObj.n, ratObj.d);
|
|
3514
|
+
return new Mul(others.concat(rational)).flatten();
|
|
3515
|
+
}
|
|
3516
|
+
collect(options) {
|
|
3446
3517
|
var partitioned = this.recurse("collect", options).partition();
|
|
3447
3518
|
var number = partitioned[0].reduce(options);
|
|
3448
3519
|
|
|
3449
3520
|
// e.g. 0*x -> 0
|
|
3450
3521
|
if (number.eval() === 0) {
|
|
3451
|
-
return
|
|
3522
|
+
return NumZero;
|
|
3452
3523
|
}
|
|
3453
|
-
|
|
3524
|
+
const other = partitioned[1].flatten();
|
|
3454
3525
|
|
|
3455
3526
|
// e.g. 2*2 -> 4
|
|
3456
3527
|
// e.g. 2*2*x -> 4*x
|
|
3457
|
-
if (!(
|
|
3458
|
-
return new Mul(number,
|
|
3528
|
+
if (!(other instanceof Mul)) {
|
|
3529
|
+
return new Mul(number, other).flatten();
|
|
3459
3530
|
}
|
|
3460
|
-
others =
|
|
3461
|
-
|
|
3462
|
-
// [Expr base, Expr exp]
|
|
3531
|
+
const others = other.terms;
|
|
3463
3532
|
var pairs = [];
|
|
3464
|
-
_.each(others,
|
|
3533
|
+
_.each(others, term => {
|
|
3465
3534
|
if (term instanceof Pow) {
|
|
3466
3535
|
pairs.push([term.base, term.exp]);
|
|
3467
3536
|
} else {
|
|
3468
|
-
pairs.push([term,
|
|
3537
|
+
pairs.push([term, NumOne]);
|
|
3469
3538
|
}
|
|
3470
3539
|
});
|
|
3471
3540
|
|
|
3472
3541
|
// {(Expr base).print(): [[Expr base, Expr exp]]}
|
|
3473
|
-
var grouped = _.groupBy(pairs,
|
|
3542
|
+
var grouped = _.groupBy(pairs, pair => {
|
|
3474
3543
|
return pair[0].normalize().print();
|
|
3475
3544
|
});
|
|
3476
|
-
|
|
3477
|
-
// [[Expr base, Expr exp]]
|
|
3478
|
-
var summed = _.compact(_.map(grouped, function (pairs) {
|
|
3545
|
+
var summed = _.compact(_.map(grouped, pairs => {
|
|
3479
3546
|
var base = pairs[0][0];
|
|
3480
3547
|
var sum = new Add(_.zip.apply(_, pairs)[1]);
|
|
3481
3548
|
var exp = sum.collect(options);
|
|
@@ -3487,7 +3554,7 @@ _.extend(Mul.prototype, {
|
|
|
3487
3554
|
}));
|
|
3488
3555
|
|
|
3489
3556
|
// XXX `pairs` is shadowed four or five times in this function
|
|
3490
|
-
|
|
3557
|
+
const groupedPairs = _.groupBy(summed, pair => {
|
|
3491
3558
|
if (pair[0] instanceof Trig && pair[0].isBasic()) {
|
|
3492
3559
|
return "trig";
|
|
3493
3560
|
} else if (pair[0] instanceof Log) {
|
|
@@ -3496,26 +3563,26 @@ _.extend(Mul.prototype, {
|
|
|
3496
3563
|
return "expr";
|
|
3497
3564
|
}
|
|
3498
3565
|
});
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3566
|
+
let trigs = groupedPairs.trig || [];
|
|
3567
|
+
let logs = groupedPairs.log || [];
|
|
3568
|
+
const exprs = groupedPairs.expr || [];
|
|
3502
3569
|
if (trigs.length > 1) {
|
|
3503
3570
|
// combine sines and cosines into other trig functions
|
|
3504
3571
|
|
|
3505
3572
|
// {Trig.arg.print(): [[Trig base, Expr exp]]}
|
|
3506
|
-
var byArg = _.groupBy(trigs,
|
|
3573
|
+
var byArg = _.groupBy(trigs, pair => {
|
|
3507
3574
|
return pair[0].arg.normalize().print();
|
|
3508
3575
|
});
|
|
3509
3576
|
trigs = [];
|
|
3510
|
-
_.each(byArg,
|
|
3511
|
-
|
|
3577
|
+
_.each(byArg, pairs => {
|
|
3578
|
+
const arg = pairs[0][0].arg;
|
|
3512
3579
|
|
|
3513
3580
|
// {Trig.type: Expr exp}
|
|
3514
|
-
|
|
3515
|
-
sin:
|
|
3516
|
-
cos:
|
|
3581
|
+
let funcs = {
|
|
3582
|
+
sin: NumZero,
|
|
3583
|
+
cos: NumZero
|
|
3517
3584
|
};
|
|
3518
|
-
_.each(pairs,
|
|
3585
|
+
_.each(pairs, pair => {
|
|
3519
3586
|
funcs[pair[0].type] = pair[1];
|
|
3520
3587
|
});
|
|
3521
3588
|
if (Mul.handleNegative(funcs.sin).collect(options).equals(funcs.cos)) {
|
|
@@ -3534,7 +3601,7 @@ _.extend(Mul.prototype, {
|
|
|
3534
3601
|
// TODO(alex): combine even if exponents not a perfect match
|
|
3535
3602
|
// TODO(alex): transform 1/sin and 1/cos into csc and sec
|
|
3536
3603
|
|
|
3537
|
-
_.each(funcs,
|
|
3604
|
+
_.each(funcs, (exp, type) => {
|
|
3538
3605
|
trigs.push([new Trig(type, arg), exp]);
|
|
3539
3606
|
});
|
|
3540
3607
|
});
|
|
@@ -3543,11 +3610,11 @@ _.extend(Mul.prototype, {
|
|
|
3543
3610
|
// combine logs with the same base
|
|
3544
3611
|
|
|
3545
3612
|
// {Log.base.print(): [[Log base, Expr exp]]}
|
|
3546
|
-
var byBase = _.groupBy(logs,
|
|
3613
|
+
var byBase = _.groupBy(logs, pair => {
|
|
3547
3614
|
return pair[0].base.normalize().print();
|
|
3548
3615
|
});
|
|
3549
3616
|
logs = [];
|
|
3550
|
-
_.each(byBase,
|
|
3617
|
+
_.each(byBase, pairs => {
|
|
3551
3618
|
// only combine two logs of the same base, otherwise commutative
|
|
3552
3619
|
// differences result in different equally valid output
|
|
3553
3620
|
// e.g. ln(x)/ln(z)*ln(y) -> log_z(x)*ln(y)
|
|
@@ -3567,25 +3634,25 @@ _.extend(Mul.prototype, {
|
|
|
3567
3634
|
// TODO(alex): combine if all inverses are the same e.g. ln(y)*ln(z)/ln(x)/ln(x)
|
|
3568
3635
|
}
|
|
3569
3636
|
|
|
3570
|
-
|
|
3571
|
-
var collected = _.map(pairs, function (pair) {
|
|
3637
|
+
var collected = _.map([...trigs, ...logs, ...exprs], pair => {
|
|
3572
3638
|
return new Pow(pair[0], pair[1]).collect(options);
|
|
3573
3639
|
});
|
|
3574
3640
|
return new Mul([number].concat(collected)).flatten();
|
|
3575
|
-
}
|
|
3576
|
-
isSubtract
|
|
3577
|
-
return _.any(this.terms,
|
|
3578
|
-
return term instanceof Num && term.hints.subtract;
|
|
3641
|
+
}
|
|
3642
|
+
isSubtract() {
|
|
3643
|
+
return _.any(this.terms, term => {
|
|
3644
|
+
return term instanceof Num && Boolean(term.hints.subtract);
|
|
3579
3645
|
});
|
|
3580
|
-
}
|
|
3646
|
+
}
|
|
3647
|
+
|
|
3581
3648
|
// factor a single -1 in to the Mul
|
|
3582
3649
|
// combine with a Num if all Nums are positive, else add as a term
|
|
3583
|
-
factorIn
|
|
3650
|
+
factorIn(hint) {
|
|
3584
3651
|
var partitioned = this.partition();
|
|
3652
|
+
// `partition` splits the terms into two Seqs - one containing
|
|
3653
|
+
// only Nums and the all non-Num nodes.
|
|
3585
3654
|
var numbers = partitioned[0].terms;
|
|
3586
|
-
var fold = numbers.length && _.all(numbers,
|
|
3587
|
-
return num.n > 0;
|
|
3588
|
-
});
|
|
3655
|
+
var fold = numbers.length && _.all(numbers, num => num.n > 0);
|
|
3589
3656
|
if (fold) {
|
|
3590
3657
|
// e.g. - x*2*3 -> x*-2*3
|
|
3591
3658
|
var num = numbers[0].negate();
|
|
@@ -3596,12 +3663,13 @@ _.extend(Mul.prototype, {
|
|
|
3596
3663
|
// e.g. - x*-2 -> -1*x*-2
|
|
3597
3664
|
return new Mul([Num.negativeOne(hint)].concat(this.terms));
|
|
3598
3665
|
}
|
|
3599
|
-
}
|
|
3666
|
+
}
|
|
3667
|
+
|
|
3600
3668
|
// factor out a single hinted -1 (assume it is the division hint)
|
|
3601
3669
|
// TODO(alex): make more general or rename to be more specific
|
|
3602
|
-
factorOut
|
|
3670
|
+
factorOut() {
|
|
3603
3671
|
var factored = false;
|
|
3604
|
-
var terms = _.compact(_.map(this.terms,
|
|
3672
|
+
var terms = _.compact(_.map(this.terms, term => {
|
|
3605
3673
|
if (!factored && term instanceof Num && term.hints.divide) {
|
|
3606
3674
|
factored = true;
|
|
3607
3675
|
return term.n !== -1 ? term.negate() : null;
|
|
@@ -3614,64 +3682,50 @@ _.extend(Mul.prototype, {
|
|
|
3614
3682
|
} else {
|
|
3615
3683
|
return new Mul(terms);
|
|
3616
3684
|
}
|
|
3617
|
-
}
|
|
3618
|
-
reduce
|
|
3619
|
-
return _.reduce(this.terms,
|
|
3685
|
+
}
|
|
3686
|
+
reduce(options) {
|
|
3687
|
+
return _.reduce(this.terms, (memo, term) => {
|
|
3620
3688
|
return memo.mul(term, options);
|
|
3621
3689
|
}, this.identity);
|
|
3622
|
-
}
|
|
3623
|
-
findGCD
|
|
3690
|
+
}
|
|
3691
|
+
findGCD(factor) {
|
|
3624
3692
|
return new Mul(_.invoke(this.terms, "findGCD", factor)).flatten();
|
|
3625
|
-
}
|
|
3626
|
-
asMul
|
|
3693
|
+
}
|
|
3694
|
+
asMul() {
|
|
3627
3695
|
return this;
|
|
3628
|
-
}
|
|
3629
|
-
asPositiveFactor
|
|
3696
|
+
}
|
|
3697
|
+
asPositiveFactor() {
|
|
3630
3698
|
if (this.isPositive()) {
|
|
3631
3699
|
return this;
|
|
3632
3700
|
} else {
|
|
3633
|
-
|
|
3701
|
+
const terms = getFactors(this.collect()).map(factor => factor.asPositiveFactor());
|
|
3634
3702
|
return new Mul(terms).flatten();
|
|
3635
3703
|
}
|
|
3636
|
-
}
|
|
3637
|
-
isNegative
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3704
|
+
}
|
|
3705
|
+
isNegative() {
|
|
3706
|
+
const terms = getFactors(this.collect()).map(factor => factor.isNegative());
|
|
3707
|
+
return _.any(terms);
|
|
3708
|
+
}
|
|
3709
|
+
fold() {
|
|
3641
3710
|
return Mul.fold(this);
|
|
3642
|
-
}
|
|
3643
|
-
negate
|
|
3644
|
-
var isNum =
|
|
3711
|
+
}
|
|
3712
|
+
negate() {
|
|
3713
|
+
var isNum = expr => {
|
|
3645
3714
|
return expr instanceof Num;
|
|
3646
3715
|
};
|
|
3647
|
-
|
|
3648
|
-
|
|
3716
|
+
const num = _.find(this.terms, isNum);
|
|
3717
|
+
if (num) {
|
|
3649
3718
|
return this.replace(num, num.negate());
|
|
3650
3719
|
} else {
|
|
3651
|
-
return new Mul([
|
|
3720
|
+
return new Mul([NumNeg].concat(this.terms));
|
|
3652
3721
|
}
|
|
3653
3722
|
}
|
|
3654
|
-
});
|
|
3655
3723
|
|
|
3656
|
-
// static methods for the sequence types
|
|
3657
|
-
_.each([Add, Mul], function (type) {
|
|
3658
|
-
_.extend(type, {
|
|
3659
|
-
// create a new sequence unless left is already one (returns a copy)
|
|
3660
|
-
createOrAppend: function (left, right) {
|
|
3661
|
-
if (left instanceof type) {
|
|
3662
|
-
return new type(left.terms.concat(right));
|
|
3663
|
-
} else {
|
|
3664
|
-
return new type(left, right);
|
|
3665
|
-
}
|
|
3666
|
-
}
|
|
3667
|
-
});
|
|
3668
|
-
});
|
|
3669
|
-
_.extend(Mul, {
|
|
3670
3724
|
// negative signs should be folded into numbers whenever possible
|
|
3671
3725
|
// never fold into a Num that's already negative or a Mul that has a negative Num
|
|
3672
3726
|
// an optional hint is kept track of to properly render user input
|
|
3673
3727
|
// an empty hint means negation
|
|
3674
|
-
handleNegative
|
|
3728
|
+
static handleNegative(expr, hint) {
|
|
3675
3729
|
if (expr instanceof Num && expr.n > 0) {
|
|
3676
3730
|
// e.g. - 2 -> -2
|
|
3677
3731
|
var negated = expr.negate();
|
|
@@ -3687,19 +3741,20 @@ _.extend(Mul, {
|
|
|
3687
3741
|
// e.g. - x -> -1*x
|
|
3688
3742
|
return new Mul(Num.negativeOne(hint), expr);
|
|
3689
3743
|
}
|
|
3690
|
-
}
|
|
3744
|
+
}
|
|
3745
|
+
|
|
3691
3746
|
// division can create either a Rational or a Mul
|
|
3692
|
-
handleDivide
|
|
3747
|
+
static handleDivide(left, right) {
|
|
3693
3748
|
// dividing by a Mul is the same as repeated division by its terms
|
|
3694
3749
|
if (right instanceof Mul) {
|
|
3695
3750
|
var first = Mul.handleDivide(left, right.terms[0]);
|
|
3696
3751
|
var rest = new Mul(_.rest(right.terms)).flatten();
|
|
3697
3752
|
return Mul.handleDivide(first, rest);
|
|
3698
3753
|
}
|
|
3699
|
-
var isInt =
|
|
3754
|
+
var isInt = expr => {
|
|
3700
3755
|
return expr instanceof Int;
|
|
3701
3756
|
};
|
|
3702
|
-
var isRational =
|
|
3757
|
+
var isRational = expr => {
|
|
3703
3758
|
return expr instanceof Rational;
|
|
3704
3759
|
};
|
|
3705
3760
|
|
|
@@ -3708,7 +3763,7 @@ _.extend(Mul, {
|
|
|
3708
3763
|
if (isInt(right) && left instanceof Mul && _.any(left.terms, isInt)) {
|
|
3709
3764
|
// search from the right
|
|
3710
3765
|
var reversed = left.terms.slice().reverse();
|
|
3711
|
-
var num =
|
|
3766
|
+
var num = reversed.find(isRational);
|
|
3712
3767
|
if (!isInt(num)) {
|
|
3713
3768
|
return new Mul(left.terms.concat([new Rational(1, right.n).addHint("fraction")]));
|
|
3714
3769
|
}
|
|
@@ -3722,17 +3777,17 @@ _.extend(Mul, {
|
|
|
3722
3777
|
}
|
|
3723
3778
|
if (num.n < 0 && right.n < 0) {
|
|
3724
3779
|
rational.d = -rational.d;
|
|
3725
|
-
return left.replace(num, [
|
|
3780
|
+
return left.replace(num, [NumNeg, rational]);
|
|
3726
3781
|
} else {
|
|
3727
3782
|
return left.replace(num, rational);
|
|
3728
3783
|
}
|
|
3729
3784
|
}
|
|
3730
|
-
var divide =
|
|
3785
|
+
var divide = (a, b) => {
|
|
3731
3786
|
if (b instanceof Int) {
|
|
3732
3787
|
if (a instanceof Int) {
|
|
3733
3788
|
if (a.n < 0 && b.n < 0) {
|
|
3734
3789
|
// e.g. -2 / -3 -> -1*-2/3
|
|
3735
|
-
return [
|
|
3790
|
+
return [NumNeg, new Rational(a.n, -b.n).addHint("fraction")];
|
|
3736
3791
|
} else {
|
|
3737
3792
|
// e.g. 2 / 3 -> 2/3
|
|
3738
3793
|
// e.g. -2 / 3 -> -2/3
|
|
@@ -3764,7 +3819,7 @@ _.extend(Mul, {
|
|
|
3764
3819
|
pow = new Pow(b.base, Mul.handleNegative(b.exp, "divide"));
|
|
3765
3820
|
} else {
|
|
3766
3821
|
// e.g. x ^ -1 -> x^-1
|
|
3767
|
-
pow = new Pow(b,
|
|
3822
|
+
pow = new Pow(b, NumDiv);
|
|
3768
3823
|
}
|
|
3769
3824
|
if (a instanceof Int && a.n === 1) {
|
|
3770
3825
|
// e.g. 1 / x -> x^-1
|
|
@@ -3776,13 +3831,16 @@ _.extend(Mul, {
|
|
|
3776
3831
|
}
|
|
3777
3832
|
};
|
|
3778
3833
|
if (left instanceof Mul) {
|
|
3834
|
+
// NOTE(kevinb): `terms` should always have at least two
|
|
3835
|
+
// elements so getting the last element is safe to do.
|
|
3779
3836
|
var divided = divide(_.last(left.terms), right);
|
|
3780
3837
|
return new Mul(_.initial(left.terms).concat(divided));
|
|
3781
3838
|
} else {
|
|
3782
3839
|
var divided = divide(left, right);
|
|
3783
3840
|
return new Mul(divided).flatten();
|
|
3784
3841
|
}
|
|
3785
|
-
}
|
|
3842
|
+
}
|
|
3843
|
+
|
|
3786
3844
|
// fold negative signs into numbers if possible
|
|
3787
3845
|
// negative signs are not the same as multiplying by negative one!
|
|
3788
3846
|
// e.g. -x -> -1*x simplified
|
|
@@ -3795,24 +3853,20 @@ _.extend(Mul, {
|
|
|
3795
3853
|
// e.g. sin(x)*x -> sin(x)*x
|
|
3796
3854
|
// e.g. sin(x)*(x) -> sin(x)*x
|
|
3797
3855
|
// e.g. sin(x)*sin(y) -> sin(x)*sin(y)
|
|
3798
|
-
fold
|
|
3856
|
+
static fold(expr) {
|
|
3799
3857
|
if (expr instanceof Mul) {
|
|
3800
3858
|
// assuming that this will be second to last
|
|
3801
|
-
var trigLog = _.find(_.initial(expr.terms),
|
|
3802
|
-
return (term instanceof Trig || term instanceof Log) && term.hints.open;
|
|
3859
|
+
var trigLog = _.find(_.initial(expr.terms), term => {
|
|
3860
|
+
return (term instanceof Trig || term instanceof Log) && Boolean(term.hints.open);
|
|
3803
3861
|
});
|
|
3804
|
-
var index = _.indexOf(expr.terms, trigLog);
|
|
3805
3862
|
if (trigLog) {
|
|
3806
|
-
|
|
3863
|
+
// expr.terms should always have at least two terms
|
|
3864
|
+
const last = _.last(expr.terms);
|
|
3807
3865
|
if (trigLog.hints.parens || last.hints.parens || last.has(Trig) || last.has(Log)) {
|
|
3808
3866
|
trigLog.hints.open = false;
|
|
3809
3867
|
} else {
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
newTrigLog = Trig.create([trigLog.type, trigLog.exp], Mul.createOrAppend(trigLog.arg, last).fold());
|
|
3813
|
-
} else {
|
|
3814
|
-
newTrigLog = Log.create(trigLog.base, Mul.createOrAppend(trigLog.power, last).fold());
|
|
3815
|
-
}
|
|
3868
|
+
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());
|
|
3869
|
+
const index = _.indexOf(expr.terms, trigLog);
|
|
3816
3870
|
if (index === 0) {
|
|
3817
3871
|
return newTrigLog;
|
|
3818
3872
|
} else {
|
|
@@ -3822,18 +3876,20 @@ _.extend(Mul, {
|
|
|
3822
3876
|
}
|
|
3823
3877
|
var partitioned = expr.partition();
|
|
3824
3878
|
var numbers = partitioned[0].terms;
|
|
3825
|
-
var pos =
|
|
3879
|
+
var pos = num => {
|
|
3826
3880
|
return num.n > 0;
|
|
3827
3881
|
};
|
|
3828
|
-
var neg =
|
|
3829
|
-
return num.n === -1 && num.hints.negate;
|
|
3882
|
+
var neg = num => {
|
|
3883
|
+
return num.n === -1 && Boolean(num.hints.negate);
|
|
3830
3884
|
};
|
|
3831
3885
|
var posOrNeg = function posOrNeg(num) {
|
|
3832
3886
|
return pos(num) || neg(num);
|
|
3833
3887
|
};
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3888
|
+
const posNum = numbers.find(pos);
|
|
3889
|
+
const negNum = numbers.find(neg);
|
|
3890
|
+
if (numbers.length > 1 && negNum && posNum && _.every(numbers, posOrNeg)) {
|
|
3891
|
+
var firstNeg = _.indexOf(expr.terms, negNum);
|
|
3892
|
+
var firstNum = _.indexOf(expr.terms, posNum);
|
|
3837
3893
|
|
|
3838
3894
|
// e.g. -x*2 -> x*-2
|
|
3839
3895
|
if (firstNeg < firstNum) {
|
|
@@ -3845,20 +3901,31 @@ _.extend(Mul, {
|
|
|
3845
3901
|
// in all other cases, make no change
|
|
3846
3902
|
return expr;
|
|
3847
3903
|
}
|
|
3848
|
-
});
|
|
3849
3904
|
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3905
|
+
// create a new sequence unless left is already one (returns a copy)
|
|
3906
|
+
static createOrAppend(left, right) {
|
|
3907
|
+
if (left instanceof Mul) {
|
|
3908
|
+
return new Mul(left.terms.concat(right));
|
|
3909
|
+
} else {
|
|
3910
|
+
return new Mul(left, right);
|
|
3911
|
+
}
|
|
3912
|
+
}
|
|
3854
3913
|
}
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3914
|
+
|
|
3915
|
+
/* exponentiation */
|
|
3916
|
+
class Pow extends Expr {
|
|
3917
|
+
constructor(base, exp) {
|
|
3918
|
+
super();
|
|
3919
|
+
this.base = void 0;
|
|
3920
|
+
this.exp = void 0;
|
|
3921
|
+
this.func = Pow;
|
|
3922
|
+
this.base = base;
|
|
3923
|
+
this.exp = exp;
|
|
3924
|
+
}
|
|
3925
|
+
args() {
|
|
3859
3926
|
return [this.base, this.exp];
|
|
3860
|
-
}
|
|
3861
|
-
eval
|
|
3927
|
+
}
|
|
3928
|
+
eval(vars = {}, options) {
|
|
3862
3929
|
var evaledBase = this.base.eval(vars, options);
|
|
3863
3930
|
var evaledExp = this.exp.eval(vars, options);
|
|
3864
3931
|
|
|
@@ -3877,7 +3944,7 @@ _.extend(Pow.prototype, {
|
|
|
3877
3944
|
// If Float, convert to a Rational to enable the logic below
|
|
3878
3945
|
if (simplifiedExp instanceof Float) {
|
|
3879
3946
|
var num = simplifiedExp.n;
|
|
3880
|
-
var decimals = (num - num.toFixed()).toString().length - 2;
|
|
3947
|
+
var decimals = (num - +num.toFixed()).toString().length - 2;
|
|
3881
3948
|
var denominator = Math.pow(10, decimals);
|
|
3882
3949
|
var rationalExp = new Rational(num * denominator, denominator);
|
|
3883
3950
|
simplifiedExp = rationalExp.simplify();
|
|
@@ -3892,32 +3959,33 @@ _.extend(Pow.prototype, {
|
|
|
3892
3959
|
}
|
|
3893
3960
|
}
|
|
3894
3961
|
return Math.pow(evaledBase, evaledExp);
|
|
3895
|
-
}
|
|
3896
|
-
getUnits
|
|
3897
|
-
return this.base.getUnits().map(
|
|
3962
|
+
}
|
|
3963
|
+
getUnits() {
|
|
3964
|
+
return this.base.getUnits().map(unit => {
|
|
3898
3965
|
return {
|
|
3899
3966
|
unit: unit.unit,
|
|
3967
|
+
// Exponents in units should always be integers
|
|
3900
3968
|
pow: unit.pow * this.exp.n
|
|
3901
3969
|
};
|
|
3902
|
-
}
|
|
3903
|
-
}
|
|
3904
|
-
codegen
|
|
3970
|
+
});
|
|
3971
|
+
}
|
|
3972
|
+
codegen() {
|
|
3905
3973
|
return "Math.pow(" + this.base.codegen() + ", " + this.exp.codegen() + ")";
|
|
3906
|
-
}
|
|
3907
|
-
print
|
|
3974
|
+
}
|
|
3975
|
+
print() {
|
|
3908
3976
|
var base = this.base.print();
|
|
3909
3977
|
if (this.base instanceof Seq || this.base instanceof Pow) {
|
|
3910
3978
|
base = "(" + base + ")";
|
|
3911
3979
|
}
|
|
3912
3980
|
return base + "^(" + this.exp.print() + ")";
|
|
3913
|
-
}
|
|
3914
|
-
tex
|
|
3981
|
+
}
|
|
3982
|
+
tex() {
|
|
3915
3983
|
if (this.isDivide()) {
|
|
3916
3984
|
// e.g. x ^ -1 w/hint -> 1/x
|
|
3917
3985
|
return "\\frac{1}{" + this.asDivide().tex() + "}";
|
|
3918
|
-
} else if (this.isRoot()) {
|
|
3986
|
+
} else if (this.isRoot() && isRational(this.exp)) {
|
|
3919
3987
|
if (this.exp.n !== 1) {
|
|
3920
|
-
|
|
3988
|
+
throw new Error("Node marked with hint 'root' does not have exponent " + "of form 1/x.");
|
|
3921
3989
|
}
|
|
3922
3990
|
if (this.exp.d === 2) {
|
|
3923
3991
|
// e.g. x ^ 1/2 w/hint -> sqrt{x}
|
|
@@ -3928,9 +3996,7 @@ _.extend(Pow.prototype, {
|
|
|
3928
3996
|
}
|
|
3929
3997
|
} else if (this.base instanceof Trig && !this.base.isInverse() && this.exp instanceof Num && this.exp.isSimple() && this.exp.eval() >= 0) {
|
|
3930
3998
|
// e.g sin(x) ^ 2 -> sin^2(x)
|
|
3931
|
-
var split = this.base.
|
|
3932
|
-
split: true
|
|
3933
|
-
});
|
|
3999
|
+
var split = this.base.texSplit();
|
|
3934
4000
|
return split[0] + "^{" + this.exp.tex() + "}" + split[1];
|
|
3935
4001
|
} else {
|
|
3936
4002
|
// e.g. x ^ y -> x^y
|
|
@@ -3944,16 +4010,16 @@ _.extend(Pow.prototype, {
|
|
|
3944
4010
|
}
|
|
3945
4011
|
return base + "^{" + this.exp.tex() + "}";
|
|
3946
4012
|
}
|
|
3947
|
-
}
|
|
3948
|
-
needsExplicitMul
|
|
4013
|
+
}
|
|
4014
|
+
needsExplicitMul() {
|
|
3949
4015
|
return this.isRoot() ? false : this.base.needsExplicitMul();
|
|
3950
|
-
}
|
|
3951
|
-
expand
|
|
4016
|
+
}
|
|
4017
|
+
expand() {
|
|
3952
4018
|
var pow = this.recurse("expand");
|
|
3953
4019
|
if (pow.base instanceof Mul) {
|
|
3954
4020
|
// e.g. (ab)^c -> a^c*b^c
|
|
3955
4021
|
|
|
3956
|
-
var terms = _.map(pow.base.terms,
|
|
4022
|
+
var terms = _.map(pow.base.terms, term => {
|
|
3957
4023
|
return new Pow(term, pow.exp);
|
|
3958
4024
|
});
|
|
3959
4025
|
return new Mul(terms).expand();
|
|
@@ -3964,49 +4030,55 @@ _.extend(Pow.prototype, {
|
|
|
3964
4030
|
var positive = pow.exp.eval() > 0;
|
|
3965
4031
|
var n = pow.exp.abs().eval();
|
|
3966
4032
|
var signed = function signed(mul) {
|
|
3967
|
-
return positive ? mul : new Pow(mul,
|
|
4033
|
+
return positive ? mul : new Pow(mul, NumDiv);
|
|
3968
4034
|
};
|
|
3969
4035
|
|
|
3970
4036
|
// compute and cache powers of 2 up to n
|
|
3971
|
-
|
|
4037
|
+
const cache = {
|
|
3972
4038
|
1: pow.base
|
|
3973
4039
|
};
|
|
3974
4040
|
for (var i = 2; i <= n; i *= 2) {
|
|
3975
|
-
|
|
3976
|
-
cache[i] =
|
|
4041
|
+
const _mul = new Mul(cache[i / 2], cache[i / 2]);
|
|
4042
|
+
cache[i] = _mul.expand().collect();
|
|
3977
4043
|
}
|
|
3978
4044
|
|
|
3979
4045
|
// if n is a power of 2, you're done!
|
|
3980
|
-
if (
|
|
4046
|
+
if (n in cache) {
|
|
3981
4047
|
return signed(cache[n]);
|
|
3982
4048
|
}
|
|
3983
4049
|
|
|
3984
4050
|
// otherwise decompose n into powers of 2 ...
|
|
3985
|
-
|
|
4051
|
+
let indices = _.map(n.toString(2).split(""), function (str, i, list) {
|
|
3986
4052
|
return Number(str) * Math.pow(2, list.length - i - 1);
|
|
3987
4053
|
});
|
|
3988
4054
|
indices = _.without(indices, 0);
|
|
3989
4055
|
|
|
3990
4056
|
// ... then combine
|
|
3991
|
-
|
|
4057
|
+
const factors = [];
|
|
4058
|
+
for (const index of indices) {
|
|
4059
|
+
if (index in cache) {
|
|
4060
|
+
factors.push(cache[index]);
|
|
4061
|
+
}
|
|
4062
|
+
}
|
|
4063
|
+
const mul = new Mul(factors).expand().collect();
|
|
3992
4064
|
return signed(mul);
|
|
3993
4065
|
} else if (pow.exp instanceof Add) {
|
|
3994
4066
|
// DEFINITELY want behind super-simplify() flag
|
|
3995
4067
|
// e.g. x^(a+b) -> x^a*x^b
|
|
3996
4068
|
|
|
3997
|
-
|
|
4069
|
+
const _terms = _.map(pow.exp.terms, term => {
|
|
3998
4070
|
return new Pow(pow.base, term).expand();
|
|
3999
4071
|
});
|
|
4000
|
-
return new Mul(
|
|
4072
|
+
return new Mul(_terms).expand();
|
|
4001
4073
|
} else {
|
|
4002
4074
|
return pow;
|
|
4003
4075
|
}
|
|
4004
|
-
}
|
|
4005
|
-
factor
|
|
4076
|
+
}
|
|
4077
|
+
factor() {
|
|
4006
4078
|
var pow = this.recurse("factor");
|
|
4007
4079
|
if (pow.base instanceof Mul) {
|
|
4008
|
-
var terms = _.map(pow.base.terms,
|
|
4009
|
-
if (term instanceof Int && pow.exp.equals(
|
|
4080
|
+
var terms = _.map(pow.base.terms, term => {
|
|
4081
|
+
if (term instanceof Int && pow.exp.equals(NumDiv)) {
|
|
4010
4082
|
// Anything that can be a Rational should be a Rational
|
|
4011
4083
|
// e.g. 2^(-1) -> 1/2
|
|
4012
4084
|
return new Rational(1, term.n);
|
|
@@ -4018,23 +4090,23 @@ _.extend(Pow.prototype, {
|
|
|
4018
4090
|
} else {
|
|
4019
4091
|
return pow;
|
|
4020
4092
|
}
|
|
4021
|
-
}
|
|
4022
|
-
collect
|
|
4093
|
+
}
|
|
4094
|
+
collect(options) {
|
|
4023
4095
|
if (this.base instanceof Pow) {
|
|
4024
4096
|
// collect this first to avoid having to deal with float precision
|
|
4025
4097
|
// e.g. sqrt(2)^2 -> 2, not 2.0000000000000004
|
|
4026
4098
|
// e.g. (x^y)^z -> x^(yz)
|
|
4027
|
-
|
|
4028
|
-
|
|
4099
|
+
const base = this.base.base;
|
|
4100
|
+
const exp = Mul.createOrAppend(this.base.exp, this.exp);
|
|
4029
4101
|
return new Pow(base, exp).collect(options);
|
|
4030
4102
|
}
|
|
4031
|
-
|
|
4032
|
-
|
|
4103
|
+
const pow = this.recurse("collect", options);
|
|
4104
|
+
const isSimilarLog = function isSimilarLog(term) {
|
|
4033
4105
|
return term instanceof Log && term.base.equals(pow.base);
|
|
4034
4106
|
};
|
|
4035
4107
|
if (pow.exp instanceof Num && pow.exp.eval() === 0) {
|
|
4036
4108
|
// e.g. x^0 -> 1
|
|
4037
|
-
return
|
|
4109
|
+
return NumOne;
|
|
4038
4110
|
} else if (pow.exp instanceof Num && pow.exp.eval() === 1) {
|
|
4039
4111
|
// e.g. x^1 -> x
|
|
4040
4112
|
return pow.base;
|
|
@@ -4043,9 +4115,11 @@ _.extend(Pow.prototype, {
|
|
|
4043
4115
|
return pow.exp.power;
|
|
4044
4116
|
} else if (pow.exp instanceof Mul && _.any(pow.exp.terms, isSimilarLog)) {
|
|
4045
4117
|
// e.g. b^(2*y*log_b(x)) -> x^(2*y)
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4118
|
+
// `log` will always be defined here because of the
|
|
4119
|
+
// `_.any(pow.exp.terms, isSimilarLog)` check above.
|
|
4120
|
+
const log = pow.exp.terms.find(isSimilarLog);
|
|
4121
|
+
const base = log.power;
|
|
4122
|
+
const exp = pow.exp.remove(log).flatten();
|
|
4049
4123
|
return new Pow(base, exp).collect(options);
|
|
4050
4124
|
} else if (pow.base instanceof Num && pow.exp instanceof Num) {
|
|
4051
4125
|
// TODO(alex): Consider encapsualting this logic (and similar logic
|
|
@@ -4060,14 +4134,18 @@ _.extend(Pow.prototype, {
|
|
|
4060
4134
|
// as floats, but ideally rationals should be pre-processed
|
|
4061
4135
|
// e.g. (1/27)^(1/3) -> 1/3 to avoid most cases.
|
|
4062
4136
|
// TODO(alex): Catch such cases and avoid converting to floats.
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4137
|
+
const exp = pow.exp.asRational();
|
|
4138
|
+
const decimalsInBase = pow.base.getDecimalPlaces();
|
|
4139
|
+
const root = new Pow(pow.base, new Rational(1, exp.d));
|
|
4140
|
+
const decimalsInRoot = root.collect()
|
|
4141
|
+
// @ts-expect-error: we assume that `root.collect()` returns
|
|
4142
|
+
// a Num here but tbh I'm not sure how this code isn't causing
|
|
4143
|
+
// an infinite loop.
|
|
4144
|
+
.getDecimalPlaces();
|
|
4067
4145
|
if (decimalsInRoot > decimalsInBase) {
|
|
4068
4146
|
// Collecting over this denominator would result in an
|
|
4069
4147
|
// imprecise float, so avoid doing so.
|
|
4070
|
-
|
|
4148
|
+
const newBase = new Pow(pow.base, new Int(exp.n)).collect();
|
|
4071
4149
|
return new Pow(newBase, new Rational(1, exp.d));
|
|
4072
4150
|
}
|
|
4073
4151
|
}
|
|
@@ -4077,16 +4155,18 @@ _.extend(Pow.prototype, {
|
|
|
4077
4155
|
} else {
|
|
4078
4156
|
return pow;
|
|
4079
4157
|
}
|
|
4080
|
-
}
|
|
4158
|
+
}
|
|
4159
|
+
|
|
4081
4160
|
// checks whether this Pow represents user-entered division
|
|
4082
|
-
isDivide
|
|
4161
|
+
isDivide() {
|
|
4083
4162
|
var isDiv = function isDiv(arg) {
|
|
4084
|
-
return arg instanceof Num && arg.hints.divide;
|
|
4163
|
+
return arg instanceof Num && Boolean(arg.hints.divide);
|
|
4085
4164
|
};
|
|
4086
4165
|
return isDiv(this.exp) || this.exp instanceof Mul && _.any(this.exp.terms, isDiv);
|
|
4087
|
-
}
|
|
4166
|
+
}
|
|
4167
|
+
|
|
4088
4168
|
// assuming this Pow represents user-entered division, returns the denominator
|
|
4089
|
-
asDivide
|
|
4169
|
+
asDivide() {
|
|
4090
4170
|
if (this.exp instanceof Num) {
|
|
4091
4171
|
if (this.exp.eval() === -1) {
|
|
4092
4172
|
return this.base;
|
|
@@ -4099,18 +4179,19 @@ _.extend(Pow.prototype, {
|
|
|
4099
4179
|
} else if (this.exp instanceof Mul) {
|
|
4100
4180
|
return new Pow(this.base, this.exp.factorOut());
|
|
4101
4181
|
} else {
|
|
4102
|
-
|
|
4182
|
+
throw new Error("called asDivide() on an Expr that wasn't a Num or Mul");
|
|
4103
4183
|
}
|
|
4104
|
-
}
|
|
4105
|
-
isRoot
|
|
4106
|
-
return this.exp instanceof Rational && this.exp.hints.root;
|
|
4107
|
-
}
|
|
4108
|
-
isSquaredTrig
|
|
4184
|
+
}
|
|
4185
|
+
isRoot() {
|
|
4186
|
+
return this.exp instanceof Rational && Boolean(this.exp.hints.root);
|
|
4187
|
+
}
|
|
4188
|
+
isSquaredTrig() {
|
|
4109
4189
|
return this.base instanceof Trig && !this.base.isInverse() && this.exp instanceof Num && this.exp.eval() === 2;
|
|
4110
|
-
}
|
|
4190
|
+
}
|
|
4191
|
+
|
|
4111
4192
|
// extract whatever denominator makes sense, ignoring hints
|
|
4112
4193
|
// if negative exponent, will recursively include the base's denominator as well
|
|
4113
|
-
getDenominator
|
|
4194
|
+
getDenominator() {
|
|
4114
4195
|
if (this.exp instanceof Num && this.exp.eval() === -1) {
|
|
4115
4196
|
return Mul.createOrAppend(this.base, this.base.getDenominator()).flatten();
|
|
4116
4197
|
} else if (this.exp.isNegative()) {
|
|
@@ -4119,18 +4200,11 @@ _.extend(Pow.prototype, {
|
|
|
4119
4200
|
} else if (this.base instanceof Num) {
|
|
4120
4201
|
return new Pow(this.base.getDenominator(), this.exp).collect();
|
|
4121
4202
|
} else {
|
|
4122
|
-
return
|
|
4123
|
-
}
|
|
4124
|
-
},
|
|
4125
|
-
findGCD: function (factor) {
|
|
4126
|
-
var base, exp;
|
|
4127
|
-
if (factor instanceof Pow) {
|
|
4128
|
-
base = factor.base;
|
|
4129
|
-
exp = factor.exp;
|
|
4130
|
-
} else {
|
|
4131
|
-
base = factor;
|
|
4132
|
-
exp = Num.One;
|
|
4203
|
+
return NumOne;
|
|
4133
4204
|
}
|
|
4205
|
+
}
|
|
4206
|
+
findGCD(factor) {
|
|
4207
|
+
const [base, exp] = factor instanceof Pow ? [factor.base, factor.exp] : [factor, NumOne];
|
|
4134
4208
|
|
|
4135
4209
|
// GCD is only relevant if same base
|
|
4136
4210
|
if (this.base.equals(base)) {
|
|
@@ -4145,7 +4219,7 @@ _.extend(Pow.prototype, {
|
|
|
4145
4219
|
} else if (this.exp instanceof Num || exp instanceof Num) {
|
|
4146
4220
|
// one numerical exponent
|
|
4147
4221
|
// e.g. GCD(x^2, x^y) -> 1
|
|
4148
|
-
return
|
|
4222
|
+
return NumOne;
|
|
4149
4223
|
}
|
|
4150
4224
|
var expA = this.exp.asMul().partition();
|
|
4151
4225
|
var expB = exp.asMul().partition();
|
|
@@ -4157,16 +4231,16 @@ _.extend(Pow.prototype, {
|
|
|
4157
4231
|
return new Pow(base, mul).collect();
|
|
4158
4232
|
}
|
|
4159
4233
|
}
|
|
4160
|
-
return
|
|
4161
|
-
}
|
|
4162
|
-
isPositive
|
|
4234
|
+
return NumOne;
|
|
4235
|
+
}
|
|
4236
|
+
isPositive() {
|
|
4163
4237
|
if (this.base.isPositive()) {
|
|
4164
4238
|
return true;
|
|
4165
4239
|
}
|
|
4166
4240
|
var exp = this.exp.simplify();
|
|
4167
4241
|
return exp instanceof Int && exp.eval() % 2 === 0;
|
|
4168
|
-
}
|
|
4169
|
-
asPositiveFactor
|
|
4242
|
+
}
|
|
4243
|
+
asPositiveFactor() {
|
|
4170
4244
|
if (this.isPositive()) {
|
|
4171
4245
|
return this;
|
|
4172
4246
|
} else {
|
|
@@ -4181,78 +4255,87 @@ _.extend(Pow.prototype, {
|
|
|
4181
4255
|
return new Pow(this.base, new Int(n + 1));
|
|
4182
4256
|
}
|
|
4183
4257
|
}
|
|
4184
|
-
return
|
|
4258
|
+
return NumOne;
|
|
4185
4259
|
}
|
|
4186
4260
|
}
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
sqrt: function (arg) {
|
|
4190
|
-
return new Pow(arg, Num.Sqrt);
|
|
4191
|
-
},
|
|
4192
|
-
nthroot: function (radicand, degree) {
|
|
4193
|
-
var exp = Mul.fold(Mul.handleDivide(new Int(1), degree));
|
|
4194
|
-
|
|
4195
|
-
// FIXME(johnsullivan): If oneOverDegree ends up being a pow object,
|
|
4196
|
-
// this "root" hint is lost between here and when tex() is called.
|
|
4197
|
-
return new Pow(radicand, exp.addHint("root"));
|
|
4261
|
+
static sqrt(arg) {
|
|
4262
|
+
return new Pow(arg, NumSqrt);
|
|
4198
4263
|
}
|
|
4199
|
-
});
|
|
4200
4264
|
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4265
|
+
// NOTE(kevinb): nthroot is used as a constructor so we need to
|
|
4266
|
+
// define it as a static property instead of a static method.
|
|
4267
|
+
// TODO(kevinb): update parser-generator.ts to call nthrooth
|
|
4268
|
+
// without using `new`.
|
|
4205
4269
|
}
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4270
|
+
|
|
4271
|
+
/* logarithm */
|
|
4272
|
+
_class5 = Pow;
|
|
4273
|
+
Pow.nthroot = function (radicand, degree) {
|
|
4274
|
+
var exp = Mul.fold(Mul.handleDivide(new Int(1), degree));
|
|
4275
|
+
|
|
4276
|
+
// FIXME(johnsullivan): If oneOverDegree ends up being a pow object,
|
|
4277
|
+
// this "root" hint is lost between here and when tex() is called.
|
|
4278
|
+
return new _class5(radicand, exp.addHint("root"));
|
|
4279
|
+
};
|
|
4280
|
+
class Log extends Expr {
|
|
4281
|
+
constructor(base, power) {
|
|
4282
|
+
super();
|
|
4283
|
+
this.base = void 0;
|
|
4284
|
+
this.power = void 0;
|
|
4285
|
+
this.func = Log;
|
|
4286
|
+
this.base = base;
|
|
4287
|
+
this.power = power;
|
|
4288
|
+
this.hints = _extends({}, this.hints, {
|
|
4289
|
+
open: false
|
|
4290
|
+
});
|
|
4291
|
+
}
|
|
4292
|
+
args() {
|
|
4210
4293
|
return [this.base, this.power];
|
|
4211
|
-
}
|
|
4212
|
-
eval
|
|
4294
|
+
}
|
|
4295
|
+
eval(vars = {}, options) {
|
|
4213
4296
|
return Math.log(this.power.eval(vars, options)) / Math.log(this.base.eval(vars, options));
|
|
4214
|
-
}
|
|
4215
|
-
codegen
|
|
4297
|
+
}
|
|
4298
|
+
codegen() {
|
|
4216
4299
|
return "(Math.log(" + this.power.codegen() + ") / Math.log(" + this.base.codegen() + "))";
|
|
4217
|
-
}
|
|
4218
|
-
print
|
|
4300
|
+
}
|
|
4301
|
+
print() {
|
|
4219
4302
|
var power = "(" + this.power.print() + ")";
|
|
4220
4303
|
if (this.isNatural()) {
|
|
4221
4304
|
return "ln" + power;
|
|
4222
4305
|
} else {
|
|
4223
4306
|
return "log_(" + this.base.print() + ") " + power;
|
|
4224
4307
|
}
|
|
4225
|
-
}
|
|
4226
|
-
tex
|
|
4308
|
+
}
|
|
4309
|
+
tex() {
|
|
4227
4310
|
var power = "(" + this.power.tex() + ")";
|
|
4228
4311
|
if (this.isNatural()) {
|
|
4229
4312
|
return "\\ln" + power;
|
|
4230
4313
|
} else {
|
|
4231
4314
|
return "\\log_{" + this.base.tex() + "}" + power;
|
|
4232
4315
|
}
|
|
4233
|
-
}
|
|
4234
|
-
collect
|
|
4316
|
+
}
|
|
4317
|
+
collect(options) {
|
|
4235
4318
|
var log = this.recurse("collect", options);
|
|
4236
4319
|
if (log.power instanceof Num && log.power.eval() === 1) {
|
|
4237
4320
|
// e.g. ln(1) -> 0
|
|
4238
|
-
return
|
|
4321
|
+
return NumZero;
|
|
4239
4322
|
} else if (log.base.equals(log.power)) {
|
|
4240
4323
|
// e.g. log_b(b) -> 1
|
|
4241
|
-
return
|
|
4324
|
+
return NumOne;
|
|
4242
4325
|
} else if (log.power instanceof Pow && log.power.base.equals(log.base)) {
|
|
4243
4326
|
// e.g. log_b(b^x) -> x
|
|
4244
4327
|
return log.power.exp;
|
|
4245
4328
|
} else {
|
|
4246
4329
|
return log;
|
|
4247
4330
|
}
|
|
4248
|
-
}
|
|
4249
|
-
expand
|
|
4331
|
+
}
|
|
4332
|
+
expand() {
|
|
4250
4333
|
var log = this.recurse("expand");
|
|
4251
4334
|
if (log.power instanceof Mul) {
|
|
4252
4335
|
// might want behind super-simplify() flag
|
|
4253
4336
|
// e.g. ln(xy) -> ln(x) + ln(y)
|
|
4254
4337
|
|
|
4255
|
-
var terms = _.map(log.power.terms,
|
|
4338
|
+
var terms = _.map(log.power.terms, term => {
|
|
4256
4339
|
// need to expand again in case new log powers are Pows
|
|
4257
4340
|
return new Log(log.base, term).expand();
|
|
4258
4341
|
});
|
|
@@ -4268,231 +4351,209 @@ _.extend(Log.prototype, {
|
|
|
4268
4351
|
} else {
|
|
4269
4352
|
return log;
|
|
4270
4353
|
}
|
|
4271
|
-
}
|
|
4272
|
-
|
|
4273
|
-
open: false
|
|
4274
|
-
}),
|
|
4275
|
-
isPositive: function () {
|
|
4354
|
+
}
|
|
4355
|
+
isPositive() {
|
|
4276
4356
|
var log = this.collect();
|
|
4277
|
-
if (log.base instanceof Num && log.power instanceof Num) {
|
|
4357
|
+
if (log instanceof Log && log.base instanceof Num && log.power instanceof Num) {
|
|
4278
4358
|
return this.eval() > 0;
|
|
4279
4359
|
} else {
|
|
4280
4360
|
return false;
|
|
4281
4361
|
}
|
|
4282
|
-
}
|
|
4283
|
-
needsExplicitMul
|
|
4362
|
+
}
|
|
4363
|
+
needsExplicitMul() {
|
|
4284
4364
|
return false;
|
|
4285
|
-
}
|
|
4286
|
-
isNatural
|
|
4365
|
+
}
|
|
4366
|
+
isNatural() {
|
|
4287
4367
|
return this.base.equals(Const.e);
|
|
4288
4368
|
}
|
|
4289
|
-
|
|
4290
|
-
_.extend(Log, {
|
|
4291
|
-
natural: function () {
|
|
4369
|
+
static natural() {
|
|
4292
4370
|
return Const.e;
|
|
4293
|
-
}
|
|
4294
|
-
common
|
|
4295
|
-
return
|
|
4296
|
-
}
|
|
4297
|
-
create
|
|
4371
|
+
}
|
|
4372
|
+
static common() {
|
|
4373
|
+
return NumTen;
|
|
4374
|
+
}
|
|
4375
|
+
static create(base, power) {
|
|
4298
4376
|
var log = new Log(base, power);
|
|
4299
4377
|
if (!power.hints.parens) {
|
|
4300
4378
|
log = log.addHint("open");
|
|
4301
4379
|
}
|
|
4302
4380
|
return log;
|
|
4303
4381
|
}
|
|
4304
|
-
});
|
|
4305
|
-
|
|
4306
|
-
/* trigonometric functions */
|
|
4307
|
-
function Trig(type, arg) {
|
|
4308
|
-
this.type = type;
|
|
4309
|
-
this.arg = arg;
|
|
4310
4382
|
}
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
eval: Math.cos,
|
|
4328
|
-
codegen: "Math.cos((",
|
|
4329
|
-
tex: "\\cos",
|
|
4330
|
-
expand: function () {
|
|
4331
|
-
return this;
|
|
4332
|
-
}
|
|
4333
|
-
},
|
|
4334
|
-
tan: {
|
|
4335
|
-
eval: Math.tan,
|
|
4336
|
-
codegen: "Math.tan((",
|
|
4337
|
-
tex: "\\tan",
|
|
4338
|
-
expand: function () {
|
|
4339
|
-
return Mul.handleDivide(Trig.sin(this.arg), Trig.cos(this.arg));
|
|
4340
|
-
}
|
|
4341
|
-
},
|
|
4342
|
-
csc: {
|
|
4343
|
-
eval: function (arg) {
|
|
4344
|
-
return 1 / Math.sin(arg);
|
|
4383
|
+
/* trigonometric functions */
|
|
4384
|
+
class Trig extends Expr {
|
|
4385
|
+
constructor(type, _arg) {
|
|
4386
|
+
super();
|
|
4387
|
+
this.type = void 0;
|
|
4388
|
+
// TODO(kevinb): Use a union type for this
|
|
4389
|
+
this.arg = void 0;
|
|
4390
|
+
this.exp = void 0;
|
|
4391
|
+
this.func = Trig;
|
|
4392
|
+
// TODO(kevinb): Use union type for the function names.
|
|
4393
|
+
this.functions = {
|
|
4394
|
+
sin: {
|
|
4395
|
+
eval: Math.sin,
|
|
4396
|
+
codegen: "Math.sin((",
|
|
4397
|
+
tex: "\\sin",
|
|
4398
|
+
expand: () => this
|
|
4345
4399
|
},
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
},
|
|
4352
|
-
sec: {
|
|
4353
|
-
eval: function (arg) {
|
|
4354
|
-
return 1 / Math.cos(arg);
|
|
4400
|
+
cos: {
|
|
4401
|
+
eval: Math.cos,
|
|
4402
|
+
codegen: "Math.cos((",
|
|
4403
|
+
tex: "\\cos",
|
|
4404
|
+
expand: () => this
|
|
4355
4405
|
},
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
},
|
|
4362
|
-
cot: {
|
|
4363
|
-
eval: function (arg) {
|
|
4364
|
-
return 1 / Math.tan(arg);
|
|
4365
|
-
},
|
|
4366
|
-
codegen: "(1/Math.tan(",
|
|
4367
|
-
tex: "\\cot",
|
|
4368
|
-
expand: function () {
|
|
4369
|
-
return Mul.handleDivide(Trig.cos(this.arg), Trig.sin(this.arg));
|
|
4370
|
-
}
|
|
4371
|
-
},
|
|
4372
|
-
arcsin: {
|
|
4373
|
-
eval: Math.asin,
|
|
4374
|
-
codegen: "Math.asin((",
|
|
4375
|
-
tex: "\\arcsin"
|
|
4376
|
-
},
|
|
4377
|
-
arccos: {
|
|
4378
|
-
eval: Math.acos,
|
|
4379
|
-
codegen: "Math.acos((",
|
|
4380
|
-
tex: "\\arccos"
|
|
4381
|
-
},
|
|
4382
|
-
arctan: {
|
|
4383
|
-
eval: Math.atan,
|
|
4384
|
-
codegen: "Math.atan((",
|
|
4385
|
-
tex: "\\arctan"
|
|
4386
|
-
},
|
|
4387
|
-
arccsc: {
|
|
4388
|
-
eval: function (arg) {
|
|
4389
|
-
return Math.asin(1 / arg);
|
|
4406
|
+
tan: {
|
|
4407
|
+
eval: Math.tan,
|
|
4408
|
+
codegen: "Math.tan((",
|
|
4409
|
+
tex: "\\tan",
|
|
4410
|
+
expand: () => Mul.handleDivide(Trig.sin(this.arg), Trig.cos(this.arg))
|
|
4390
4411
|
},
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4412
|
+
csc: {
|
|
4413
|
+
eval: arg => {
|
|
4414
|
+
return 1 / Math.sin(arg);
|
|
4415
|
+
},
|
|
4416
|
+
codegen: "(1/Math.sin(",
|
|
4417
|
+
tex: "\\csc",
|
|
4418
|
+
expand: () => Mul.handleDivide(NumOne, Trig.sin(this.arg))
|
|
4397
4419
|
},
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4420
|
+
sec: {
|
|
4421
|
+
eval: arg => {
|
|
4422
|
+
return 1 / Math.cos(arg);
|
|
4423
|
+
},
|
|
4424
|
+
codegen: "(1/Math.cos(",
|
|
4425
|
+
tex: "\\sec",
|
|
4426
|
+
expand: () => Mul.handleDivide(NumOne, Trig.cos(this.arg))
|
|
4404
4427
|
},
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4428
|
+
cot: {
|
|
4429
|
+
eval: arg => {
|
|
4430
|
+
return 1 / Math.tan(arg);
|
|
4431
|
+
},
|
|
4432
|
+
codegen: "(1/Math.tan(",
|
|
4433
|
+
tex: "\\cot",
|
|
4434
|
+
expand: () => Mul.handleDivide(Trig.cos(this.arg), Trig.sin(this.arg))
|
|
4411
4435
|
},
|
|
4412
|
-
|
|
4413
|
-
|
|
4436
|
+
arcsin: {
|
|
4437
|
+
eval: Math.asin,
|
|
4438
|
+
codegen: "Math.asin((",
|
|
4439
|
+
tex: "\\arcsin"
|
|
4414
4440
|
},
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
},
|
|
4420
|
-
cosh: {
|
|
4421
|
-
eval: function (arg) {
|
|
4422
|
-
return (Math.exp(arg) + Math.exp(-arg)) / 2;
|
|
4441
|
+
arccos: {
|
|
4442
|
+
eval: Math.acos,
|
|
4443
|
+
codegen: "Math.acos((",
|
|
4444
|
+
tex: "\\arccos"
|
|
4423
4445
|
},
|
|
4424
|
-
|
|
4425
|
-
|
|
4446
|
+
arctan: {
|
|
4447
|
+
eval: Math.atan,
|
|
4448
|
+
codegen: "Math.atan((",
|
|
4449
|
+
tex: "\\arctan"
|
|
4426
4450
|
},
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
eval: function (arg) {
|
|
4434
|
-
return (Math.exp(arg) - Math.exp(-arg)) / (Math.exp(arg) + Math.exp(-arg));
|
|
4451
|
+
arccsc: {
|
|
4452
|
+
eval: arg => {
|
|
4453
|
+
return Math.asin(1 / arg);
|
|
4454
|
+
},
|
|
4455
|
+
codegen: "Math.asin(1/(",
|
|
4456
|
+
tex: "\\operatorname{arccsc}"
|
|
4435
4457
|
},
|
|
4436
|
-
|
|
4437
|
-
|
|
4458
|
+
arcsec: {
|
|
4459
|
+
eval: arg => {
|
|
4460
|
+
return Math.acos(1 / arg);
|
|
4461
|
+
},
|
|
4462
|
+
codegen: "Math.acos(1/(",
|
|
4463
|
+
tex: "\\operatorname{arcsec}"
|
|
4438
4464
|
},
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
eval: function (arg) {
|
|
4446
|
-
return 2 / (Math.exp(arg) - Math.exp(-arg));
|
|
4465
|
+
arccot: {
|
|
4466
|
+
eval: arg => {
|
|
4467
|
+
return Math.atan(1 / arg);
|
|
4468
|
+
},
|
|
4469
|
+
codegen: "Math.atan(1/(",
|
|
4470
|
+
tex: "\\operatorname{arccot}"
|
|
4447
4471
|
},
|
|
4448
|
-
|
|
4449
|
-
|
|
4472
|
+
sinh: {
|
|
4473
|
+
eval: arg => {
|
|
4474
|
+
return (Math.exp(arg) - Math.exp(-arg)) / 2;
|
|
4475
|
+
},
|
|
4476
|
+
codegen: argStr => {
|
|
4477
|
+
return "((Math.exp(" + argStr + ") - Math.exp(-(" + argStr + "))) / 2)";
|
|
4478
|
+
},
|
|
4479
|
+
tex: "\\sinh",
|
|
4480
|
+
expand: () => this
|
|
4450
4481
|
},
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4482
|
+
cosh: {
|
|
4483
|
+
eval: arg => {
|
|
4484
|
+
return (Math.exp(arg) + Math.exp(-arg)) / 2;
|
|
4485
|
+
},
|
|
4486
|
+
codegen: argStr => {
|
|
4487
|
+
return "((Math.exp(" + argStr + ") + Math.exp(-(" + argStr + "))) / 2)";
|
|
4488
|
+
},
|
|
4489
|
+
tex: "\\cosh",
|
|
4490
|
+
expand: () => this
|
|
4459
4491
|
},
|
|
4460
|
-
|
|
4461
|
-
|
|
4492
|
+
tanh: {
|
|
4493
|
+
eval: arg => {
|
|
4494
|
+
return (Math.exp(arg) - Math.exp(-arg)) / (Math.exp(arg) + Math.exp(-arg));
|
|
4495
|
+
},
|
|
4496
|
+
codegen: argStr => {
|
|
4497
|
+
return "(" + "(Math.exp(" + argStr + ") - Math.exp(-(" + argStr + ")))" + " / " + "(Math.exp(" + argStr + ") + Math.exp(-(" + argStr + ")))" + ")";
|
|
4498
|
+
},
|
|
4499
|
+
tex: "\\tanh",
|
|
4500
|
+
expand: () => Mul.handleDivide(Trig.sinh(this.arg), Trig.cosh(this.arg))
|
|
4462
4501
|
},
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4502
|
+
csch: {
|
|
4503
|
+
eval: arg => {
|
|
4504
|
+
return 2 / (Math.exp(arg) - Math.exp(-arg));
|
|
4505
|
+
},
|
|
4506
|
+
codegen: argStr => {
|
|
4507
|
+
return "(2 / (Math.exp(" + argStr + ") - Math.exp(-(" + argStr + "))))";
|
|
4508
|
+
},
|
|
4509
|
+
tex: "\\csch",
|
|
4510
|
+
expand: () => Mul.handleDivide(NumOne, Trig.sinh(this.arg))
|
|
4471
4511
|
},
|
|
4472
|
-
|
|
4473
|
-
|
|
4512
|
+
sech: {
|
|
4513
|
+
eval: arg => {
|
|
4514
|
+
return 2 / (Math.exp(arg) + Math.exp(-arg));
|
|
4515
|
+
},
|
|
4516
|
+
codegen: argStr => {
|
|
4517
|
+
return "(2 / (Math.exp(" + argStr + ") + Math.exp(-(" + argStr + "))))";
|
|
4518
|
+
},
|
|
4519
|
+
tex: "\\sech",
|
|
4520
|
+
expand: () => Mul.handleDivide(NumOne, Trig.cosh(this.arg))
|
|
4474
4521
|
},
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4522
|
+
coth: {
|
|
4523
|
+
eval: arg => {
|
|
4524
|
+
return (Math.exp(arg) + Math.exp(-arg)) / (Math.exp(arg) - Math.exp(-arg));
|
|
4525
|
+
},
|
|
4526
|
+
codegen: argStr => {
|
|
4527
|
+
return "(" + "(Math.exp(" + argStr + ") + Math.exp(-(" + argStr + ")))" + " / " + "(Math.exp(" + argStr + ") - Math.exp(-(" + argStr + ")))" + ")";
|
|
4528
|
+
},
|
|
4529
|
+
tex: "\\coth",
|
|
4530
|
+
expand: () => Mul.handleDivide(Trig.cosh(this.arg), Trig.sinh(this.arg))
|
|
4478
4531
|
}
|
|
4479
|
-
}
|
|
4480
|
-
|
|
4481
|
-
|
|
4532
|
+
};
|
|
4533
|
+
this.type = type;
|
|
4534
|
+
this.arg = _arg;
|
|
4535
|
+
this.hints = _extends({}, this.hints, {
|
|
4536
|
+
open: false
|
|
4537
|
+
});
|
|
4538
|
+
}
|
|
4539
|
+
args() {
|
|
4540
|
+
return [this.type, this.arg];
|
|
4541
|
+
}
|
|
4542
|
+
isEven() {
|
|
4482
4543
|
return _.contains(["cos", "sec"], this.type);
|
|
4483
|
-
}
|
|
4484
|
-
isInverse
|
|
4544
|
+
}
|
|
4545
|
+
isInverse() {
|
|
4485
4546
|
return this.type.indexOf("arc") === 0;
|
|
4486
|
-
}
|
|
4487
|
-
isBasic
|
|
4547
|
+
}
|
|
4548
|
+
isBasic() {
|
|
4488
4549
|
return _.contains(["sin", "cos"], this.type);
|
|
4489
|
-
}
|
|
4490
|
-
eval
|
|
4550
|
+
}
|
|
4551
|
+
eval(vars = {}, options) {
|
|
4491
4552
|
var func = this.functions[this.type].eval;
|
|
4492
4553
|
var arg = this.arg.eval(vars, options);
|
|
4493
4554
|
return func(arg);
|
|
4494
|
-
}
|
|
4495
|
-
codegen
|
|
4555
|
+
}
|
|
4556
|
+
codegen() {
|
|
4496
4557
|
var func = this.functions[this.type].codegen;
|
|
4497
4558
|
if (typeof func === "function") {
|
|
4498
4559
|
return func(this.arg.codegen());
|
|
@@ -4501,27 +4562,29 @@ _.extend(Trig.prototype, {
|
|
|
4501
4562
|
} else {
|
|
4502
4563
|
throw new Error("codegen not implemented for " + this.type);
|
|
4503
4564
|
}
|
|
4504
|
-
}
|
|
4505
|
-
print
|
|
4565
|
+
}
|
|
4566
|
+
print() {
|
|
4506
4567
|
return this.type + "(" + this.arg.print() + ")";
|
|
4507
|
-
}
|
|
4508
|
-
tex
|
|
4568
|
+
}
|
|
4569
|
+
tex() {
|
|
4570
|
+
var func = this.functions[this.type].tex;
|
|
4571
|
+
var arg = "(" + this.arg.tex() + ")";
|
|
4572
|
+
return func + arg;
|
|
4573
|
+
}
|
|
4574
|
+
texSplit() {
|
|
4509
4575
|
var func = this.functions[this.type].tex;
|
|
4510
4576
|
var arg = "(" + this.arg.tex() + ")";
|
|
4511
|
-
return
|
|
4512
|
-
}
|
|
4513
|
-
|
|
4514
|
-
open: false
|
|
4515
|
-
}),
|
|
4516
|
-
isPositive: function () {
|
|
4577
|
+
return [func, arg];
|
|
4578
|
+
}
|
|
4579
|
+
isPositive() {
|
|
4517
4580
|
var trig = this.collect();
|
|
4518
|
-
if (trig.arg instanceof Num) {
|
|
4581
|
+
if (trig instanceof Trig && trig.arg instanceof Num) {
|
|
4519
4582
|
return this.eval() > 0;
|
|
4520
4583
|
} else {
|
|
4521
4584
|
return false;
|
|
4522
4585
|
}
|
|
4523
|
-
}
|
|
4524
|
-
completeParse
|
|
4586
|
+
}
|
|
4587
|
+
completeParse() {
|
|
4525
4588
|
if (this.exp) {
|
|
4526
4589
|
var pow = new Pow(this, this.exp);
|
|
4527
4590
|
this.exp = undefined;
|
|
@@ -4529,47 +4592,42 @@ _.extend(Trig.prototype, {
|
|
|
4529
4592
|
} else {
|
|
4530
4593
|
return this;
|
|
4531
4594
|
}
|
|
4532
|
-
}
|
|
4595
|
+
}
|
|
4596
|
+
|
|
4533
4597
|
// TODO(alex): does every new node type need to redefine these?
|
|
4534
|
-
needsExplicitMul
|
|
4598
|
+
needsExplicitMul() {
|
|
4535
4599
|
return false;
|
|
4536
|
-
}
|
|
4537
|
-
expand
|
|
4600
|
+
}
|
|
4601
|
+
expand() {
|
|
4538
4602
|
var trig = this.recurse("expand");
|
|
4539
4603
|
if (!trig.isInverse()) {
|
|
4540
4604
|
// e.g. tan(x) -> sin(x)/cos(x)
|
|
4605
|
+
// NOTE(kevinb): All non-inverse trig functions have an expand property.
|
|
4541
4606
|
var expand = trig.functions[trig.type].expand;
|
|
4542
4607
|
return _.bind(expand, trig)();
|
|
4543
4608
|
} else {
|
|
4544
4609
|
return trig;
|
|
4545
4610
|
}
|
|
4546
|
-
}
|
|
4547
|
-
collect
|
|
4611
|
+
}
|
|
4612
|
+
collect(options) {
|
|
4548
4613
|
var trig = this.recurse("collect", options);
|
|
4549
4614
|
if (!trig.isInverse() && trig.arg.isNegative()) {
|
|
4550
|
-
|
|
4551
|
-
if (trig.arg instanceof Num) {
|
|
4552
|
-
arg = trig.arg.abs();
|
|
4553
|
-
} else {
|
|
4554
|
-
arg = Mul.handleDivide(trig.arg, Num.Neg).collect(options);
|
|
4555
|
-
}
|
|
4615
|
+
const arg = trig.arg instanceof Num ? trig.arg.abs() : Mul.handleDivide(trig.arg, NumNeg).collect(options);
|
|
4556
4616
|
if (trig.isEven()) {
|
|
4557
4617
|
// e.g. cos(-x) -> cos(x)
|
|
4558
4618
|
return new Trig(trig.type, arg);
|
|
4559
4619
|
} else {
|
|
4560
4620
|
// e.g. sin(-x) -> -sin(x)
|
|
4561
|
-
return new Mul(
|
|
4621
|
+
return new Mul(NumNeg, new Trig(trig.type, arg));
|
|
4562
4622
|
}
|
|
4563
4623
|
} else {
|
|
4564
4624
|
return trig;
|
|
4565
4625
|
}
|
|
4566
4626
|
}
|
|
4567
|
-
|
|
4568
|
-
_.extend(Trig, {
|
|
4569
|
-
create: function (pair, arg) {
|
|
4627
|
+
static create(pair, arg) {
|
|
4570
4628
|
var type = pair[0];
|
|
4571
4629
|
var exp = pair[1];
|
|
4572
|
-
if (exp && exp.equals(
|
|
4630
|
+
if (exp && exp.equals(NumNeg)) {
|
|
4573
4631
|
// e.g. sin^-1(x) -> arcsin(x)
|
|
4574
4632
|
type = "arc" + type;
|
|
4575
4633
|
exp = undefined;
|
|
@@ -4582,42 +4640,43 @@ _.extend(Trig, {
|
|
|
4582
4640
|
trig.exp = exp;
|
|
4583
4641
|
}
|
|
4584
4642
|
return trig;
|
|
4585
|
-
}
|
|
4586
|
-
sin
|
|
4643
|
+
}
|
|
4644
|
+
static sin(arg) {
|
|
4587
4645
|
return new Trig("sin", arg);
|
|
4588
|
-
}
|
|
4589
|
-
cos
|
|
4646
|
+
}
|
|
4647
|
+
static cos(arg) {
|
|
4590
4648
|
return new Trig("cos", arg);
|
|
4591
|
-
}
|
|
4592
|
-
sinh
|
|
4649
|
+
}
|
|
4650
|
+
static sinh(arg) {
|
|
4593
4651
|
return new Trig("sinh", arg);
|
|
4594
|
-
}
|
|
4595
|
-
cosh
|
|
4652
|
+
}
|
|
4653
|
+
static cosh(arg) {
|
|
4596
4654
|
return new Trig("cosh", arg);
|
|
4597
4655
|
}
|
|
4598
|
-
});
|
|
4599
|
-
function Abs(arg) {
|
|
4600
|
-
this.arg = arg;
|
|
4601
4656
|
}
|
|
4602
|
-
Abs
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4657
|
+
class Abs extends Expr {
|
|
4658
|
+
constructor(arg) {
|
|
4659
|
+
super();
|
|
4660
|
+
this.arg = void 0;
|
|
4661
|
+
this.func = Abs;
|
|
4662
|
+
this.arg = arg;
|
|
4663
|
+
}
|
|
4664
|
+
args() {
|
|
4606
4665
|
return [this.arg];
|
|
4607
|
-
}
|
|
4608
|
-
eval
|
|
4666
|
+
}
|
|
4667
|
+
eval(vars = {}, options) {
|
|
4609
4668
|
return Math.abs(this.arg.eval(vars, options));
|
|
4610
|
-
}
|
|
4611
|
-
codegen
|
|
4669
|
+
}
|
|
4670
|
+
codegen() {
|
|
4612
4671
|
return "Math.abs(" + this.arg.codegen() + ")";
|
|
4613
|
-
}
|
|
4614
|
-
print
|
|
4672
|
+
}
|
|
4673
|
+
print() {
|
|
4615
4674
|
return "abs(" + this.arg.print() + ")";
|
|
4616
|
-
}
|
|
4617
|
-
tex
|
|
4675
|
+
}
|
|
4676
|
+
tex() {
|
|
4618
4677
|
return "\\left|" + this.arg.tex() + "\\right|";
|
|
4619
|
-
}
|
|
4620
|
-
collect
|
|
4678
|
+
}
|
|
4679
|
+
collect(options) {
|
|
4621
4680
|
var abs = this.recurse("collect", options);
|
|
4622
4681
|
if (abs.arg.isPositive()) {
|
|
4623
4682
|
// e.g. |2^x| -> 2^x
|
|
@@ -4627,7 +4686,7 @@ _.extend(Abs.prototype, {
|
|
|
4627
4686
|
return abs.arg.abs();
|
|
4628
4687
|
} else if (abs.arg instanceof Mul) {
|
|
4629
4688
|
// e.g. |-2*pi*x| -> 2*pi*|x|
|
|
4630
|
-
var terms = _.groupBy(abs.arg.terms,
|
|
4689
|
+
var terms = _.groupBy(abs.arg.terms, term => {
|
|
4631
4690
|
if (term.isPositive()) {
|
|
4632
4691
|
return "positive";
|
|
4633
4692
|
} else if (term instanceof Num) {
|
|
@@ -4644,55 +4703,60 @@ _.extend(Abs.prototype, {
|
|
|
4644
4703
|
} else {
|
|
4645
4704
|
return abs;
|
|
4646
4705
|
}
|
|
4647
|
-
}
|
|
4706
|
+
}
|
|
4707
|
+
|
|
4648
4708
|
// this should definitely be behind a super-simplify flag
|
|
4649
|
-
expand
|
|
4709
|
+
expand() {
|
|
4650
4710
|
var abs = this.recurse("expand");
|
|
4651
4711
|
if (abs.arg instanceof Mul) {
|
|
4652
4712
|
// e.g. |xyz| -> |x|*|y|*|z|
|
|
4653
|
-
var terms = _.map(abs.arg.terms,
|
|
4713
|
+
var terms = _.map(abs.arg.terms, term => {
|
|
4654
4714
|
return new Abs(term);
|
|
4655
4715
|
});
|
|
4656
4716
|
return new Mul(terms);
|
|
4657
4717
|
} else {
|
|
4658
4718
|
return abs;
|
|
4659
4719
|
}
|
|
4660
|
-
}
|
|
4661
|
-
isPositive
|
|
4720
|
+
}
|
|
4721
|
+
isPositive() {
|
|
4662
4722
|
return true;
|
|
4663
4723
|
}
|
|
4664
|
-
}
|
|
4724
|
+
}
|
|
4665
4725
|
|
|
4666
4726
|
/* equation */
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4727
|
+
class Eq extends Expr {
|
|
4728
|
+
constructor(left, type, right) {
|
|
4729
|
+
super();
|
|
4730
|
+
this.left = void 0;
|
|
4731
|
+
this.type = void 0;
|
|
4732
|
+
// TODO(kevinb): use an enum for this
|
|
4733
|
+
this.right = void 0;
|
|
4734
|
+
this.func = Eq;
|
|
4735
|
+
this.signs = {
|
|
4736
|
+
"=": " = ",
|
|
4737
|
+
"<": " < ",
|
|
4738
|
+
">": " > ",
|
|
4739
|
+
"<>": " \\ne ",
|
|
4740
|
+
"<=": " \\le ",
|
|
4741
|
+
">=": " \\ge "
|
|
4742
|
+
};
|
|
4743
|
+
this.left = left;
|
|
4744
|
+
this.type = type;
|
|
4745
|
+
this.right = right;
|
|
4746
|
+
}
|
|
4747
|
+
args() {
|
|
4676
4748
|
return [this.left, this.type, this.right];
|
|
4677
|
-
}
|
|
4678
|
-
needsExplicitMul
|
|
4749
|
+
}
|
|
4750
|
+
needsExplicitMul() {
|
|
4679
4751
|
return false;
|
|
4680
|
-
}
|
|
4681
|
-
print
|
|
4752
|
+
}
|
|
4753
|
+
print() {
|
|
4682
4754
|
return this.left.print() + this.type + this.right.print();
|
|
4683
|
-
}
|
|
4684
|
-
|
|
4685
|
-
"=": " = ",
|
|
4686
|
-
"<": " < ",
|
|
4687
|
-
">": " > ",
|
|
4688
|
-
"<>": " \\ne ",
|
|
4689
|
-
"<=": " \\le ",
|
|
4690
|
-
">=": " \\ge "
|
|
4691
|
-
},
|
|
4692
|
-
tex: function () {
|
|
4755
|
+
}
|
|
4756
|
+
tex() {
|
|
4693
4757
|
return this.left.tex() + this.signs[this.type] + this.right.tex();
|
|
4694
|
-
}
|
|
4695
|
-
normalize
|
|
4758
|
+
}
|
|
4759
|
+
normalize() {
|
|
4696
4760
|
var eq = this.recurse("normalize");
|
|
4697
4761
|
if (_.contains([">", ">="], eq.type)) {
|
|
4698
4762
|
// inequalities should have the smaller side on the left
|
|
@@ -4700,18 +4764,19 @@ _.extend(Eq.prototype, {
|
|
|
4700
4764
|
} else {
|
|
4701
4765
|
return eq;
|
|
4702
4766
|
}
|
|
4703
|
-
}
|
|
4767
|
+
}
|
|
4768
|
+
|
|
4704
4769
|
// convert this equation to an expression set to zero
|
|
4705
4770
|
// the expression is normalized to a canonical form
|
|
4706
4771
|
// e.g. y/2=x/4 -> y/2-x/4(=0) -> 2y-x(=0)
|
|
4707
4772
|
// unless unfactored is specified, will then divide through
|
|
4708
|
-
asExpr
|
|
4709
|
-
var isZero =
|
|
4773
|
+
asExpr(unfactored = false) {
|
|
4774
|
+
var isZero = expr => {
|
|
4710
4775
|
return expr instanceof Num && expr.isSimple() && expr.eval() === 0;
|
|
4711
4776
|
};
|
|
4712
4777
|
|
|
4713
4778
|
// first convert to a sequence of additive terms
|
|
4714
|
-
|
|
4779
|
+
let terms = [];
|
|
4715
4780
|
if (this.left instanceof Add) {
|
|
4716
4781
|
terms = _.clone(this.left.terms);
|
|
4717
4782
|
} else if (!isZero(this.left)) {
|
|
@@ -4741,8 +4806,8 @@ _.extend(Eq.prototype, {
|
|
|
4741
4806
|
if (isInequality && !denominator.isPositive()) {
|
|
4742
4807
|
denominator = denominator.asPositiveFactor();
|
|
4743
4808
|
}
|
|
4744
|
-
if (!denominator.equals(
|
|
4745
|
-
terms = _.map(terms,
|
|
4809
|
+
if (!denominator.equals(NumOne)) {
|
|
4810
|
+
terms = _.map(terms, term => {
|
|
4746
4811
|
return Mul.createOrAppend(term, denominator).simplify({
|
|
4747
4812
|
once: true,
|
|
4748
4813
|
preciseFloats: true
|
|
@@ -4752,41 +4817,33 @@ _.extend(Eq.prototype, {
|
|
|
4752
4817
|
}
|
|
4753
4818
|
var add = new Add(terms).flatten();
|
|
4754
4819
|
return unfactored ? add : this.divideThrough(add);
|
|
4755
|
-
}
|
|
4820
|
+
}
|
|
4821
|
+
|
|
4756
4822
|
// divide through by every common factor in the expression
|
|
4757
4823
|
// e.g. 2y-4x(=0) -> y-2x(=0)
|
|
4758
4824
|
// TODO(alex): Make it an option to only divide by variables/expressions
|
|
4759
4825
|
// guaranteed to be nonzero
|
|
4760
|
-
divideThrough
|
|
4761
|
-
|
|
4762
|
-
|
|
4826
|
+
divideThrough(expr) {
|
|
4827
|
+
const isInequality = !this.isEquality();
|
|
4828
|
+
const simplified = expr.simplify({
|
|
4763
4829
|
once: true
|
|
4764
4830
|
});
|
|
4765
|
-
|
|
4831
|
+
const factored = simplified.factor({
|
|
4766
4832
|
keepNegative: isInequality
|
|
4767
4833
|
});
|
|
4768
4834
|
if (!(factored instanceof Mul)) {
|
|
4769
4835
|
return expr;
|
|
4770
4836
|
}
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
var hasVar = function hasVar(term) {
|
|
4776
|
-
return !!term.getVars().length;
|
|
4777
|
-
};
|
|
4778
|
-
var isOne = function isOne(term) {
|
|
4779
|
-
return term.equals(Num.One);
|
|
4780
|
-
};
|
|
4781
|
-
var grouped = _.groupBy(terms, isAdd);
|
|
4782
|
-
var adds = grouped[true] || [];
|
|
4783
|
-
var others = grouped[false] || [];
|
|
4837
|
+
const terms = factored.terms;
|
|
4838
|
+
const hasVar = term => !!term.getVars().length;
|
|
4839
|
+
const isOne = term => term.equals(NumOne);
|
|
4840
|
+
const [adds, others] = partition(terms, isAdd);
|
|
4784
4841
|
if (adds.length && this.isEquality()) {
|
|
4785
4842
|
// keep only Adds
|
|
4786
4843
|
// e.g. 2xy(z+1)(=0) -> z+1(=0)
|
|
4787
4844
|
return new Mul(adds).flatten();
|
|
4788
4845
|
}
|
|
4789
|
-
|
|
4846
|
+
let denominator = others;
|
|
4790
4847
|
if (!adds.length) {
|
|
4791
4848
|
// if no Adds, keep all variable terms to preserve meaning
|
|
4792
4849
|
// e.g. 42xyz(=0) -> xyz(=0)
|
|
@@ -4800,10 +4857,10 @@ _.extend(Eq.prototype, {
|
|
|
4800
4857
|
|
|
4801
4858
|
// don't need to divide by one
|
|
4802
4859
|
denominator = _.reject(denominator, isOne);
|
|
4803
|
-
denominator = _.map(denominator,
|
|
4804
|
-
return new Pow(term,
|
|
4860
|
+
denominator = _.map(denominator, term => {
|
|
4861
|
+
return new Pow(term, NumDiv);
|
|
4805
4862
|
});
|
|
4806
|
-
|
|
4863
|
+
const dividedResult = new Mul(terms.concat(denominator)).collect();
|
|
4807
4864
|
|
|
4808
4865
|
// If the end result is the same as the original factoring,
|
|
4809
4866
|
// rollback the factoring and discard all intermediate steps.
|
|
@@ -4812,11 +4869,11 @@ _.extend(Eq.prototype, {
|
|
|
4812
4869
|
} else {
|
|
4813
4870
|
return dividedResult;
|
|
4814
4871
|
}
|
|
4815
|
-
}
|
|
4816
|
-
isEquality
|
|
4872
|
+
}
|
|
4873
|
+
isEquality() {
|
|
4817
4874
|
return _.contains(["=", "<>"], this.type);
|
|
4818
|
-
}
|
|
4819
|
-
compare
|
|
4875
|
+
}
|
|
4876
|
+
compare(other) {
|
|
4820
4877
|
// expression comparisons are handled by Expr.compare()
|
|
4821
4878
|
if (!(other instanceof Eq)) {
|
|
4822
4879
|
return false;
|
|
@@ -4837,9 +4894,10 @@ _.extend(Eq.prototype, {
|
|
|
4837
4894
|
} else {
|
|
4838
4895
|
return expr1.compare(expr2);
|
|
4839
4896
|
}
|
|
4840
|
-
}
|
|
4897
|
+
}
|
|
4898
|
+
|
|
4841
4899
|
// should only be done after compare() returns true to avoid false positives
|
|
4842
|
-
sameForm
|
|
4900
|
+
sameForm(other) {
|
|
4843
4901
|
var eq1 = this.normalize();
|
|
4844
4902
|
var eq2 = other.normalize();
|
|
4845
4903
|
var same = eq1.left.sameForm(eq2.left) && eq1.right.sameForm(eq2.right);
|
|
@@ -4849,71 +4907,66 @@ _.extend(Eq.prototype, {
|
|
|
4849
4907
|
} else {
|
|
4850
4908
|
return same;
|
|
4851
4909
|
}
|
|
4852
|
-
}
|
|
4910
|
+
}
|
|
4911
|
+
|
|
4853
4912
|
// we don't want to override collect because it would turn y=x into y-x(=0)
|
|
4854
4913
|
// instead, we ask if the equation was in that form, would it be simplified?
|
|
4855
|
-
isSimplified
|
|
4914
|
+
isSimplified() {
|
|
4856
4915
|
var expr = this.asExpr( /* unfactored */true);
|
|
4857
4916
|
var simplified = this.divideThrough(expr).simplify();
|
|
4858
4917
|
return expr.equals(simplified) && this.left.isSimplified() && this.right.isSimplified();
|
|
4859
4918
|
}
|
|
4860
|
-
|
|
4861
|
-
_.extend(Eq.prototype, {
|
|
4919
|
+
|
|
4862
4920
|
// Assumptions: Expression is of the form a+bx, and we solve for x
|
|
4863
|
-
solveLinearEquationForVariable
|
|
4921
|
+
solveLinearEquationForVariable(variable) {
|
|
4864
4922
|
var expr = this.asExpr();
|
|
4865
|
-
if (!expr
|
|
4923
|
+
if (!(expr instanceof Add) || expr.terms.length !== 2) {
|
|
4866
4924
|
throw new Error("Can only handle linear equations of the form " + "a + bx (= 0)");
|
|
4867
4925
|
}
|
|
4868
|
-
var hasVar =
|
|
4926
|
+
var hasVar = term => {
|
|
4869
4927
|
return term.has(Var) && _.contains(term.getVars(), variable.symbol);
|
|
4870
4928
|
};
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
b = Mul.handleDivide(expr.terms[0], variable);
|
|
4875
|
-
} else {
|
|
4876
|
-
a = Mul.handleNegative(expr.terms[0]);
|
|
4877
|
-
b = Mul.handleDivide(expr.terms[1], variable);
|
|
4878
|
-
}
|
|
4929
|
+
const termHasVar = hasVar(expr.terms[0]);
|
|
4930
|
+
const a = termHasVar ? Mul.handleNegative(expr.terms[1]) : Mul.handleNegative(expr.terms[0]);
|
|
4931
|
+
const b = termHasVar ? Mul.handleDivide(expr.terms[0], variable) : Mul.handleDivide(expr.terms[1], variable);
|
|
4879
4932
|
return Mul.handleDivide(a, b).simplify();
|
|
4880
4933
|
}
|
|
4881
|
-
}
|
|
4934
|
+
}
|
|
4882
4935
|
|
|
4883
4936
|
/* abstract symbol node */
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
_.extend(Symbol.prototype, {
|
|
4887
|
-
needsExplicitMul: function () {
|
|
4937
|
+
class Sym extends Expr {
|
|
4938
|
+
needsExplicitMul() {
|
|
4888
4939
|
return false;
|
|
4889
|
-
}
|
|
4890
|
-
findGCD
|
|
4891
|
-
if (factor instanceof
|
|
4892
|
-
return this.equals(factor) ? this :
|
|
4940
|
+
}
|
|
4941
|
+
findGCD(factor) {
|
|
4942
|
+
if (factor instanceof Sym || factor instanceof Num) {
|
|
4943
|
+
return this.equals(factor) ? this : NumOne;
|
|
4893
4944
|
} else {
|
|
4894
4945
|
return factor.findGCD(this);
|
|
4895
4946
|
}
|
|
4896
4947
|
}
|
|
4897
|
-
}
|
|
4948
|
+
}
|
|
4898
4949
|
|
|
4899
4950
|
/* function variable */
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
|
|
4951
|
+
class Func extends Sym {
|
|
4952
|
+
constructor(symbol, arg) {
|
|
4953
|
+
super();
|
|
4954
|
+
this.symbol = void 0;
|
|
4955
|
+
this.arg = void 0;
|
|
4956
|
+
this.func = Func;
|
|
4957
|
+
this.symbol = symbol;
|
|
4958
|
+
this.arg = arg;
|
|
4959
|
+
}
|
|
4960
|
+
args() {
|
|
4908
4961
|
return [this.symbol, this.arg];
|
|
4909
|
-
}
|
|
4910
|
-
print
|
|
4962
|
+
}
|
|
4963
|
+
print() {
|
|
4911
4964
|
return this.symbol + "(" + this.arg.print() + ")";
|
|
4912
|
-
}
|
|
4913
|
-
tex
|
|
4965
|
+
}
|
|
4966
|
+
tex() {
|
|
4914
4967
|
return this.symbol + "(" + this.arg.tex() + ")";
|
|
4915
|
-
}
|
|
4916
|
-
eval
|
|
4968
|
+
}
|
|
4969
|
+
eval(vars = {}, options) {
|
|
4917
4970
|
var arg = this.arg;
|
|
4918
4971
|
var func = vars[this.symbol];
|
|
4919
4972
|
var newVars = _.extend(_.clone(vars), {
|
|
@@ -4925,191 +4978,211 @@ _.extend(Func.prototype, {
|
|
|
4925
4978
|
}
|
|
4926
4979
|
// If parsedFunc isn't actually parsed, return its error
|
|
4927
4980
|
return parsedFunc;
|
|
4928
|
-
}
|
|
4929
|
-
codegen
|
|
4981
|
+
}
|
|
4982
|
+
codegen() {
|
|
4930
4983
|
return 'vars["' + this.symbol + '"](' + this.arg.codegen() + ")";
|
|
4931
|
-
}
|
|
4932
|
-
getUnits
|
|
4984
|
+
}
|
|
4985
|
+
getUnits() {
|
|
4933
4986
|
return this.arg.getUnits();
|
|
4934
|
-
}
|
|
4935
|
-
getVars
|
|
4987
|
+
}
|
|
4988
|
+
getVars(excludeFunc) {
|
|
4936
4989
|
if (excludeFunc) {
|
|
4937
4990
|
return this.arg.getVars();
|
|
4938
4991
|
} else {
|
|
4939
4992
|
return _.union(this.arg.getVars(), [this.symbol]).sort();
|
|
4940
4993
|
}
|
|
4941
|
-
}
|
|
4942
|
-
getConsts
|
|
4994
|
+
}
|
|
4995
|
+
getConsts() {
|
|
4943
4996
|
return this.arg.getConsts();
|
|
4944
4997
|
}
|
|
4945
|
-
}
|
|
4998
|
+
}
|
|
4946
4999
|
|
|
4947
5000
|
/* variable */
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
5001
|
+
class Var extends Sym {
|
|
5002
|
+
constructor(symbol, subscript) {
|
|
5003
|
+
super();
|
|
5004
|
+
this.symbol = void 0;
|
|
5005
|
+
this.subscript = void 0;
|
|
5006
|
+
this.func = Var;
|
|
5007
|
+
this.symbol = symbol;
|
|
5008
|
+
this.subscript = subscript;
|
|
5009
|
+
}
|
|
5010
|
+
args() {
|
|
4956
5011
|
return [this.symbol, this.subscript];
|
|
4957
|
-
}
|
|
4958
|
-
exprArgs
|
|
5012
|
+
}
|
|
5013
|
+
exprArgs() {
|
|
4959
5014
|
return [];
|
|
4960
|
-
}
|
|
4961
|
-
recurse
|
|
5015
|
+
}
|
|
5016
|
+
recurse() {
|
|
4962
5017
|
return this;
|
|
4963
|
-
}
|
|
4964
|
-
print
|
|
5018
|
+
}
|
|
5019
|
+
print() {
|
|
4965
5020
|
var sub = "";
|
|
4966
5021
|
if (this.subscript) {
|
|
4967
5022
|
sub = "_(" + this.subscript.print() + ")";
|
|
4968
5023
|
}
|
|
4969
5024
|
return this.symbol + sub;
|
|
4970
|
-
}
|
|
5025
|
+
}
|
|
5026
|
+
|
|
4971
5027
|
// Provide a way to easily evalate expressions with the common case,
|
|
4972
5028
|
// subscripts that consist of a single number or symbol e.g. x_a or x_42
|
|
4973
|
-
prettyPrint
|
|
5029
|
+
prettyPrint() {
|
|
4974
5030
|
var sub = this.subscript;
|
|
4975
|
-
if (sub && (sub instanceof Num || sub instanceof
|
|
5031
|
+
if (sub && (sub instanceof Num || sub instanceof Sym)) {
|
|
4976
5032
|
return this.symbol + "_" + sub.print();
|
|
4977
5033
|
} else {
|
|
4978
5034
|
return this.print();
|
|
4979
5035
|
}
|
|
4980
|
-
}
|
|
4981
|
-
tex
|
|
5036
|
+
}
|
|
5037
|
+
tex() {
|
|
4982
5038
|
var sub = "";
|
|
4983
5039
|
if (this.subscript) {
|
|
4984
5040
|
sub = "_{" + this.subscript.tex() + "}";
|
|
4985
5041
|
}
|
|
4986
5042
|
var prefix = this.symbol.length > 1 ? "\\" : "";
|
|
4987
5043
|
return prefix + this.symbol + sub;
|
|
4988
|
-
}
|
|
4989
|
-
repr
|
|
5044
|
+
}
|
|
5045
|
+
repr() {
|
|
4990
5046
|
return "Var(" + this.print() + ")";
|
|
4991
|
-
}
|
|
4992
|
-
eval
|
|
5047
|
+
}
|
|
5048
|
+
eval(vars = {}, options) {
|
|
5049
|
+
// @ts-expect-error: values is Vars are strings, but here
|
|
5050
|
+
// we expect them to be numbers. We should probably store
|
|
5051
|
+
// Var and Func entries separately in the Vars type so that
|
|
5052
|
+
// they can be typed correctly.
|
|
4993
5053
|
return vars[this.prettyPrint()];
|
|
4994
|
-
}
|
|
4995
|
-
codegen
|
|
5054
|
+
}
|
|
5055
|
+
codegen() {
|
|
4996
5056
|
return 'vars["' + this.prettyPrint() + '"]';
|
|
4997
|
-
}
|
|
4998
|
-
getVars
|
|
5057
|
+
}
|
|
5058
|
+
getVars() {
|
|
4999
5059
|
return [this.prettyPrint()];
|
|
5000
|
-
}
|
|
5001
|
-
isPositive
|
|
5060
|
+
}
|
|
5061
|
+
isPositive() {
|
|
5002
5062
|
return false;
|
|
5003
5063
|
}
|
|
5004
|
-
}
|
|
5064
|
+
}
|
|
5005
5065
|
|
|
5006
5066
|
/* constant */
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5067
|
+
class Const extends Sym {
|
|
5068
|
+
constructor(symbol) {
|
|
5069
|
+
super();
|
|
5070
|
+
this.symbol = void 0;
|
|
5071
|
+
this.func = Const;
|
|
5072
|
+
this.symbol = symbol;
|
|
5073
|
+
}
|
|
5074
|
+
args() {
|
|
5014
5075
|
return [this.symbol];
|
|
5015
|
-
}
|
|
5016
|
-
recurse
|
|
5076
|
+
}
|
|
5077
|
+
recurse() {
|
|
5017
5078
|
return this;
|
|
5018
|
-
}
|
|
5019
|
-
eval
|
|
5079
|
+
}
|
|
5080
|
+
eval(vars = {}, options) {
|
|
5020
5081
|
if (this.symbol === "pi") {
|
|
5021
5082
|
return Math.PI;
|
|
5022
5083
|
} else if (this.symbol === "e") {
|
|
5023
5084
|
return Math.E;
|
|
5085
|
+
} else {
|
|
5086
|
+
// @ts-expect-error: should we throw an error here?
|
|
5087
|
+
return undefined;
|
|
5024
5088
|
}
|
|
5025
|
-
}
|
|
5026
|
-
codegen
|
|
5089
|
+
}
|
|
5090
|
+
codegen() {
|
|
5027
5091
|
if (this.symbol === "pi") {
|
|
5028
5092
|
return "Math.PI";
|
|
5029
5093
|
} else if (this.symbol === "e") {
|
|
5030
5094
|
return "Math.E";
|
|
5095
|
+
} else {
|
|
5096
|
+
// @ts-expect-error: should we throw an error here?
|
|
5097
|
+
return undefined;
|
|
5031
5098
|
}
|
|
5032
|
-
}
|
|
5033
|
-
print
|
|
5099
|
+
}
|
|
5100
|
+
print() {
|
|
5034
5101
|
return this.symbol;
|
|
5035
|
-
}
|
|
5036
|
-
tex
|
|
5102
|
+
}
|
|
5103
|
+
tex() {
|
|
5037
5104
|
if (this.symbol === "pi") {
|
|
5038
5105
|
return "\\pi ";
|
|
5039
5106
|
} else if (this.symbol === "e") {
|
|
5040
5107
|
return "e";
|
|
5108
|
+
} else {
|
|
5109
|
+
// @ts-expect-error: should we return this.symbol here?
|
|
5110
|
+
return undefined;
|
|
5041
5111
|
}
|
|
5042
|
-
}
|
|
5043
|
-
isPositive
|
|
5112
|
+
}
|
|
5113
|
+
isPositive() {
|
|
5044
5114
|
return this.eval() > 0;
|
|
5045
|
-
}
|
|
5046
|
-
abs
|
|
5115
|
+
}
|
|
5116
|
+
abs() {
|
|
5047
5117
|
if (this.eval() > 0) {
|
|
5048
5118
|
return this;
|
|
5049
5119
|
} else {
|
|
5050
5120
|
return Mul.handleNegative(this);
|
|
5051
5121
|
}
|
|
5052
|
-
}
|
|
5053
|
-
getConsts
|
|
5122
|
+
}
|
|
5123
|
+
getConsts() {
|
|
5054
5124
|
return [this.print()];
|
|
5055
5125
|
}
|
|
5056
|
-
}
|
|
5057
|
-
Const.e = new Const("e");
|
|
5058
|
-
Const.pi = new Const("pi");
|
|
5126
|
+
}
|
|
5059
5127
|
|
|
5060
5128
|
/* abstract number node */
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
|
|
5129
|
+
_class12 = Const;
|
|
5130
|
+
Const.e = new _class12("e");
|
|
5131
|
+
Const.pi = new _class12("pi");
|
|
5132
|
+
class Num extends Expr {
|
|
5133
|
+
constructor() {
|
|
5134
|
+
super();
|
|
5135
|
+
// hints for interpreting and rendering user input
|
|
5136
|
+
this.n = 0;
|
|
5137
|
+
this.hints = _extends({}, this.hints, {
|
|
5138
|
+
negate: false,
|
|
5139
|
+
subtract: false,
|
|
5140
|
+
divide: false,
|
|
5141
|
+
root: false,
|
|
5142
|
+
fraction: false,
|
|
5143
|
+
entered: false
|
|
5144
|
+
});
|
|
5145
|
+
}
|
|
5146
|
+
repr() {
|
|
5065
5147
|
return this.print();
|
|
5066
|
-
}
|
|
5067
|
-
strip
|
|
5148
|
+
}
|
|
5149
|
+
strip() {
|
|
5068
5150
|
return this.abs();
|
|
5069
|
-
}
|
|
5070
|
-
recurse
|
|
5151
|
+
}
|
|
5152
|
+
recurse() {
|
|
5071
5153
|
return this;
|
|
5072
|
-
}
|
|
5073
|
-
codegen
|
|
5154
|
+
}
|
|
5155
|
+
codegen() {
|
|
5074
5156
|
return this.print();
|
|
5075
|
-
}
|
|
5157
|
+
}
|
|
5158
|
+
|
|
5076
5159
|
// takes another Num and returns a new Num
|
|
5077
|
-
|
|
5078
|
-
mul: abstract,
|
|
5160
|
+
|
|
5079
5161
|
// returns this Num's additive inverse
|
|
5080
|
-
|
|
5081
|
-
isSubtract
|
|
5082
|
-
return this.hints.subtract;
|
|
5083
|
-
}
|
|
5162
|
+
|
|
5163
|
+
isSubtract() {
|
|
5164
|
+
return Boolean(this.hints.subtract);
|
|
5165
|
+
}
|
|
5166
|
+
|
|
5084
5167
|
// return the absolute value of the number
|
|
5085
|
-
|
|
5086
|
-
needsExplicitMul
|
|
5168
|
+
|
|
5169
|
+
needsExplicitMul() {
|
|
5087
5170
|
return true;
|
|
5088
|
-
}
|
|
5089
|
-
|
|
5090
|
-
isPositive: function () {
|
|
5171
|
+
}
|
|
5172
|
+
isPositive() {
|
|
5091
5173
|
return this.eval() > 0;
|
|
5092
|
-
}
|
|
5093
|
-
isNegative
|
|
5174
|
+
}
|
|
5175
|
+
isNegative() {
|
|
5094
5176
|
return this.eval() < 0;
|
|
5095
|
-
}
|
|
5096
|
-
asPositiveFactor
|
|
5177
|
+
}
|
|
5178
|
+
asPositiveFactor() {
|
|
5097
5179
|
return this.isPositive() ? this : this.abs();
|
|
5098
|
-
}
|
|
5099
|
-
|
|
5100
|
-
hints: _.extend(Num.prototype.hints, {
|
|
5101
|
-
negate: false,
|
|
5102
|
-
subtract: false,
|
|
5103
|
-
divide: false,
|
|
5104
|
-
root: false,
|
|
5105
|
-
fraction: false,
|
|
5106
|
-
entered: false
|
|
5107
|
-
}),
|
|
5180
|
+
}
|
|
5181
|
+
|
|
5108
5182
|
// whether a number is considered simple (one term)
|
|
5109
5183
|
// e.g. for reals, ints and floats are simple
|
|
5110
|
-
isSimple: abstract,
|
|
5111
5184
|
// Based on http://stackoverflow.com/a/10454560/2571482
|
|
5112
|
-
getDecimalPlaces
|
|
5185
|
+
getDecimalPlaces() {
|
|
5113
5186
|
var match = ("" + this.n).match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/);
|
|
5114
5187
|
if (match) {
|
|
5115
5188
|
return Math.max(0,
|
|
@@ -5120,52 +5193,88 @@ _.extend(Num.prototype, {
|
|
|
5120
5193
|
} else {
|
|
5121
5194
|
return 0;
|
|
5122
5195
|
}
|
|
5123
|
-
}
|
|
5124
|
-
|
|
5125
|
-
|
|
5196
|
+
}
|
|
5197
|
+
static negativeOne(hint) {
|
|
5198
|
+
if (hint === "subtract") {
|
|
5199
|
+
return NumSub;
|
|
5200
|
+
} else if (hint === "divide") {
|
|
5201
|
+
return NumDiv;
|
|
5202
|
+
} else {
|
|
5203
|
+
return NumNeg;
|
|
5204
|
+
}
|
|
5205
|
+
}
|
|
5126
5206
|
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5207
|
+
// find the greatest common denominator
|
|
5208
|
+
static findGCD(a, b) {
|
|
5209
|
+
var mod;
|
|
5210
|
+
a = Math.abs(a);
|
|
5211
|
+
b = Math.abs(b);
|
|
5212
|
+
|
|
5213
|
+
// Euclid's method doesn't handle non-integers very well. For now
|
|
5214
|
+
// we just say we can't pull out a common factor. It might be
|
|
5215
|
+
// reasonable to do better than this in the future.
|
|
5216
|
+
if (a !== Math.floor(a) || b !== Math.floor(b)) {
|
|
5217
|
+
return 1;
|
|
5218
|
+
}
|
|
5219
|
+
while (b) {
|
|
5220
|
+
mod = a % b;
|
|
5221
|
+
a = b;
|
|
5222
|
+
b = mod;
|
|
5223
|
+
}
|
|
5224
|
+
return a;
|
|
5225
|
+
}
|
|
5226
|
+
static min(...args) {
|
|
5227
|
+
return _.min(args, num => num.eval());
|
|
5228
|
+
}
|
|
5229
|
+
static max(...args) {
|
|
5230
|
+
return _.max(args, num => num.eval());
|
|
5231
|
+
}
|
|
5137
5232
|
}
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5233
|
+
|
|
5234
|
+
/* rational number (n: numerator, d: denominator) */
|
|
5235
|
+
class Rational extends Num {
|
|
5236
|
+
constructor(numerator, denominator) {
|
|
5237
|
+
super();
|
|
5238
|
+
this.n = void 0;
|
|
5239
|
+
this.d = void 0;
|
|
5240
|
+
this.func = Rational;
|
|
5241
|
+
var n = numerator;
|
|
5242
|
+
var d = denominator;
|
|
5243
|
+
if (d < 0) {
|
|
5244
|
+
n = -n;
|
|
5245
|
+
d = -d;
|
|
5246
|
+
}
|
|
5247
|
+
this.n = n;
|
|
5248
|
+
this.d = d;
|
|
5249
|
+
}
|
|
5250
|
+
args() {
|
|
5142
5251
|
return [this.n, this.d];
|
|
5143
|
-
}
|
|
5144
|
-
eval
|
|
5252
|
+
}
|
|
5253
|
+
eval() {
|
|
5145
5254
|
return this.n / this.d;
|
|
5146
|
-
}
|
|
5147
|
-
print
|
|
5255
|
+
}
|
|
5256
|
+
print() {
|
|
5148
5257
|
return this.n.toString() + "/" + this.d.toString();
|
|
5149
|
-
}
|
|
5150
|
-
tex
|
|
5258
|
+
}
|
|
5259
|
+
tex() {
|
|
5151
5260
|
var tex = "\\frac{" + Math.abs(this.n).toString() + "}{" + this.d.toString() + "}";
|
|
5152
5261
|
return this.n < 0 ? "-" + tex : tex;
|
|
5153
|
-
}
|
|
5154
|
-
add
|
|
5262
|
+
}
|
|
5263
|
+
add(num, options) {
|
|
5155
5264
|
if (num instanceof Rational) {
|
|
5156
5265
|
return new Rational(this.n * num.d + this.d * num.n, this.d * num.d).collect();
|
|
5157
5266
|
} else {
|
|
5158
5267
|
return num.add(this, options);
|
|
5159
5268
|
}
|
|
5160
|
-
}
|
|
5161
|
-
mul
|
|
5269
|
+
}
|
|
5270
|
+
mul(num, options) {
|
|
5162
5271
|
if (num instanceof Rational) {
|
|
5163
5272
|
return new Rational(this.n * num.n, this.d * num.d).collect();
|
|
5164
5273
|
} else {
|
|
5165
5274
|
return num.mul(this, options);
|
|
5166
5275
|
}
|
|
5167
|
-
}
|
|
5168
|
-
collect
|
|
5276
|
+
}
|
|
5277
|
+
collect() {
|
|
5169
5278
|
var gcd = Num.findGCD(this.n, this.d);
|
|
5170
5279
|
var n = this.n / gcd;
|
|
5171
5280
|
var d = this.d / gcd;
|
|
@@ -5174,14 +5283,14 @@ _.extend(Rational.prototype, {
|
|
|
5174
5283
|
} else {
|
|
5175
5284
|
return new Rational(n, d);
|
|
5176
5285
|
}
|
|
5177
|
-
}
|
|
5178
|
-
negate
|
|
5286
|
+
}
|
|
5287
|
+
negate() {
|
|
5179
5288
|
return new Rational(-this.n, this.d);
|
|
5180
|
-
}
|
|
5181
|
-
abs
|
|
5289
|
+
}
|
|
5290
|
+
abs() {
|
|
5182
5291
|
return new Rational(Math.abs(this.n), this.d);
|
|
5183
|
-
}
|
|
5184
|
-
findGCD
|
|
5292
|
+
}
|
|
5293
|
+
findGCD(factor) {
|
|
5185
5294
|
// Attempt to factor out common numerators and denominators to return
|
|
5186
5295
|
// a Rational instead of a Float
|
|
5187
5296
|
if (factor instanceof Rational) {
|
|
@@ -5196,9 +5305,10 @@ _.extend(Rational.prototype, {
|
|
|
5196
5305
|
} else {
|
|
5197
5306
|
return factor.findGCD(this);
|
|
5198
5307
|
}
|
|
5199
|
-
}
|
|
5308
|
+
}
|
|
5309
|
+
|
|
5200
5310
|
// for now, assuming that exp is a Num
|
|
5201
|
-
raiseToThe
|
|
5311
|
+
raiseToThe(exp) {
|
|
5202
5312
|
if (exp instanceof Int) {
|
|
5203
5313
|
var positive = exp.eval() > 0;
|
|
5204
5314
|
var abs = exp.abs().eval();
|
|
@@ -5212,120 +5322,121 @@ _.extend(Rational.prototype, {
|
|
|
5212
5322
|
} else {
|
|
5213
5323
|
return new Float(this.eval()).raiseToThe(exp);
|
|
5214
5324
|
}
|
|
5215
|
-
}
|
|
5216
|
-
getDenominator
|
|
5325
|
+
}
|
|
5326
|
+
getDenominator() {
|
|
5217
5327
|
return new Int(this.d);
|
|
5218
|
-
}
|
|
5219
|
-
isSimple
|
|
5328
|
+
}
|
|
5329
|
+
isSimple() {
|
|
5220
5330
|
return false;
|
|
5221
|
-
}
|
|
5222
|
-
asRational
|
|
5331
|
+
}
|
|
5332
|
+
asRational() {
|
|
5223
5333
|
return this;
|
|
5224
5334
|
}
|
|
5225
|
-
}
|
|
5335
|
+
}
|
|
5226
5336
|
|
|
5227
5337
|
/* integer (n: numerator/number) */
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
args: function () {
|
|
5338
|
+
class Int extends Rational {
|
|
5339
|
+
constructor(number) {
|
|
5340
|
+
super(number, 1);
|
|
5341
|
+
this.func = Int;
|
|
5342
|
+
}
|
|
5343
|
+
args() {
|
|
5235
5344
|
return [this.n];
|
|
5236
|
-
}
|
|
5237
|
-
print
|
|
5345
|
+
}
|
|
5346
|
+
print() {
|
|
5238
5347
|
return this.n.toString();
|
|
5239
|
-
}
|
|
5240
|
-
tex
|
|
5348
|
+
}
|
|
5349
|
+
tex() {
|
|
5241
5350
|
return this.n.toString();
|
|
5242
|
-
}
|
|
5243
|
-
negate
|
|
5351
|
+
}
|
|
5352
|
+
negate() {
|
|
5244
5353
|
return new Int(-this.n);
|
|
5245
|
-
}
|
|
5246
|
-
abs
|
|
5354
|
+
}
|
|
5355
|
+
abs() {
|
|
5247
5356
|
return new Int(Math.abs(this.n));
|
|
5248
|
-
}
|
|
5249
|
-
isSimple
|
|
5357
|
+
}
|
|
5358
|
+
isSimple() {
|
|
5250
5359
|
return true;
|
|
5251
|
-
}
|
|
5252
|
-
findGCD
|
|
5360
|
+
}
|
|
5361
|
+
findGCD(factor) {
|
|
5253
5362
|
if (factor instanceof Int) {
|
|
5254
5363
|
return new Int(Num.findGCD(this.n, factor.n));
|
|
5255
5364
|
} else {
|
|
5256
5365
|
return factor.findGCD(this);
|
|
5257
5366
|
}
|
|
5258
5367
|
}
|
|
5259
|
-
|
|
5260
|
-
_.extend(Int, {
|
|
5261
|
-
create: function (n) {
|
|
5368
|
+
static create(n) {
|
|
5262
5369
|
return new Int(n).addHint("entered");
|
|
5263
5370
|
}
|
|
5264
|
-
}
|
|
5371
|
+
}
|
|
5265
5372
|
|
|
5266
5373
|
/* float (n: number) */
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
|
|
5273
|
-
|
|
5374
|
+
class Float extends Num {
|
|
5375
|
+
constructor(number) {
|
|
5376
|
+
super();
|
|
5377
|
+
this.n = void 0;
|
|
5378
|
+
this.func = Float;
|
|
5379
|
+
this.n = number;
|
|
5380
|
+
}
|
|
5381
|
+
args() {
|
|
5274
5382
|
return [this.n];
|
|
5275
|
-
}
|
|
5276
|
-
eval
|
|
5383
|
+
}
|
|
5384
|
+
eval() {
|
|
5277
5385
|
return this.n;
|
|
5278
|
-
}
|
|
5386
|
+
}
|
|
5387
|
+
|
|
5279
5388
|
// TODO(alex): when we internationalize number parsing/display
|
|
5280
5389
|
// we should make sure to use the appropriate decimal mark here
|
|
5281
|
-
print
|
|
5390
|
+
print() {
|
|
5282
5391
|
return this.n.toString();
|
|
5283
|
-
}
|
|
5284
|
-
tex
|
|
5392
|
+
}
|
|
5393
|
+
tex() {
|
|
5285
5394
|
return this.n.toString();
|
|
5286
|
-
}
|
|
5287
|
-
add
|
|
5395
|
+
}
|
|
5396
|
+
add(num, options) {
|
|
5288
5397
|
if (options && options.preciseFloats) {
|
|
5289
5398
|
return Float.toDecimalPlaces(this.n + num.eval(), Math.max(this.getDecimalPlaces(), num.getDecimalPlaces()));
|
|
5290
5399
|
} else {
|
|
5291
5400
|
return new Float(this.n + num.eval()).collect();
|
|
5292
5401
|
}
|
|
5293
|
-
}
|
|
5294
|
-
mul
|
|
5402
|
+
}
|
|
5403
|
+
mul(num, options) {
|
|
5295
5404
|
if (options && options.preciseFloats) {
|
|
5296
5405
|
return Float.toDecimalPlaces(this.n * num.eval(), this.getDecimalPlaces() + num.getDecimalPlaces());
|
|
5297
5406
|
} else {
|
|
5298
5407
|
return new Float(this.n * num.eval()).collect();
|
|
5299
5408
|
}
|
|
5300
|
-
}
|
|
5301
|
-
collect
|
|
5409
|
+
}
|
|
5410
|
+
collect(options) {
|
|
5302
5411
|
// We used to simplify Floats to Ints here whenever possible, but no
|
|
5303
5412
|
// longer do so in order to preserve significant figures.
|
|
5304
5413
|
return this;
|
|
5305
|
-
}
|
|
5306
|
-
negate
|
|
5414
|
+
}
|
|
5415
|
+
negate() {
|
|
5307
5416
|
return new Float(-this.n);
|
|
5308
|
-
}
|
|
5309
|
-
abs
|
|
5417
|
+
}
|
|
5418
|
+
abs() {
|
|
5310
5419
|
return new Float(Math.abs(this.n));
|
|
5311
|
-
}
|
|
5312
|
-
findGCD
|
|
5420
|
+
}
|
|
5421
|
+
findGCD(factor) {
|
|
5313
5422
|
if (factor instanceof Num) {
|
|
5314
5423
|
return new Float(Num.findGCD(this.eval(), factor.eval())).collect();
|
|
5315
5424
|
} else {
|
|
5316
5425
|
return factor.findGCD(this);
|
|
5317
5426
|
}
|
|
5318
|
-
}
|
|
5427
|
+
}
|
|
5428
|
+
|
|
5319
5429
|
// for now, assuming that exp is a Num
|
|
5320
|
-
raiseToThe
|
|
5430
|
+
raiseToThe(exp, options) {
|
|
5321
5431
|
if (options && options.preciseFloats && exp instanceof Int && exp.n > 1) {
|
|
5322
5432
|
return Float.toDecimalPlaces(new Pow(this, exp).eval(), this.getDecimalPlaces() * exp.n);
|
|
5323
5433
|
} else {
|
|
5324
5434
|
return new Float(new Pow(this, exp).eval()).collect();
|
|
5325
5435
|
}
|
|
5326
|
-
}
|
|
5436
|
+
}
|
|
5437
|
+
|
|
5327
5438
|
// only to be used on non-repeating decimals (e.g. user-provided)
|
|
5328
|
-
asRational
|
|
5439
|
+
asRational() {
|
|
5329
5440
|
var parts = this.n.toString().split(".");
|
|
5330
5441
|
if (parts.length === 1) {
|
|
5331
5442
|
return new Rational(this.n, 1);
|
|
@@ -5334,77 +5445,30 @@ _.extend(Float.prototype, {
|
|
|
5334
5445
|
var denominator = Math.pow(10, parts[1].length);
|
|
5335
5446
|
return new Rational(numerator, denominator).collect();
|
|
5336
5447
|
}
|
|
5337
|
-
}
|
|
5338
|
-
getDenominator
|
|
5448
|
+
}
|
|
5449
|
+
getDenominator() {
|
|
5339
5450
|
return this.asRational().getDenominator();
|
|
5340
|
-
}
|
|
5341
|
-
isSimple
|
|
5451
|
+
}
|
|
5452
|
+
isSimple() {
|
|
5342
5453
|
return true;
|
|
5343
5454
|
}
|
|
5344
|
-
|
|
5345
|
-
_.extend(Float, {
|
|
5346
|
-
create: function (n) {
|
|
5455
|
+
static create(n) {
|
|
5347
5456
|
return new Float(n).addHint("entered");
|
|
5348
|
-
}
|
|
5457
|
+
}
|
|
5458
|
+
|
|
5349
5459
|
// Account for floating point imprecision by explicitly controlling the
|
|
5350
5460
|
// number of decimal places in common operations (e.g. +, *, ^)
|
|
5351
|
-
toDecimalPlaces
|
|
5461
|
+
static toDecimalPlaces(n, places) {
|
|
5352
5462
|
return new Float(+n.toFixed(Math.min(places, 20))).collect();
|
|
5353
5463
|
}
|
|
5354
|
-
}
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
return Num.Div;
|
|
5363
|
-
} else {
|
|
5364
|
-
return Num.Neg;
|
|
5365
|
-
}
|
|
5366
|
-
},
|
|
5367
|
-
// find the greatest common denominator
|
|
5368
|
-
findGCD: function (a, b) {
|
|
5369
|
-
var mod;
|
|
5370
|
-
a = Math.abs(a);
|
|
5371
|
-
b = Math.abs(b);
|
|
5372
|
-
|
|
5373
|
-
// Euclid's method doesn't handle non-integers very well. For now
|
|
5374
|
-
// we just say we can't pull out a common factor. It might be
|
|
5375
|
-
// reasonable to do better than this in the future.
|
|
5376
|
-
if (a !== Math.floor(a) || b !== Math.floor(b)) {
|
|
5377
|
-
return 1;
|
|
5378
|
-
}
|
|
5379
|
-
while (b) {
|
|
5380
|
-
mod = a % b;
|
|
5381
|
-
a = b;
|
|
5382
|
-
b = mod;
|
|
5383
|
-
}
|
|
5384
|
-
return a;
|
|
5385
|
-
},
|
|
5386
|
-
min: function () {
|
|
5387
|
-
return _.min(_.toArray(arguments), function (num) {
|
|
5388
|
-
return num.eval();
|
|
5389
|
-
});
|
|
5390
|
-
},
|
|
5391
|
-
max: function () {
|
|
5392
|
-
return _.max(_.toArray(arguments), function (num) {
|
|
5393
|
-
return num.eval();
|
|
5394
|
-
});
|
|
5395
|
-
}
|
|
5396
|
-
});
|
|
5397
|
-
Num.Neg = new Int(-1).addHint("negate");
|
|
5398
|
-
Num.Sub = new Int(-1).addHint("subtract");
|
|
5399
|
-
Num.Div = new Int(-1).addHint("divide");
|
|
5400
|
-
Num.Sqrt = new Rational(1, 2).addHint("root");
|
|
5401
|
-
Num.Zero = new Int(0);
|
|
5402
|
-
Num.One = new Int(1);
|
|
5403
|
-
Num.Ten = new Int(10);
|
|
5404
|
-
|
|
5405
|
-
// set identities here
|
|
5406
|
-
Add.prototype.identity = Num.Zero;
|
|
5407
|
-
Mul.prototype.identity = Num.One;
|
|
5464
|
+
}
|
|
5465
|
+
const NumNeg = new Int(-1).addHint("negate");
|
|
5466
|
+
const NumSub = new Int(-1).addHint("subtract");
|
|
5467
|
+
const NumDiv = new Int(-1).addHint("divide");
|
|
5468
|
+
const NumSqrt = new Rational(1, 2).addHint("root");
|
|
5469
|
+
const NumZero = new Int(0);
|
|
5470
|
+
const NumOne = new Int(1);
|
|
5471
|
+
const NumTen = new Int(10);
|
|
5408
5472
|
var parseError = function parseError(str, hash) {
|
|
5409
5473
|
// return int location of parsing error
|
|
5410
5474
|
throw new Error(hash.loc.first_column);
|
|
@@ -5467,10 +5531,56 @@ const parse = function parse(input, options) {
|
|
|
5467
5531
|
};
|
|
5468
5532
|
|
|
5469
5533
|
/* unit */
|
|
5470
|
-
|
|
5471
|
-
|
|
5534
|
+
class Unit extends Sym {
|
|
5535
|
+
constructor(symbol) {
|
|
5536
|
+
super();
|
|
5537
|
+
this.symbol = void 0;
|
|
5538
|
+
this.func = Unit;
|
|
5539
|
+
this.symbol = symbol;
|
|
5540
|
+
}
|
|
5541
|
+
args() {
|
|
5542
|
+
return [this.symbol];
|
|
5543
|
+
}
|
|
5544
|
+
recurse() {
|
|
5545
|
+
return this;
|
|
5546
|
+
}
|
|
5547
|
+
eval(vars = {}, options) {
|
|
5548
|
+
// This is called when comparing units. A unit doesn't affect the
|
|
5549
|
+
// numerical value of its coefficient, so this needs to be 1.
|
|
5550
|
+
//
|
|
5551
|
+
// On the other hand, things must not evaluate to the same thing if
|
|
5552
|
+
// they don't have the same type. I believe that's also true - form is
|
|
5553
|
+
// checked before numerical equivalence. I do not know where, though.
|
|
5554
|
+
// However, there are a couple tests checking this.
|
|
5555
|
+
return 1;
|
|
5556
|
+
}
|
|
5557
|
+
getUnits() {
|
|
5558
|
+
return [{
|
|
5559
|
+
unit: this.symbol,
|
|
5560
|
+
pow: 1
|
|
5561
|
+
}];
|
|
5562
|
+
}
|
|
5563
|
+
codegen() {
|
|
5564
|
+
return "1";
|
|
5565
|
+
}
|
|
5566
|
+
print() {
|
|
5567
|
+
return this.symbol;
|
|
5568
|
+
}
|
|
5569
|
+
tex() {
|
|
5570
|
+
return this.symbol;
|
|
5571
|
+
}
|
|
5572
|
+
|
|
5573
|
+
// Simplify units by replacing prefixes with multiplication
|
|
5574
|
+
collect(options) {
|
|
5575
|
+
if (_(baseUnits).has(this.symbol)) {
|
|
5576
|
+
return this;
|
|
5577
|
+
} else if (_(derivedUnits).has(this.symbol)) {
|
|
5578
|
+
return derivedUnits[this.symbol].conversion;
|
|
5579
|
+
} else {
|
|
5580
|
+
throw new Error("could not understand unit: " + this.symbol);
|
|
5581
|
+
}
|
|
5582
|
+
}
|
|
5472
5583
|
}
|
|
5473
|
-
Unit.prototype = new Symbol();
|
|
5474
5584
|
|
|
5475
5585
|
// If possible, replace unit prefixes with a multiplication.
|
|
5476
5586
|
//
|
|
@@ -5482,7 +5592,7 @@ var unprefixify = function unprefixify(symbol) {
|
|
|
5482
5592
|
}
|
|
5483
5593
|
|
|
5484
5594
|
// check for prefix
|
|
5485
|
-
var prefix = _(_(siPrefixes).keys()).find(
|
|
5595
|
+
var prefix = _(_(siPrefixes).keys()).find(testPrefix => {
|
|
5486
5596
|
return new RegExp("^" + testPrefix).test(symbol);
|
|
5487
5597
|
});
|
|
5488
5598
|
if (prefix) {
|
|
@@ -5522,18 +5632,18 @@ const unitParse = function unitParse(input) {
|
|
|
5522
5632
|
//
|
|
5523
5633
|
// denom is optionally null
|
|
5524
5634
|
|
|
5525
|
-
|
|
5526
|
-
_(parseResult.unit.num).each(
|
|
5635
|
+
const unitArray = [];
|
|
5636
|
+
_(parseResult.unit.num).each(unitSpec => {
|
|
5527
5637
|
unitArray.push(new Pow(unprefixify(unitSpec.name), new Int(unitSpec.pow)));
|
|
5528
5638
|
});
|
|
5529
|
-
_(parseResult.unit.denom).each(
|
|
5639
|
+
_(parseResult.unit.denom).each(unitSpec => {
|
|
5530
5640
|
unitArray.push(new Pow(unprefixify(unitSpec.name), new Int(-1 * unitSpec.pow)));
|
|
5531
5641
|
});
|
|
5532
5642
|
var unit = new Mul(unitArray).flatten();
|
|
5533
5643
|
if (parseResult.type === "unitMagnitude") {
|
|
5534
5644
|
// in the first case we have a magnitude coefficient as well as the
|
|
5535
5645
|
// unit itself.
|
|
5536
|
-
var coefArray = [new Float(+parseResult.magnitude)]
|
|
5646
|
+
var coefArray = [new Float(+parseResult.magnitude), ...unitArray];
|
|
5537
5647
|
var expr = new Mul(coefArray);
|
|
5538
5648
|
return {
|
|
5539
5649
|
parsed: true,
|
|
@@ -5557,50 +5667,6 @@ const unitParse = function unitParse(input) {
|
|
|
5557
5667
|
};
|
|
5558
5668
|
}
|
|
5559
5669
|
};
|
|
5560
|
-
_.extend(Unit.prototype, {
|
|
5561
|
-
func: Unit,
|
|
5562
|
-
args: function () {
|
|
5563
|
-
return [this.symbol];
|
|
5564
|
-
},
|
|
5565
|
-
recurse: function () {
|
|
5566
|
-
return this;
|
|
5567
|
-
},
|
|
5568
|
-
eval: function (vars, options) {
|
|
5569
|
-
// This is called when comparing units. A unit doesn't affect the
|
|
5570
|
-
// numerical value of its coefficient, so this needs to be 1.
|
|
5571
|
-
//
|
|
5572
|
-
// On the other hand, things must not evaluate to the same thing if
|
|
5573
|
-
// they don't have the same type. I believe that's also true - form is
|
|
5574
|
-
// checked before numerical equivalence. I do not know where, though.
|
|
5575
|
-
// However, there are a couple tests checking this.
|
|
5576
|
-
return 1;
|
|
5577
|
-
},
|
|
5578
|
-
getUnits: function () {
|
|
5579
|
-
return [{
|
|
5580
|
-
unit: this.symbol,
|
|
5581
|
-
pow: 1
|
|
5582
|
-
}];
|
|
5583
|
-
},
|
|
5584
|
-
codegen: function () {
|
|
5585
|
-
return "1";
|
|
5586
|
-
},
|
|
5587
|
-
print: function () {
|
|
5588
|
-
return this.symbol;
|
|
5589
|
-
},
|
|
5590
|
-
tex: function () {
|
|
5591
|
-
return this.symbol;
|
|
5592
|
-
},
|
|
5593
|
-
// Simplify units by replacing prefixes with multiplication
|
|
5594
|
-
collect: function (options) {
|
|
5595
|
-
if (_(baseUnits).has(this.symbol)) {
|
|
5596
|
-
return this;
|
|
5597
|
-
} else if (_(derivedUnits).has(this.symbol)) {
|
|
5598
|
-
return derivedUnits[this.symbol].conversion;
|
|
5599
|
-
} else {
|
|
5600
|
-
throw new Error("could not understand unit: " + this.symbol);
|
|
5601
|
-
}
|
|
5602
|
-
}
|
|
5603
|
-
});
|
|
5604
5670
|
var baseUnits = {
|
|
5605
5671
|
m: new Unit("m"),
|
|
5606
5672
|
// Note: kg is the SI base unit but we use g for consistency
|
|
@@ -5633,31 +5699,23 @@ var siPrefixes = {
|
|
|
5633
5699
|
};
|
|
5634
5700
|
|
|
5635
5701
|
// Use these two values to mark a unit as either SI-prefixable or not.
|
|
5636
|
-
|
|
5637
|
-
|
|
5638
|
-
|
|
5702
|
+
const hasPrefixes = "hasPrefixes";
|
|
5703
|
+
const hasntPrefixes = "hasntPrefixes";
|
|
5704
|
+
const makeAlias = function makeAlias(str, prefixes) {
|
|
5639
5705
|
var splits = str.split("|");
|
|
5640
5706
|
var coefficientStr = splits[0].trim();
|
|
5641
5707
|
var unitsStr = splits[1].trim();
|
|
5642
|
-
var coefficient =
|
|
5708
|
+
var coefficient = NumOne;
|
|
5643
5709
|
if (coefficientStr !== "") {
|
|
5644
5710
|
coefficient = parse(coefficientStr).expr;
|
|
5645
5711
|
}
|
|
5646
5712
|
var numdenomStr = unitsStr.split("/");
|
|
5647
5713
|
var numdenom = [coefficient];
|
|
5648
5714
|
if (numdenomStr[0]) {
|
|
5649
|
-
numdenomStr[0].split(" ").filter(
|
|
5650
|
-
return x !== "";
|
|
5651
|
-
}).map(function (x) {
|
|
5652
|
-
numdenom.push(new Unit(x));
|
|
5653
|
-
});
|
|
5715
|
+
numdenomStr[0].split(" ").filter(x => x !== "").forEach(x => numdenom.push(new Unit(x)));
|
|
5654
5716
|
}
|
|
5655
5717
|
if (numdenomStr[1]) {
|
|
5656
|
-
numdenomStr[1].split(" ").filter(
|
|
5657
|
-
return x !== "";
|
|
5658
|
-
}).map(function (x) {
|
|
5659
|
-
numdenom.push(new Pow(new Unit(x), Num.Div));
|
|
5660
|
-
});
|
|
5718
|
+
numdenomStr[1].split(" ").filter(x => x !== "").forEach(x => numdenom.push(new Pow(new Unit(x), NumDiv)));
|
|
5661
5719
|
}
|
|
5662
5720
|
return {
|
|
5663
5721
|
conversion: new Mul(numdenom),
|
|
@@ -5814,18 +5872,8 @@ var derivedUnits = {
|
|
|
5814
5872
|
// other
|
|
5815
5873
|
Hz: makeAlias("| / s", hasPrefixes)
|
|
5816
5874
|
};
|
|
5817
|
-
const Zero =
|
|
5818
|
-
const One =
|
|
5819
|
-
|
|
5820
|
-
function _extends() {
|
|
5821
|
-
return _extends = Object.assign ? Object.assign.bind() : function (n) {
|
|
5822
|
-
for (var e = 1; e < arguments.length; e++) {
|
|
5823
|
-
var t = arguments[e];
|
|
5824
|
-
for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]);
|
|
5825
|
-
}
|
|
5826
|
-
return n;
|
|
5827
|
-
}, _extends.apply(null, arguments);
|
|
5828
|
-
}
|
|
5875
|
+
const Zero = NumZero;
|
|
5876
|
+
const One = NumOne;
|
|
5829
5877
|
|
|
5830
5878
|
// Assumes that both expressions have already been parsed
|
|
5831
5879
|
// TODO(alex): be able to pass a random() function to compare()
|