Search code examples
normal-distribution

generate random values with a parabolic distribution


I want to generate random delays between x and y. I want the ouputs to be around the middle of x and y more commonly than at x or y.

For example if my x is 10 and y is 20, I want the most common output to be 15.

I have been trying to solve this problem on paper but I am not a math genius, unfortunately.

I tried some formulas with Random.nextGuassian() but I simply cannot comprehend the odd output that it gives.


Solution

  • Before you can implement a random number generator with a parabolic distribution you need to define what you mean by this. Here is one attempt:

    Parabolic distribution

    The distribution is defined on the interval 0 to 1 as a quadratic polynomial. On this interval the area below the curve has to be 1. By varying the parameters K and M you can adjust the distribution. K determines where the center of the parabola is located in the interval and M determines how far down to "pull" the parabola. In your question K is ½ as you want the apex to be in the middle of the interval. You did not specify M.

    The parabola is specified by the function

    f(x) = ax2 + bx + c

    Provided K and M and that the area below the curve on the interval 0 to 1 should be 1 you need to compute a, b and c.

    If K = ½ and M = ½ the exact solution is

    f(x) = 6x2 - 6x + 2.

    If this solution is not sufficient (because you want other values for K and/or M) then you will have to create a set of equations. Based on how the parabola is placed it is known that a has to be positive, b negative and c > M.

    The apex of the parabola should touch (K, M). This means that f(K) = M or f(K) - M = 0.

    For the apex to touch this point the discriminant of the modified quadratic equation has to be 0:

    d' = b2 - 4a(c - M)

    Provided that you know a and c you can calculate b (it is already known that b has to be negative to create the desired parabola):

    b = -√(4a(c - M)) [equation A]

    The equation for the apex is:

    f(K) - M = aK2 + bK + c - M = 0 [equation B]

    The area below the parabola is computed from the definite integral over the interval 0 to 1 of f(x)

    01 f(x) dx = a/3 + b/2 + c = 1 [equation C]

    You can substitute b from equation A into equation B and C giving two equations with two unknowns a and c. Unfortunately, these equations are non-linear and my math is getting rusty so I chose the "easy path" and used the Solver add-in in Excel to find approximate solutions for different values of K and M. If you take this approach you should probably add a constraint in the solver that d' cannot be negative as well as constraints on a and c as noted above.

    Now that you know a, b and c for the desired parabolic equation the approach to create the random numbers with a parabolic distribution is as follows:

    • Generate a random number on the domain of the parabolic equation with uniform distribution (visually you can think of this as generating a y value or an f(x) value)
    • By using the inverse of f(x) compute the corresponding x value - notice that there are two possible solutions
    • If one of the solutions is outside the interval 0 to 1 pick the other solution
    • If both solutions are in the interval 0 to 1 pick one of them randomly

    This solution picked is a random number with a parabolic distribution.

    The inverse of f(x) is

    x = (-b ± √(b2 - 4a(c - y)))/2a

    Combining all this I have created a C# class. You will have to substitute the desired values for the parameters K, M, a, b and c. Or you can extend this code to compute a, b and c from K and M using numerical algorithms.

    class ParabolicRandom
    {
        const double k = 0.5D;
        const double m = 0.5D;
    
        const double a = 6;
        const double b = -6;
        const double c = 2;
    
        readonly double yMin = m;
        readonly double yMax = Math.Max(F(0D), F(1D));
    
        Random random;
    
        public ParabolicRandom() => random = new Random();
    
        public ParabolicRandom(int seed) => random = new Random(seed);
    
        public double Next()
        {
            var randomY = (yMax - yMin) * random.NextDouble() + yMin;
            var randomX1 = ReverseF1(randomY);
            var randomX2 = ReverseF2(randomY);
    
            if (randomX1 < 0D || randomX1 > 1D)
                return randomX2;
            if (randomX2 < 0D || randomX2 > 1D)
                return randomX1;
            return random.Next()%2 == 0 ? randomX1 : randomX2;
        }
    
        static double F(double x) => a * x * x + b * x + c;
        double ReverseF1(double y) => (-b + Math.Sqrt(b * b - 4 * a * (c - y))) / (2 * a);
        double ReverseF2(double y) => (-b - Math.Sqrt(b * b - 4 * a * (c - y))) / (2 * a);
    }
    

    To generate random number between 10 and 20 with half as many at 15 compared to 10 and 20 you can use it like this:

    var lowerInclusive = 10;
    var upperInclusive = 20;
    var value = (int) Math.Floor(
        (upperInclusive - lowerInclusive + 1)*parabolicRandom.Next() + lowerInclusive);