Source: mixture.js

import { getRandomGenerator } from './seed'
import { baseGenerator }  from './base-generator'
import { inverseNormalCDF } from './util/inverse-normal-cdf'
import { EPS } from './util/constants'
import { uniform } from './uniform'

/**
 * Generates random numbers or arrays of random numbers for a mixture of given distributions.
 *
 * @param {Array} [generators] - The list of generators corresponding to each distribution.
 * @param {number} [priors] - The list of priors, one per distribution, must sum to 1.
 * @param {number|Array|null} [size=null] - The shape of the output:
 *   - If `null`, a single number is returned.
 *   - If a number, a 1D array of the specified size is returned.
 *   - If an array (e.g., [m, n]), a multidimensional array of the specified shape is returned.
 * @throws {Error} If any of the generators raise an error on sampling. 
 * @returns {number|Array} A single sample, an array, or a multidimensional array of numbers accoirding to the mixture of the distributions provided.
 *
 */
export function mixture(generators, priors, size) {
    if (generators.length !== priors.length) {
        throw new Error("The number of priors must match the number of generators.");
    }

    const sumPriors = priors.reduce((acc, p) => acc + p, 0);
    if (Math.abs(sumPriors - 1) > EPS) {
        throw new Error("Priors must sum to 1.");
    }

    function pickGenerator() {
        const threshold = uniform();
        let cumulative = 0
        for (let indexDistribution = 0; indexDistribution < priors.length; indexDistribution++) {
            cumulative += priors[indexDistribution]
            if (threshold < cumulative) {
                return generators[indexDistribution]
            }
        }
        return generators[generators.length - 1];
    }

    return baseGenerator(() => pickGenerator()(), size)
}