@khanacademy/kmath 0.0.0-PR862-20231207182234 → 0.0.0-PR875-20250222011102

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