Distributions

Overview

Distribution
Usage
Normal DistributionUsed to model real world, as many real world processes are normal distributed, like height of people and many other natural processes.
Discrete DistributionSince 1.1.0 Custom discrete distribution where every choice can be given a probability. More performant alternative to the roulette distribution.
Roulette DistributionThis distribution models a ball rolling down a roulette wheel, where each slot can have its own probability. Alternative to discrete distribution, which is faster but more restrictive.
Custom Distribution (Rejection Sampling)Models every custom distribution described by a AnimationCurve and sampled by rejection sampling.
Fair Random DistributionNew in 1.2.0 A distribution that simulates a proc rate for RPG games by steadily increasing the probability.
Rpg Dice DistributionNew in 1.2.0 A distribution inspired by the dice notation for RPG games.

Normal Distribution

Normal (Marsaglia)

The methods NormalDistributionMarsaglia.NextNormal(ref <RNG>, mean, std) are using the Marsaglia polar method to generate a random number following the normal distribution.

Normal (Ziggurat)

The methods NormalDistributionZiggurat.NextNormal(ref <RNG>, mean, std) are using the Ziggurat algorithm which is usually faster than the Marsaglia method, even when the implementation is much more complicated. It also needs additional memory, as lookup tables are needed. In contrast to the marsaglia method, Ziggurat mostly doesn’t use any complicated math function like sin, cos or exp.


Discrete Distribution Since 1.1.0

Models a discrete distribution which allows every index to have its own probability. This is a (much) faster version of the roulette distribution, with the restrictions: All probabilities must sum up to one and no probability can be negative.

Example usage:

var prob = new NativeArray<uint>(101, Allocator.Temp);

// initialize probabilities
for (var i = 0; i < prob.Length; i++) {
    // Assign probabilities to each index. This should be set to the desired probability   
    prob[i] = 1.0f / prob.Length; 
}

// Generate weights for faster RNG generation
var alias = DiscreteDistribution.GenerateWeights(prob, Allocator.Temp, true);

// Sample the distribution
var val = DiscreteDistribution.Next(ref rng, alias);

Roulette

The roulette distribution chooses a slot randomly between 0 and length (excluding). Each slot has its own probability. Probabilities can be specified in uint, float and double.

The individual probabilities are calculated as individual probability / sum over all values.

Using uints can sometimes be more accurate/faster, as float division or multiplication is avoided.

Example usage:

var prob = new NativeArray<uint>(101, Allocator.Temp);

// initialize probabilities
for (var i = 0; i < prob.Length; i++) { 
    // Assign probabilities to each index. This should be set to the desired probability
    prob[i] = 1;
}

// Generate weights for faster RNG generation
var weights = RouletteDistribution.GenerateWeights(prob, Allocator.Temp);

// Sample the distribution
var val = RouletteDistribution.NextRoulette(ref rng, weights);

The individual probability is 1/100 ( = the sum of all values ).


Custom Distribution (Rejection Sampling) Since 1.1.0

Samples supplied distributions that are described by an animation curve. The Unity-AnimationCurve must first be converted to a temporary structure, that can be used from the job system JobAnimationCurve. It’s expected that Unity will provide a job compatible AnimationCurve some time in the future. The rejection sampling algorithm used is very simple and slow, but doesn’t have any restrictions on the curve/distribution itself. This allows not-well-defined probability distributions to be sampled (for example: distributions that do not have an area of 1 under the curve).

Example usage:

var jobAnimationCurve = animationCurve.ToJobsAnimationCurve();
unsafe {
    var jac = UnsafeUtility.AddressOf(ref jobAnimationCurve);
    SampleDistribution(jac);
}

[BurstCompile]
public unsafe static void SampleDistribution(void* jobAnimationCurve) {
    var jac = UnsafeUtility.AsRef<JobAnimationCurve>(jobAnimationCurve);

    var rng = new GRandom(1337);
    var v = NaiveAlgorithms.NextFloatWithRejection(ref rng, ref jac);

    // use generated value v
}

Fair Random Distribution New in 1.2.0

The fair random distribution is inspired by the critical hit rate in RPG games. It’s a distribution that steadily increases the probability of a success. The distribution is binary and has only the outcomes true and false. This probability of success is increased by a small amount every time the distribution is sampled and doesn’t result in true. The probability is reset to the initial value when the result is a success.

The fair random example gives a good overview of the distribution.

The fair random distribution needs an maxTriesUntilProc parameter, which specifies the absolute maximum tries until the distribution should proc (result in a success). If instead a probability is needed, the maxTriesUntilProc can be calculated with FairRandomHelper.FindMaxTriesUntilProcForProbability.

Example usage:

var rng = new GRandom(1337);

// Optional: Calculate the maxTriesUntilProc from a given probability
var maxTriesUntilProc = FairRandomHelper.FindMaxTriesUntilProcForProbability(0.2f);

// Initialize status object
var frs = new FairRandomStatus<double>(maxTriesUntilProc);

// Sample the distribution
var hit = FairRandom.Next(ref rng, ref frs);

Rpg Dice Distribution New in 1.2.0

The rpg dice distribution is inspired by the Dice Notation for RPG games and provides (almost) the same features. It allows to simulate various dice rolls, like 1d6, 2d6+1, 2d6+1d4 and so on. Whereas 1d6 means to roll a standard dice once, and 2d6+1 means to roll a standard dice twice and add 1 to the result. The notation is often used in pen and paper RPG games like Dungeons and Dragons.

Supported features:

  • AdX*M+C: roll a dice with X sides A times, multiply the result with M and add C
  • AdXkHYkLZ: roll a dice with X sides A times, keep the Y highest and Z lowest results
  • (AdX+AdX)*M+C: Multiply the result of multiply dices by M and add C

The expression can be created with a builder and is stack based. The expression is created from left to right and evaluated the same way.

To create a distribution for (AdX+BdX)*M+C the following code can be used:

var builder = new RpgDiceBuilderMono();

builder.AddDice(A, X);
builder.AddDice(B, X);
builder.Expression(M, C);

var expr = builder.BuildMono(); // use .Build() for burst compatible version

var rng = new GRandom(1337);

// sample the distribution
var result = RpgDiceDistribution.Next(ref rng, ref expr); 

The implementation is separated in a mono and burst compatible version. The mono version is more flexible and allows to use the distribution in the editor. For that the RpgDiceInspector has been created. The burst compatible version is faster and can be used in jobs.

The RpgDice example is a good starting point to investigate the functionality of the distribution.