summaryrefslogtreecommitdiff
path: root/src/app/utils/math_utils.js
blob: f5482d3b4ee3c20d67748e2d13873fb2e020a53b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
/**
 * Mathematical utilities.
 * @module app/utils/math_utils
 */

/**
 * RegExp to check if a string contains only hexadecimal values.
 * The string should only contain the characters [0-9] or [a-f], case-insensitive.
 * @type {RegExp}
 */
const HEXADECIMAL_REGEXP = new RegExp("^[a-f0-9]+$", "i");

/**
 * Check if a string is a valid hexadecimal number.
 * @param  {String}  text  the string to check
 * @return {Boolean}       true if the string is a valid hexadecimal number
 */
export const isHexadecimal = (text) => !!text?.match(HEXADECIMAL_REGEXP);

/**
 * Constrain a number between an upper or lower bound.
 * @param  {Number} value the value to clamp
 * @param  {Number} low   the lower bound
 * @param  {Number} high  the upper bound
 * @return {Number}       the clamped value
 */
export const clamp = (value, low = 0, high = 1) =>
  value < low ? low : value < high ? value : high;

/**
 * Implementation of mod that supports negative numbers (unlike JavaScript % operator)
 * @param  {Number} numerator   the modulo numerator
 * @param  {Number} denominator the modulo denominator
 * @return {Number}             `numerator mod denominator`
 */
export const mod = (numerator, denominator) =>
  numerator - denominator * Math.floor(numerator / denominator);

/**
 * Get the mean of a list of numbers (where non-null)
 * @param  {Array} numbers  list of numbers
 * @return {number}         arithmetic mean
 */
export const arrayMean = (numbers) => {
  const nonZero = (numbers || []).filter((number) => !!number);
  if (!nonZero.length) return 0;
  const sum = nonZero.reduce((a, b) => {
    return a + b;
  }, 0);
  return sum / nonZero.length;
};

/**
 * Find the (planar) centroid of a set of points
 * @param  {Array} items    list of location-like items having { lat, lng }
 * @return {Object}         object with averaged lat/lng
 */
export const centroid = (items) => ({
  lat: arrayMean(items.map((item) => item.lat)),
  lng: arrayMean(items.map((item) => item.lng)),
});

/**
 * Returns a gaussian (normal) random function with the given mean and stdev.
 * @param  {Number} mean    center value
 * @param  {Number} stdev   standard deviation (radius around mean)
 * @return {Function}       function generating numbers with a normal distribution
 */
export function gaussian(mean, stdev, random = Math.random) {
  let y2;
  let use_last = false;
  return () => {
    let y1;
    if (use_last) {
      y1 = y2;
      use_last = false;
    } else {
      let x1, x2, w;
      do {
        x1 = 2.0 * random() - 1.0;
        x2 = 2.0 * random() - 1.0;
        w = x1 * x1 + x2 * x2;
      } while (w >= 1.0);
      w = Math.sqrt((-2.0 * Math.log(w)) / w);
      y1 = x1 * w;
      y2 = x2 * w;
      use_last = true;
    }

    return mean + stdev * y1;
    // if (retval > 0) return retval;
    // return -retval;
  };
}