@khanacademy/kmath 0.0.0-PR862-20231207182125 → 0.0.0-PR875-20250221232857

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/index.js CHANGED
@@ -2,17 +2,61 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var perseusCore = require('@khanacademy/perseus-core');
6
5
  var _ = require('underscore');
6
+ var perseusCore = require('@khanacademy/perseus-core');
7
+ var kmath = require('@khanacademy/kmath');
8
+ var $ = require('jquery');
7
9
 
8
10
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
9
11
 
10
12
  var ___default = /*#__PURE__*/_interopDefaultLegacy(_);
13
+ var $__default = /*#__PURE__*/_interopDefaultLegacy($);
14
+
15
+ /**
16
+ * Adds the given perseus library version information to the __perseus_debug__
17
+ * object and ensures that the object is attached to `globalThis` (`window` in
18
+ * browser environments).
19
+ *
20
+ * This allows each library to provide runtime version information to assist in
21
+ * debugging in production environments.
22
+ */
23
+ const addLibraryVersionToPerseusDebug = (libraryName, libraryVersion) => {
24
+ // If the library version is the default value, then we don't want to
25
+ // prefix it with a "v" to indicate that it is a version number.
26
+ let prefix = "v";
27
+ if (libraryVersion === "__lib_version__") {
28
+ prefix = "";
29
+ }
30
+ const formattedVersion = `${prefix}${libraryVersion}`;
31
+ if (typeof globalThis !== "undefined") {
32
+ globalThis.__perseus_debug__ = globalThis.__perseus_debug__ ?? {};
33
+ const existingVersionEntry = globalThis.__perseus_debug__[libraryName];
34
+ if (existingVersionEntry) {
35
+ // If we already have an entry and it doesn't match the registered
36
+ // version, we morph the entry into an array and log a warning.
37
+ if (existingVersionEntry !== formattedVersion) {
38
+ // Existing entry might be an array already (oops, at least 2
39
+ // versions of the library already loaded!).
40
+ const allVersions = Array.isArray(existingVersionEntry) ? existingVersionEntry : [existingVersionEntry];
41
+ allVersions.push(formattedVersion);
42
+ globalThis.__perseus_debug__[libraryName] = allVersions;
43
+
44
+ // eslint-disable-next-line no-console
45
+ console.warn(`Multiple versions of ${libraryName} loaded on this page: ${allVersions.sort().join(", ")}`);
46
+ }
47
+ } else {
48
+ globalThis.__perseus_debug__[libraryName] = formattedVersion;
49
+ }
50
+ } else {
51
+ // eslint-disable-next-line no-console
52
+ console.warn(`globalThis not found found (${formattedVersion})`);
53
+ }
54
+ };
11
55
 
12
56
  // This file is processed by a Rollup plugin (replace) to inject the production
13
57
  const libName = "@khanacademy/kmath";
14
- const libVersion = "0.1.7";
15
- perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
58
+ const libVersion = "0.3.5";
59
+ addLibraryVersionToPerseusDebug(libName, libVersion);
16
60
 
17
61
  /**
18
62
  * Number Utils
@@ -40,7 +84,7 @@ function equal$4(x, y, tolerance) {
40
84
  }
41
85
  return Math.abs(x - y) < tolerance;
42
86
  }
43
- function sign(x, tolerance) /* Should be: 0 | 1 | -1 */{
87
+ function sign$1(x, tolerance) /* Should be: 0 | 1 | -1 */{
44
88
  return equal$4(x, 0, tolerance) ? 0 : Math.abs(x) / x;
45
89
  }
46
90
  function isInteger(num, tolerance) {
@@ -109,7 +153,7 @@ var number = /*#__PURE__*/Object.freeze({
109
153
  EPSILON: EPSILON,
110
154
  is: is$2,
111
155
  equal: equal$4,
112
- sign: sign,
156
+ sign: sign$1,
113
157
  isInteger: isInteger,
114
158
  round: round$2,
115
159
  roundTo: roundTo$2,
@@ -128,6 +172,20 @@ function arraySum(array) {
128
172
  function arrayProduct(array) {
129
173
  return array.reduce((memo, arg) => memo * arg, 1);
130
174
  }
175
+ function zip() {
176
+ for (var _len = arguments.length, arrays = new Array(_len), _key = 0; _key < _len; _key++) {
177
+ arrays[_key] = arguments[_key];
178
+ }
179
+ const n = Math.min(...arrays.map(a => a.length));
180
+ const results = [];
181
+ for (let i = 0; i < n; i++) {
182
+ results.push(arrays.map(a => a[i]));
183
+ }
184
+ return results;
185
+ }
186
+ function map(pair, f) {
187
+ return [f(pair[0], 0), f(pair[1], 1)];
188
+ }
131
189
 
132
190
  /**
133
191
  * Checks if the given vector contains only numbers and, optionally, is of the
@@ -137,8 +195,9 @@ function arrayProduct(array) {
137
195
  * is([1, "Hello", 3]) -> false
138
196
  * is([1, 2, 3], 1) -> false
139
197
  */
198
+
140
199
  function is$1(vec, dimension) {
141
- if (!___default["default"].isArray(vec)) {
200
+ if (!Array.isArray(vec)) {
142
201
  return false;
143
202
  }
144
203
  if (dimension !== undefined && vec.length !== dimension) {
@@ -158,7 +217,7 @@ function length(v) {
158
217
  }
159
218
  // Dot product of two vectors
160
219
  function dot(a, b) {
161
- const zipped = ___default["default"].zip(a, b);
220
+ const zipped = zip(a, b);
162
221
  const multiplied = zipped.map(arrayProduct);
163
222
  return arraySum(multiplied);
164
223
  }
@@ -167,14 +226,14 @@ function dot(a, b) {
167
226
  *
168
227
  * add([1, 2], [3, 4]) -> [4, 6]
169
228
  */
170
- function add() {
171
- const zipped = ___default["default"].zip(...arguments);
229
+ function add$1() {
230
+ const zipped = zip(...arguments);
172
231
  // @ts-expect-error - TS2322 - Type 'number[]' is not assignable to type 'V'.
173
232
  return zipped.map(arraySum);
174
233
  }
175
234
  function subtract(v1, v2) {
176
235
  // @ts-expect-error - TS2322 - Type 'number[]' is not assignable to type 'V'.
177
- return ___default["default"].zip(v1, v2).map(dim => dim[0] - dim[1]);
236
+ return zip(v1, v2).map(dim => dim[0] - dim[1]);
178
237
  }
179
238
  function negate(v) {
180
239
  // @ts-expect-error - TS2322 - Type 'number[]' is not assignable to type 'V'.
@@ -191,11 +250,7 @@ function scale(v1, scalar) {
191
250
  });
192
251
  }
193
252
  function equal$3(v1, v2, tolerance) {
194
- // _.zip will nicely deal with the lengths, going through
195
- // the length of the longest vector. knumber.equal then
196
- // returns false for any number compared to the undefined
197
- // passed in if one of the vectors is shorter.
198
- return ___default["default"].zip(v1, v2).every(pair => equal$4(pair[0], pair[1], tolerance));
253
+ return v1.length === v2.length && zip(v1, v2).every(pair => equal$4(pair[0], pair[1], tolerance));
199
254
  }
200
255
  function codirectional(v1, v2, tolerance) {
201
256
  // The origin is trivially codirectional with all other vectors.
@@ -208,7 +263,7 @@ function codirectional(v1, v2, tolerance) {
208
263
  v2 = normalize(v2);
209
264
  return equal$3(v1, v2, tolerance);
210
265
  }
211
- function collinear(v1, v2, tolerance) {
266
+ function collinear$1(v1, v2, tolerance) {
212
267
  return codirectional(v1, v2, tolerance) || codirectional(v1, negate(v2), tolerance);
213
268
  }
214
269
 
@@ -285,6 +340,7 @@ function round$1(vec, precision) {
285
340
  }
286
341
 
287
342
  // Round each number to the nearest increment
343
+
288
344
  function roundTo$1(vec, increment) {
289
345
  // @ts-expect-error - TS2322 - Type 'number[]' is not assignable to type 'V'.
290
346
  return vec.map((elem, i) => roundTo$2(elem, increment[i] || increment));
@@ -298,19 +354,21 @@ function ceilTo$1(vec, increment) {
298
354
  return vec.map((elem, i) => ceilTo$2(elem, increment[i] || increment));
299
355
  }
300
356
 
301
- var vector = /*#__PURE__*/Object.freeze({
357
+ var vector$1 = /*#__PURE__*/Object.freeze({
302
358
  __proto__: null,
359
+ zip: zip,
360
+ map: map,
303
361
  is: is$1,
304
362
  normalize: normalize,
305
363
  length: length,
306
364
  dot: dot,
307
- add: add,
365
+ add: add$1,
308
366
  subtract: subtract,
309
367
  negate: negate,
310
368
  scale: scale,
311
369
  equal: equal$3,
312
370
  codirectional: codirectional,
313
- collinear: collinear,
371
+ collinear: collinear$1,
314
372
  polarRadFromCart: polarRadFromCart$1,
315
373
  polarDegFromCart: polarDegFromCart$1,
316
374
  cartFromPolarRad: cartFromPolarRad$1,
@@ -338,14 +396,14 @@ function rotateRad(point, theta, center) {
338
396
  if (center === undefined) {
339
397
  return rotateRad$1(point, theta);
340
398
  } else {
341
- return add(center, rotateRad$1(subtract(point, center), theta));
399
+ return add$1(center, rotateRad$1(subtract(point, center), theta));
342
400
  }
343
401
  }
344
402
  function rotateDeg(point, theta, center) {
345
403
  if (center === undefined) {
346
404
  return rotateDeg$1(point, theta);
347
405
  } else {
348
- return add(center, rotateDeg$1(subtract(point, center), theta));
406
+ return add$1(center, rotateDeg$1(subtract(point, center), theta));
349
407
  }
350
408
  }
351
409
 
@@ -369,7 +427,7 @@ function reflectOverLine(point, line) {
369
427
  const pv = subtract(point, line[0]);
370
428
  const projectedPv = projection(pv, lv);
371
429
  const reflectedPv = subtract(scale(projectedPv, 2), pv);
372
- return add(line[0], reflectedPv);
430
+ return add$1(line[0], reflectedPv);
373
431
  }
374
432
 
375
433
  /**
@@ -397,8 +455,8 @@ function compare(point1, point2, equalityTolerance) /* TODO: convert to -1 | 0 |
397
455
  const is = is$1;
398
456
 
399
457
  // Add and subtract vector(s)
400
- const addVector = add;
401
- const addVectors = add;
458
+ const addVector = add$1;
459
+ const addVectors = add$1;
402
460
  const subtractVector = subtract;
403
461
  const equal$2 = equal$3;
404
462
 
@@ -456,7 +514,7 @@ function equal$1(line1, line2, tolerance) {
456
514
  // Compare the directions of the lines
457
515
  const v1 = subtract(line1[1], line1[0]);
458
516
  const v2 = subtract(line2[1], line2[0]);
459
- if (!collinear(v1, v2, tolerance)) {
517
+ if (!collinear$1(v1, v2, tolerance)) {
460
518
  return false;
461
519
  }
462
520
  // If the start point is the same for the two lines, then they are the same
@@ -466,7 +524,7 @@ function equal$1(line1, line2, tolerance) {
466
524
  // Make sure that the direction to get from line1 to
467
525
  // line2 is the same as the direction of the lines
468
526
  const line1ToLine2Vector = subtract(line2[0], line1[0]);
469
- return collinear(v1, line1ToLine2Vector, tolerance);
527
+ return collinear$1(v1, line1ToLine2Vector, tolerance);
470
528
  }
471
529
 
472
530
  var line = /*#__PURE__*/Object.freeze({
@@ -497,10 +555,626 @@ var ray = /*#__PURE__*/Object.freeze({
497
555
  equal: equal
498
556
  });
499
557
 
558
+ /**
559
+ * A collection of geomtry-related utility functions
560
+ */
561
+
562
+ // This should really be a readonly tuple of [number, number]
563
+
564
+ // Given a number, return whether it is positive (1), negative (-1), or zero (0)
565
+ function sign(val) {
566
+ if (perseusCore.approximateEqual(val, 0)) {
567
+ return 0;
568
+ }
569
+ return val > 0 ? 1 : -1;
570
+ }
571
+
572
+ // Determine whether three points are collinear (0), for a clockwise turn (negative),
573
+ // or counterclockwise turn (positive)
574
+ function ccw(a, b, c) {
575
+ return (b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1]);
576
+ }
577
+ function collinear(a, b, c) {
578
+ return perseusCore.approximateEqual(ccw(a, b, c), 0);
579
+ }
580
+
581
+ // Given rect bounding points A and B, whether point C is inside the rect
582
+ function pointInRect(a, b, c) {
583
+ return c[0] <= Math.max(a[0], b[0]) && c[0] >= Math.min(a[0], b[0]) && c[1] <= Math.max(a[1], b[1]) && c[1] >= Math.min(a[1], b[1]);
584
+ }
585
+
586
+ // Whether line segment AB intersects line segment CD
587
+ // http://www.geeksforgeeks.org/check-if-two-given-line-segments-intersect/
588
+ function intersects(ab, cd) {
589
+ const triplets = [[ab[0], ab[1], cd[0]], [ab[0], ab[1], cd[1]], [cd[0], cd[1], ab[0]], [cd[0], cd[1], ab[1]]];
590
+ const orientations = ___default["default"].map(triplets, function (triplet) {
591
+ return sign(ccw(...triplet));
592
+ });
593
+ if (orientations[0] !== orientations[1] && orientations[2] !== orientations[3]) {
594
+ return true;
595
+ }
596
+ for (let i = 0; i < 4; i++) {
597
+ if (orientations[i] === 0 && pointInRect(...triplets[i])) {
598
+ return true;
599
+ }
600
+ }
601
+ return false;
602
+ }
603
+
604
+ // Whether any two sides of a polygon intersect each other
605
+ function polygonSidesIntersect(vertices) {
606
+ for (let i = 0; i < vertices.length; i++) {
607
+ for (let k = i + 1; k < vertices.length; k++) {
608
+ // If any two vertices are the same point, sides overlap
609
+ if (kmath.point.equal(vertices[i], vertices[k])) {
610
+ return true;
611
+ }
612
+
613
+ // Find the other end of the sides starting at vertices i and k
614
+ const iNext = (i + 1) % vertices.length;
615
+ const kNext = (k + 1) % vertices.length;
616
+
617
+ // Adjacent sides always intersect (at the vertex); skip those
618
+ if (iNext === k || kNext === i) {
619
+ continue;
620
+ }
621
+ const side1 = [vertices[i], vertices[iNext]];
622
+ const side2 = [vertices[k], vertices[kNext]];
623
+ if (intersects(side1, side2)) {
624
+ return true;
625
+ }
626
+ }
627
+ }
628
+ return false;
629
+ }
630
+ function vector(a, b) {
631
+ return ___default["default"].map(___default["default"].zip(a, b), function (pair) {
632
+ return pair[0] - pair[1];
633
+ });
634
+ }
635
+ function reverseVector(vector) {
636
+ return [-vector[0], -vector[1]];
637
+ }
638
+
639
+ // Returns whether connecting the given sequence of `points` forms a clockwise
640
+ // path (assuming a closed loop, where the last point connects back to the
641
+ // first).
642
+ function clockwise(points) {
643
+ const segments = ___default["default"].zip(points, points.slice(1).concat(points.slice(0, 1)));
644
+ const areas = ___default["default"].map(segments, function (segment) {
645
+ const p1 = segment[0];
646
+ const p2 = segment[1];
647
+ return (p2[0] - p1[0]) * (p2[1] + p1[1]);
648
+ });
649
+ return kmath.sum(areas) > 0;
650
+ }
651
+ function magnitude(v) {
652
+ return Math.sqrt(___default["default"].reduce(v, function (memo, el) {
653
+ // @ts-expect-error - TS2345 - Argument of type 'Coord' is not assignable to parameter of type 'number'.
654
+ return memo + Math.pow(el, 2);
655
+ }, 0));
656
+ }
657
+ function dotProduct(a, b) {
658
+ return ___default["default"].reduce(___default["default"].zip(a, b), function (memo, pair) {
659
+ return memo + pair[0] * pair[1];
660
+ }, 0);
661
+ }
662
+ function sideLengths(coords) {
663
+ const segments = ___default["default"].zip(coords, rotate(coords));
664
+ return segments.map(function (segment) {
665
+ // @ts-expect-error - TS2345 - Argument of type 'number[]' is not assignable to parameter of type 'readonly Coord[]'. | TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
666
+ return magnitude(vector(...segment));
667
+ });
668
+ }
669
+
670
+ // Based on http://math.stackexchange.com/a/151149
671
+ function angleMeasures(coords) {
672
+ const triplets = ___default["default"].zip(rotate(coords, -1), coords, rotate(coords, 1));
673
+ const offsets = ___default["default"].map(triplets, function (triplet) {
674
+ const p = vector(triplet[1], triplet[0]);
675
+ const q = vector(triplet[2], triplet[1]);
676
+ // @ts-expect-error - TS2345 - Argument of type 'number[]' is not assignable to parameter of type 'Coord'. | TS2345 - Argument of type 'number[]' is not assignable to parameter of type 'readonly Coord[]'. | TS2345 - Argument of type 'number[]' is not assignable to parameter of type 'readonly Coord[]'.
677
+ const raw = Math.acos(dotProduct(p, q) / (magnitude(p) * magnitude(q)));
678
+ // @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
679
+ return sign(ccw(...triplet)) > 0 ? raw : -raw;
680
+ });
681
+ const sum = ___default["default"].reduce(offsets, function (memo, arg) {
682
+ return memo + arg;
683
+ }, 0);
684
+ return ___default["default"].map(offsets, function (offset) {
685
+ return sum > 0 ? Math.PI - offset : Math.PI + offset;
686
+ });
687
+ }
688
+
689
+ // Whether two polygons are similar (or if specified, congruent)
690
+ function similar(coords1, coords2, tolerance) {
691
+ if (coords1.length !== coords2.length) {
692
+ return false;
693
+ }
694
+ const n = coords1.length;
695
+ const angles1 = angleMeasures(coords1);
696
+ const angles2 = angleMeasures(coords2);
697
+ const sides1 = sideLengths(coords1);
698
+ const sides2 = sideLengths(coords2);
699
+ for (let i = 0; i < 2 * n; i++) {
700
+ let angles = angles2.slice();
701
+ let sides = sides2.slice();
702
+
703
+ // Reverse angles and sides to allow matching reflected polygons
704
+ if (i >= n) {
705
+ angles.reverse();
706
+ sides.reverse();
707
+ // Since sides are calculated from two coordinates,
708
+ // simply reversing results in an off by one error
709
+ // @ts-expect-error - TS4104 - The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
710
+ sides = rotate(sides, 1);
711
+ }
712
+
713
+ // @ts-expect-error - TS4104 - The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
714
+ angles = rotate(angles, i);
715
+ // @ts-expect-error - TS4104 - The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
716
+ sides = rotate(sides, i);
717
+ if (perseusCore.approximateDeepEqual(angles1, angles)) {
718
+ const sidePairs = ___default["default"].zip(sides1, sides);
719
+ const factors = ___default["default"].map(sidePairs, function (pair) {
720
+ return pair[0] / pair[1];
721
+ });
722
+ const same = ___default["default"].all(factors, function (factor) {
723
+ return perseusCore.approximateEqual(factors[0], factor);
724
+ });
725
+ const congruentEnough = ___default["default"].all(sidePairs, function (pair) {
726
+ return kmath.number.equal(pair[0], pair[1], tolerance);
727
+ });
728
+ if (same && congruentEnough) {
729
+ return true;
730
+ }
731
+ }
732
+ }
733
+ return false;
734
+ }
735
+
736
+ // Given triangle with sides ABC return angle opposite side C in degrees
737
+ function lawOfCosines(a, b, c) {
738
+ return Math.acos((a * a + b * b - c * c) / (2 * a * b)) * 180 / Math.PI;
739
+ }
740
+ function canonicalSineCoefficients(_ref) {
741
+ let [amplitude, angularFrequency, phase, verticalOffset] = _ref;
742
+ // For a curve of the form f(x) = a * Sin(b * x - c) + d,
743
+ // this function ensures that a, b > 0, and c is its
744
+ // smallest possible positive value.
745
+
746
+ // Guarantee a > 0
747
+ if (amplitude < 0) {
748
+ amplitude *= -1;
749
+ angularFrequency *= -1;
750
+ phase *= -1;
751
+ }
752
+ const period = 2 * Math.PI;
753
+ // Guarantee b > 0
754
+ if (angularFrequency < 0) {
755
+ angularFrequency *= -1;
756
+ phase *= -1;
757
+ phase += period / 2;
758
+ }
759
+
760
+ // Guarantee c is smallest possible positive value
761
+ while (phase > 0) {
762
+ phase -= period;
763
+ }
764
+ while (phase < 0) {
765
+ phase += period;
766
+ }
767
+ return [amplitude, angularFrequency, phase, verticalOffset];
768
+ }
769
+
770
+ // e.g. rotate([1, 2, 3]) -> [2, 3, 1]
771
+ function rotate(array, n) {
772
+ n = typeof n === "undefined" ? 1 : n % array.length;
773
+ return array.slice(n).concat(array.slice(0, n));
774
+ }
775
+ function getLineEquation(first, second) {
776
+ if (perseusCore.approximateEqual(first[0], second[0])) {
777
+ return "x = " + first[0].toFixed(3);
778
+ }
779
+ const m = (second[1] - first[1]) / (second[0] - first[0]);
780
+ const b = first[1] - m * first[0];
781
+ return "y = " + m.toFixed(3) + "x + " + b.toFixed(3);
782
+ }
783
+
784
+ // Stolen from the wikipedia article
785
+ // http://en.wikipedia.org/wiki/Line-line_intersection
786
+ function getLineIntersection(
787
+ // TODO(LP-10725): update these to be 2-tuples
788
+ firstPoints, secondPoints) {
789
+ const x1 = firstPoints[0][0];
790
+ const y1 = firstPoints[0][1];
791
+ const x2 = firstPoints[1][0];
792
+ const y2 = firstPoints[1][1];
793
+ const x3 = secondPoints[0][0];
794
+ const y3 = secondPoints[0][1];
795
+ const x4 = secondPoints[1][0];
796
+ const y4 = secondPoints[1][1];
797
+ const determinant = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
798
+ if (Math.abs(determinant) < 1e-9) {
799
+ return "Lines are parallel";
800
+ }
801
+ const x = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / determinant;
802
+ const y = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / determinant;
803
+ return "Intersection: (" + x.toFixed(3) + ", " + y.toFixed(3) + ")";
804
+ }
805
+
806
+ var geometry = /*#__PURE__*/Object.freeze({
807
+ __proto__: null,
808
+ sign: sign,
809
+ ccw: ccw,
810
+ collinear: collinear,
811
+ intersects: intersects,
812
+ polygonSidesIntersect: polygonSidesIntersect,
813
+ vector: vector,
814
+ reverseVector: reverseVector,
815
+ clockwise: clockwise,
816
+ magnitude: magnitude,
817
+ angleMeasures: angleMeasures,
818
+ similar: similar,
819
+ lawOfCosines: lawOfCosines,
820
+ canonicalSineCoefficients: canonicalSineCoefficients,
821
+ rotate: rotate,
822
+ getLineEquation: getLineEquation,
823
+ getLineIntersection: getLineIntersection
824
+ });
825
+
826
+ // This file contains helper functions for working with angles.
827
+ function convertDegreesToRadians(degrees) {
828
+ return degrees / 180 * Math.PI;
829
+ }
830
+ function convertRadiansToDegrees(radians) {
831
+ const degree = radians / Math.PI * 180;
832
+ // Account for floating point errors.
833
+ return Number(degree.toPrecision(15));
834
+ }
835
+
836
+ // Returns a value between -180 and 180, inclusive. The angle is measured
837
+ // between the positive x-axis and the given vector.
838
+ function calculateAngleInDegrees(_ref) {
839
+ let [x, y] = _ref;
840
+ return Math.atan2(y, x) * 180 / Math.PI;
841
+ }
842
+
843
+ // Converts polar coordinates to cartesian. The th(eta) parameter is in degrees.
844
+ function polar(r, th) {
845
+ if (typeof r === "number") {
846
+ r = [r, r];
847
+ }
848
+ th = th * Math.PI / 180;
849
+ return [r[0] * Math.cos(th), r[1] * Math.sin(th)];
850
+ }
851
+ // This function calculates the angle between two points and an optional vertex.
852
+ // If the vertex is not provided, the angle is measured between the two points.
853
+ // This does not account for reflex angles or clockwise position.
854
+ const getAngleFromVertex = (point, vertex) => {
855
+ const x = point[0] - vertex[0];
856
+ const y = point[1] - vertex[1];
857
+ if (!x && !y) {
858
+ return 0;
859
+ }
860
+ return (180 + Math.atan2(-y, -x) * 180 / Math.PI + 360) % 360;
861
+ };
862
+
863
+ // This function calculates the clockwise angle between three points,
864
+ // and is used to generate the labels and equation strings of the
865
+ // current angle for the interactive graph.
866
+ const getClockwiseAngle = function (coords) {
867
+ let allowReflexAngles = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
868
+ const coordsCopy = [...coords];
869
+ // The coords are saved as [point1, vertex, point2] in the interactive graph
870
+ const areClockwise = clockwise([coordsCopy[0], coordsCopy[2], coordsCopy[1]]);
871
+
872
+ // We may need to reverse the coordinates if we allow
873
+ // reflex angles and the points are not in clockwise order.
874
+ const shouldReverseCoords = !areClockwise || allowReflexAngles;
875
+
876
+ // Reverse the coordinates accordingly to ensure the angle is calculated correctly
877
+ const clockwiseCoords = shouldReverseCoords ? coordsCopy.reverse() : coordsCopy;
878
+
879
+ // Calculate the angles between the two points and get the difference
880
+ // between the two angles to get the clockwise angle.
881
+ const startAngle = getAngleFromVertex(clockwiseCoords[0], clockwiseCoords[1]);
882
+ const endAngle = getAngleFromVertex(clockwiseCoords[2], clockwiseCoords[1]);
883
+ const angle = (startAngle + 360 - endAngle) % 360;
884
+ return angle;
885
+ };
886
+
887
+ var angles = /*#__PURE__*/Object.freeze({
888
+ __proto__: null,
889
+ convertDegreesToRadians: convertDegreesToRadians,
890
+ convertRadiansToDegrees: convertRadiansToDegrees,
891
+ calculateAngleInDegrees: calculateAngleInDegrees,
892
+ polar: polar,
893
+ getAngleFromVertex: getAngleFromVertex,
894
+ getClockwiseAngle: getClockwiseAngle
895
+ });
896
+
897
+ // TODO: there's another, very similar getSinusoidCoefficients function
898
+ // they should probably be merged
899
+ function getSinusoidCoefficients(coords) {
900
+ // It's assumed that p1 is the root and p2 is the first peak
901
+ const p1 = coords[0];
902
+ const p2 = coords[1];
903
+
904
+ // Resulting coefficients are canonical for this sine curve
905
+ const amplitude = p2[1] - p1[1];
906
+ const angularFrequency = Math.PI / (2 * (p2[0] - p1[0]));
907
+ const phase = p1[0] * angularFrequency;
908
+ const verticalOffset = p1[1];
909
+ return [amplitude, angularFrequency, phase, verticalOffset];
910
+ }
911
+ // TODO: there's another, very similar getQuadraticCoefficients function
912
+ // they should probably be merged
913
+ function getQuadraticCoefficients(coords) {
914
+ const p1 = coords[0];
915
+ const p2 = coords[1];
916
+ const p3 = coords[2];
917
+ const denom = (p1[0] - p2[0]) * (p1[0] - p3[0]) * (p2[0] - p3[0]);
918
+ if (denom === 0) {
919
+ // Many of the callers assume that the return value is always defined.
920
+ // @ts-expect-error - TS2322 - Type 'undefined' is not assignable to type 'QuadraticCoefficient'.
921
+ return;
922
+ }
923
+ const a = (p3[0] * (p2[1] - p1[1]) + p2[0] * (p1[1] - p3[1]) + p1[0] * (p3[1] - p2[1])) / denom;
924
+ const b = (p3[0] * p3[0] * (p1[1] - p2[1]) + p2[0] * p2[0] * (p3[1] - p1[1]) + p1[0] * p1[0] * (p2[1] - p3[1])) / denom;
925
+ const c = (p2[0] * p3[0] * (p2[0] - p3[0]) * p1[1] + p3[0] * p1[0] * (p3[0] - p1[0]) * p2[1] + p1[0] * p2[0] * (p1[0] - p2[0]) * p3[1]) / denom;
926
+ return [a, b, c];
927
+ }
928
+
929
+ var coefficients = /*#__PURE__*/Object.freeze({
930
+ __proto__: null,
931
+ getSinusoidCoefficients: getSinusoidCoefficients,
932
+ getQuadraticCoefficients: getQuadraticCoefficients
933
+ });
934
+
935
+ const KhanMath = {
936
+ // Simplify formulas before display
937
+ cleanMath: function (expr) {
938
+ return typeof expr === "string" ? expr.replace(/\+\s*-/g, "- ").replace(/-\s*-/g, "+ ").replace(/\^1/g, "") : expr;
939
+ },
940
+ // Bound a number by 1e-6 and 1e20 to avoid exponents after toString
941
+ bound: function (num) {
942
+ if (num === 0) {
943
+ return num;
944
+ }
945
+ if (num < 0) {
946
+ return -KhanMath.bound(-num);
947
+ }
948
+ return Math.max(1e-6, Math.min(num, 1e20));
949
+ },
950
+ factorial: function (x) {
951
+ if (x <= 1) {
952
+ return x;
953
+ }
954
+ return x * KhanMath.factorial(x - 1);
955
+ },
956
+ getGCD: function (a, b) {
957
+ if (arguments.length > 2) {
958
+ // TODO(kevinb): rewrite using rest args instead of arguments
959
+ // eslint-disable-next-line prefer-rest-params
960
+ const rest = [].slice.call(arguments, 1);
961
+ // @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
962
+ return KhanMath.getGCD(a, KhanMath.getGCD(...rest));
963
+ }
964
+ let mod;
965
+ a = Math.abs(a);
966
+ b = Math.abs(b);
967
+ while (b) {
968
+ mod = a % b;
969
+ a = b;
970
+ b = mod;
971
+ }
972
+ return a;
973
+ },
974
+ getLCM: function (a, b) {
975
+ if (arguments.length > 2) {
976
+ // TODO(kevinb): rewrite using rest args instead of arguments
977
+ // eslint-disable-next-line prefer-rest-params
978
+ const rest = [].slice.call(arguments, 1);
979
+ // @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
980
+ return KhanMath.getLCM(a, KhanMath.getLCM(...rest));
981
+ }
982
+ return Math.abs(a * b) / KhanMath.getGCD(a, b);
983
+ },
984
+ primes: [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97],
985
+ isPrime: function (n) {
986
+ if (n <= 1) {
987
+ return false;
988
+ }
989
+ if (n < 101) {
990
+ return !!$__default["default"].grep(KhanMath.primes, function (p, i) {
991
+ return Math.abs(p - n) <= 0.5;
992
+ }).length;
993
+ }
994
+ if (n <= 1 || n > 2 && n % 2 === 0) {
995
+ return false;
996
+ }
997
+ for (let i = 3, sqrt = Math.sqrt(n); i <= sqrt; i += 2) {
998
+ if (n % i === 0) {
999
+ return false;
1000
+ }
1001
+ }
1002
+ return true;
1003
+ },
1004
+ // @ts-expect-error - TS2366 - Function lacks ending return statement and return type does not include 'undefined'.
1005
+ getPrimeFactorization: function (number) {
1006
+ if (number === 1) {
1007
+ return [];
1008
+ }
1009
+ if (KhanMath.isPrime(number)) {
1010
+ return [number];
1011
+ }
1012
+ const maxf = Math.sqrt(number);
1013
+ for (let f = 2; f <= maxf; f++) {
1014
+ if (number % f === 0) {
1015
+ return $__default["default"].merge(KhanMath.getPrimeFactorization(f), KhanMath.getPrimeFactorization(number / f));
1016
+ }
1017
+ }
1018
+ },
1019
+ // Round a number to the nearest increment
1020
+ // E.g., if increment = 30 and num = 40, return 30. if increment = 30 and
1021
+ // num = 45, return 60.
1022
+ roundToNearest: function (increment, num) {
1023
+ return Math.round(num / increment) * increment;
1024
+ },
1025
+ // Round a number to a certain number of decimal places
1026
+ roundTo: function (precision, num) {
1027
+ const factor = Math.pow(10, precision).toFixed(5);
1028
+ // @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'number'. | TS2363 - The right-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type. | TS2363 - The right-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.
1029
+ return Math.round((num * factor).toFixed(5)) / factor;
1030
+ },
1031
+ /**
1032
+ * Return a string of num rounded to a fixed precision decimal places,
1033
+ * with an approx symbol if num had to be rounded, and trailing 0s
1034
+ */
1035
+ toFixedApprox: function (num, precision) {
1036
+ // TODO(aria): Make this locale-dependent
1037
+ const fixedStr = num.toFixed(precision);
1038
+ if (kmath.number.equal(+fixedStr, num)) {
1039
+ return fixedStr;
1040
+ }
1041
+ return "\\approx " + fixedStr;
1042
+ },
1043
+ /**
1044
+ * Return a string of num rounded to precision decimal places, with an
1045
+ * approx symbol if num had to be rounded, but no trailing 0s if it was
1046
+ * not rounded.
1047
+ */
1048
+ roundToApprox: function (num, precision) {
1049
+ const fixed = KhanMath.roundTo(precision, num);
1050
+ if (kmath.number.equal(fixed, num)) {
1051
+ return String(fixed);
1052
+ }
1053
+ return KhanMath.toFixedApprox(num, precision);
1054
+ },
1055
+ // toFraction(4/8) => [1, 2]
1056
+ // toFraction(0.666) => [333, 500]
1057
+ // toFraction(0.666, 0.001) => [2, 3]
1058
+ //
1059
+ // tolerance can't be bigger than 1, sorry
1060
+ toFraction: function (decimal, tolerance) {
1061
+ if (tolerance == null) {
1062
+ tolerance = Math.pow(2, -46);
1063
+ }
1064
+ if (decimal < 0 || decimal > 1) {
1065
+ let fract = decimal % 1;
1066
+ fract += fract < 0 ? 1 : 0;
1067
+ const nd = KhanMath.toFraction(fract, tolerance);
1068
+ nd[0] += Math.round(decimal - fract) * nd[1];
1069
+ return nd;
1070
+ }
1071
+ if (Math.abs(Math.round(Number(decimal)) - decimal) <= tolerance) {
1072
+ return [Math.round(decimal), 1];
1073
+ }
1074
+ let loN = 0;
1075
+ let loD = 1;
1076
+ let hiN = 1;
1077
+ let hiD = 1;
1078
+ let midN = 1;
1079
+ let midD = 2;
1080
+
1081
+ // eslint-disable-next-line no-constant-condition
1082
+ while (true) {
1083
+ if (Math.abs(Number(midN / midD) - decimal) <= tolerance) {
1084
+ return [midN, midD];
1085
+ }
1086
+ if (midN / midD < decimal) {
1087
+ loN = midN;
1088
+ loD = midD;
1089
+ } else {
1090
+ hiN = midN;
1091
+ hiD = midD;
1092
+ }
1093
+ midN = loN + hiN;
1094
+ midD = loD + hiD;
1095
+ }
1096
+ },
1097
+ // Returns the format (string) of a given numeric string
1098
+ // Note: purposively more inclusive than answer-types' predicate.forms
1099
+ // That is, it is not necessarily true that interpreted input are numeric
1100
+ getNumericFormat: function (text) {
1101
+ text = $__default["default"].trim(text);
1102
+ text = text.replace(/\u2212/, "-").replace(/([+-])\s+/g, "$1");
1103
+ if (text.match(/^[+-]?\d+$/)) {
1104
+ return "integer";
1105
+ }
1106
+ if (text.match(/^[+-]?\d+\s+\d+\s*\/\s*\d+$/)) {
1107
+ return "mixed";
1108
+ }
1109
+ const fraction = text.match(/^[+-]?(\d+)\s*\/\s*(\d+)$/);
1110
+ if (fraction) {
1111
+ return parseFloat(fraction[1]) > parseFloat(fraction[2]) ? "improper" : "proper";
1112
+ }
1113
+ if (text.replace(/[,. ]/g, "").match(/^\d+$/)) {
1114
+ return "decimal";
1115
+ }
1116
+ if (text.match(/(pi?|\u03c0|t(?:au)?|\u03c4|pau)/)) {
1117
+ return "pi";
1118
+ }
1119
+ return null;
1120
+ },
1121
+ // Returns a string of the number in a specified format
1122
+ toNumericString: function (number, format) {
1123
+ if (number == null) {
1124
+ return "";
1125
+ }
1126
+ if (number === 0) {
1127
+ return "0"; // otherwise it might end up as 0% or 0pi
1128
+ }
1129
+ if (format === "percent") {
1130
+ return number * 100 + "%";
1131
+ }
1132
+ if (format === "pi") {
1133
+ const fraction = kmath.number.toFraction(number / Math.PI);
1134
+ const numerator = Math.abs(fraction[0]);
1135
+ const denominator = fraction[1];
1136
+ if (kmath.number.isInteger(numerator)) {
1137
+ const sign = number < 0 ? "-" : "";
1138
+ const pi = "\u03C0";
1139
+ return sign + (numerator === 1 ? "" : numerator) + pi + (denominator === 1 ? "" : "/" + denominator);
1140
+ }
1141
+ }
1142
+ if (___default["default"](["proper", "improper", "mixed", "fraction"]).contains(format)) {
1143
+ const fraction = kmath.number.toFraction(number);
1144
+ const numerator = Math.abs(fraction[0]);
1145
+ const denominator = fraction[1];
1146
+ const sign = number < 0 ? "-" : "";
1147
+ if (denominator === 1) {
1148
+ return sign + numerator; // for integers, irrational, d > 1000
1149
+ }
1150
+ if (format === "mixed") {
1151
+ const modulus = numerator % denominator;
1152
+ const integer = (numerator - modulus) / denominator;
1153
+ return sign + (integer ? integer + " " : "") + modulus + "/" + denominator;
1154
+ } // otherwise proper, improper, or fraction
1155
+ return sign + numerator + "/" + denominator;
1156
+ }
1157
+
1158
+ // otherwise (decimal, float, long long)
1159
+ return String(number);
1160
+ }
1161
+ };
1162
+ function sum(array) {
1163
+ return array.reduce(add, 0);
1164
+ }
1165
+ function add(a, b) {
1166
+ return a + b;
1167
+ }
1168
+
1169
+ exports.KhanMath = KhanMath;
1170
+ exports.angles = angles;
1171
+ exports.coefficients = coefficients;
1172
+ exports.geometry = geometry;
500
1173
  exports.libVersion = libVersion;
501
1174
  exports.line = line;
502
1175
  exports.number = number;
503
1176
  exports.point = point;
504
1177
  exports.ray = ray;
505
- exports.vector = vector;
1178
+ exports.sum = sum;
1179
+ exports.vector = vector$1;
506
1180
  //# sourceMappingURL=index.js.map