I'm trying to create a random float generator (range of 0.0-1.0), where I can supply a single target value, and a strength value that increases or decreases the chance that this target will be hit. For example, if my target is 0.7, and I have a high strength value, I would expect the function to return mostly values around 0.7.
Put another way, I want a function that, when run a lot of times, would produce a distribution graph something like this:
Histogram
Something like a bell curve, yes, but with a strict range limit (instead of the -inf/+inf range limit of a normal distribution). Clamping a normal distribution is not ideal, I want the distribution to naturally end at the range limits.
The approach I've been attempting is to come up with a formula to transform a value from uniform distribution to the mythical distribution I'm envisioning. Something like an inverse sine:
Inverse Sine
with the ability to widen out that middle point, via the strength value:
Widened Midpoint
and also the ability to move that midpoint up and down, via the target value:
Target changed to 0.7 (courtesy of MS Paint because I couldn't figure this part out mathematically)
The range of this theoretical "strength value" is up for debate. I could imagine either a limited value, say between 0 and 1, where 0 means it's uniform distribution and 1 means it's a 100% chance of hitting the target; or, I could imagine a value that approaches a 100% chance the higher it gets, without ever reaching it. Something along either line would work.
I'm working in C# but this can be language-agnostic. Any help pointing me in the right direction is appreciated. Also happy to clarify further.
I'm not a mathematician but I took a look and I feel like I got something that might work for you.
All i did was take the normal distribution formula:
and use 0.7 as mu to shift the distribution towards 0.7. I added a leading coefficient of 0.623 to shift the values to be between 0 and 1 and migrated it from formula to C#, this can be found below.
Usage:
DistributedRandom random = new DistributedRandom();
// roll for the chance to hit
double roll = random.NextDouble();
// add a strength modifier to lower or strengthen the roll based on level or something
double actualRoll = 0.7d * roll;
Definition
public class DistributedRandom : Random
{
public double Mean { get; set; } = 0.7d;
private const double limit = 0.623d;
private const double alpha = 0.25d;
private readonly double sqrtOf2Pi;
private readonly double leadingCoefficient;
public DistributedRandom()
{
sqrtOf2Pi = Math.Sqrt(2 * Math.PI);
leadingCoefficient = 1d / (alpha * sqrtOf2Pi);
leadingCoefficient *= limit;
}
public override double NextDouble()
{
double x = base.NextDouble();
double exponent = -0.5d * Math.Pow((x - Mean) / alpha, 2d);
double result = leadingCoefficient * Math.Pow(Math.E,exponent);
return result;
}
}
Edit:
In case you're not looking for output similar to the distribution histogram that you provided and instead want something more similar to the sigmoid function you drew I have created an alternate version.
Thanks to Ruzihm for pointing this out.
I went ahead and used the CDF for the normal distribution: where erf is defined as the error function: . I added a coefficient of 1.77 to scale the output to keep it within 0d - 1d.
It should produce numbers similar to this:
Here you can find the alternate class:
public class DistributedRandom : Random
{
public double Mean { get; set; } = 0.7d;
private const double xOffset = 1d;
private const double yOffset = 0.88d;
private const double alpha = 0.25d;
private readonly double sqrtOf2Pi = Math.Sqrt(2 * Math.PI);
private readonly double leadingCoefficient;
private const double cdfLimit = 1.77d;
private readonly double sqrt2 = Math.Sqrt(2);
private readonly double sqrtPi = Math.Sqrt(Math.PI);
private readonly double errorFunctionCoefficient;
private readonly double cdfDivisor;
public DistributedRandom()
{
leadingCoefficient = 1d / (alpha * sqrtOf2Pi);
errorFunctionCoefficient = 2d / sqrtPi;
cdfDivisor = alpha * sqrt2;
}
public override double NextDouble()
{
double x = base.NextDouble();
return CDF(x) - yOffset;
}
private double DistributionFunction(double x)
{
double exponent = -0.5d * Math.Pow((x - Mean) / alpha, 2d);
double result = leadingCoefficient * Math.Pow(Math.E, exponent);
return result;
}
private double ErrorFunction(double x)
{
return errorFunctionCoefficient * Math.Pow(Math.E,-Math.Pow(x,2));
}
private double CDF(double x)
{
x = DistributionFunction(x + xOffset)/cdfDivisor;
double result = 0.5d * (1 + ErrorFunction(x));
return cdfLimit * result;
}
}
I came up with a workable solution. This isn't quite as elegant as I was aiming for because it requires 2 random numbers per result, but it definitely fulfills the requirement. Basically it takes one random number, uses another random number that's exponentially curved towards 1, and lerps towards the target.
I wrote it out in python because it was easier for me to visualize the histogram of it:
import math
import random
# Linearly interpolate between a and b by t.
def lerp(a, b, t):
return ((1.0 - t) * a) + (t * b)
# What we want the median value to be.
target = 0.7
# How often we will hit that median value. (0 = uniform distribution, higher = greater chance of hitting median)
strength = 1.0
values = []
for i in range(0, 1000):
# Start with a base float between 0 and 1.
base = random.random()
# Get another float between 0 and 1, that trends towards 1 with a higher strength value.
adjust = random.random()
adjust = 1.0 - math.pow(1.0 - adjust, strength)
# Lerp the base float towards the target by the adjust amount.
value = lerp(base, target, adjust)
values.append(value)
# Graph histogram
import matplotlib.pyplot as plt
import scipy.special as sps
count, bins, ignored = plt.hist(values, 50, density=True)
plt.show()
Target = 0.7, Strength = 1
Target = 0.2, Strength = 1
Target = 0.7, Strength = 3
Target = 0.7, Strength = 0
(This is meant to be uniform distribution - it might look kinda jagged, but I tested and that's just python's random number generator.)
Related
I'd like to take a range of four random integers between 1-64, and generate a random value somewhere within the range but leaning towards a more weighted average.
The practical application is that you take a pixel, grab the 4 surrounding pixels and use those plus the current pixel to generate a value that can then be used as the base weight for a Gaussian random number generator. So you have a pixel of 10 brightness, surrounded by 8,8,9,9. Add them all up, average out to 8.8. 8.8 is then the weight for the Gaussian random number generator. So you have a random result within a range, but close to the average brightness which is 8.8, and still with some element of randomness.
The issue comes when you have wide variations because of random noise.
To give a pseudo example of how I would like it to work..
Input = [1,16,19,21]
The average of this is 14.25, but that has too much movement because of the "1" bringing the average way down. The average of this should be more around the 18 mark, because more of the numbers are clustered around that area.
I would like to see a random result coming out that is between 1 and 64, but heavily weighted between 15 and 22, with a lower possibility of it being towards the 1(Because it is still within the range as a whole) and a much lower possibility of it being over 22(Because that is completely outside of the range).
Additional The purpose of this is to generate a galactic map. I have got to the point where I have a good set of galaxy shaped data, giving me the rough density of each sector on the map. Now I need to generate specific sets of data and generate exact numbers of stars in each sector. Taking the average of the 4 surrounding sectors and using that to work out how "dense" this sector should be is the main purpose. The main thing I want to avoid is that sectors bordering an empty region of space do not also end up mostly empty, as this does not fit with general observations of galaxies.
You could imagine that the four numbers are points in a line, the x axis. Around that points there is a sphere of probability with a radius of 64, with the probability more concentrated in the proximity of the points rather than on the edges. Pick randomly one of the four points, calculate a random point inside the sphere of that number and take its x coordinate. Repeat if it is out of the range 1..64.
using System;
using System.Collections.Generic;
namespace ProbabilityDistribution1
{
class Program
{
// This derived class converts the uniformly distributed random
// numbers generated by base.Sample( ) to another distribution.
class RandomProportional : Random
{
// The Sample method generates a distribution more concentrated around the 0, in the range [0.0, 1.0].
protected override double Sample()
{
double BSample = base.Sample();
const double concentrationAroundInputs = 5;//more concentrated when greater
double result = Math.Pow(BSample, concentrationAroundInputs);
return result;
}
}
static double XCoordinateOfRandomUnitInsideSphere(Random aRandom)
{
//Even with uniform distribution the probability of exiting is greater than 0.5 on each iteration
while (true)
{
double x = aRandom.NextDouble();
double y = aRandom.NextDouble();
double z = aRandom.NextDouble();
if ((x * x + y * y + z * z) < 1) //inside the sphere
{
return x;
}
}
}
static void TestDistribution()
{
double[] Input = { 1, 16, 19, 21 };
List<int> sampleValues = new List<int>();
Random aRandom = new Random();
RandomProportional aRandomProportinal = new RandomProportional();
for (int i = 0; i < 100; i++)
{
int value = 0;
do
{
int indexChosen = aRandom.Next(4);
double xCoordinate = XCoordinateOfRandomUnitInsideSphere(aRandomProportinal);
if (aRandom.Next(2)==0)
{
xCoordinate = -xCoordinate;
}
double xRandomResult = xCoordinate * 64;
value = (int)(Input[indexChosen] + xRandomResult);
} while (value < 1 || value > 64);
sampleValues.Add((int)value);
}
sampleValues.Sort();
Console.WriteLine();
foreach (int i in sampleValues)
{
Console.Write(" {0:00} ", i);
}
Console.WriteLine();
}
static void Main(string[] args)
{
TestDistribution();
Console.WriteLine("end");
Console.ReadLine();
}
}
}
I'm stuck on one final piece of a calculation puzzle below. I know how to generate a percentage score of correct parts from total correct possible parts ((correctNumPartsOnBoard / totalPossibleCorrectParts)*100) but I want to the final percentage score to factor in the number the incorrect parts on the board as well. (even if all the right parts are on the board you still won't get 100% if there are also incorrect parts). Right now my current formula percentCorrectParts = ((correctNumPartsOnBoard / totalPossibleCorrectParts) / totalNumPartsOnBoard) * 100); is wrong and I'm having trouble pinpointing the correct calculation.
So, the way the calc would need to work is: a user needs to match one of the six possible animals, each animal has around 15 correct parts, but users can also drag incorrect parts onto the board (parts from the other animals are still visible so they could drag a different set of legs or horns on a lizard head, they could make frankenstein type creatures as well this way). So the total number of parts available would be 6*15. But seeing as how they're not all correct they would influence the score as well by bringing the overall score average of pieces on the board down.
What's the correct formula for this?
// Scoring System
using UnityEngine;
using System.Linq;
using System.Collections.Generic;
public class ScoreManager : MonoBehaviour
{
public List<string> totalBuildBoardParts; // Running list of all parts on board (by Tag)
public int numCorrectPartsOnBoard;
public int numIncorrectPartsOnBoard;
public int totalPossibleCorrectParts;
public float percentCorrectParts;
void Start()
{
GameObject gameController = GameObject.FindGameObjectWithTag("gc");
GameSetup gameSetup = gameController.GetComponent<GameSetup>();
totalPossibleCorrectParts = gameSetup.totalPossibleCorrectParts;
Debug.Log("TOTAL POSSIBLE CORRECT PARTS ARE: " + totalPossibleCorrectParts);
}
public void AddAnimalPartByTag(string tag)
{
// Add object tag to List
totalBuildBoardParts.Add(tag);
Debug.Log ("Added an object tagged as: " + tag);
GameObject gameController = GameObject.FindGameObjectWithTag("gc");
GameSetup gameSetup = gameController.GetComponent<GameSetup>();
if (tag == gameSetup.activeTag)
{
numCorrectPartsOnBoard ++;
Debug.Log ("There are " + numCorrectPartsOnBoard + " correct parts on the board");
} else {
numIncorrectPartsOnBoard ++;
}
CalculateScore();
}
public void RemoveAnimalPartByTag(string tag)
{
// Add object tag to List
totalBuildBoardParts.Remove(tag);
Debug.Log ("Removed an object tagged as: " + tag);
GameObject gameController = GameObject.FindGameObjectWithTag("gc");
GameSetup gameSetup = gameController.GetComponent<GameSetup>();
if (tag == gameSetup.activeTag)
{
numCorrectPartsOnBoard --;
Debug.Log ("There are " + numCorrectPartsOnBoard + " correct parts on the board");
} else {
numIncorrectPartsOnBoard --;
}
CalculateScore();
}
public void CalculateScore()
{
float totalNumPartsOnBoard = totalBuildBoardParts.Count();
float correctNumPartsOnBoard = numCorrectPartsOnBoard;
percentCorrectParts = ((correctNumPartsOnBoard / totalPossibleCorrectParts) / totalNumPartsOnBoard) * 100);
Debug.Log ("Your current score is: " + percentCorrectParts);
}
}
Your formula is probably correct. However, your datatypes are not.
You are currently doing an integer division, which results in an int too. So let's say that correctNumPartsOnBoard is 3 and totalPossibleCorrectParts is 5, 3/5 gives 0 because an int does not have any decimals.
You need to cast one of the two operands in the division as a datatype with decimals ( float, double or decimal for example):
percentCorrectParts = ((correctNumPartsOnBoard / (float)totalPossibleCorrectParts) / totalNumPartsOnBoard) * 100);
By setting denominator totalPossibleCorrectParts as a float, the first division will return a float. That float is then used in the second division, also returning correctly a float.
I think your formula should look like this:
int correctParts;
int possibleCorrect;
int incorrectParts;
int parts;
float percentFinished =
Mathf.Max((((float)correctParts/possibleCorrect) // Correct percent
- ((float)incorrectParts/parts)) // Minus incorrect percent
* 100f, // Normalized to 100
0f); // Always a minimum of 0
Also with this formula unlike other answers, you don't have to use all of the parts to get 100%, just get the total possible correct parts which doesn't necessarily have to use up all of your parts ;)
Scenario
Lets say you have 100 parts, with 3 right and 3 wrong. Total right we are aiming for here is 20.
int correctParts = 3;
int possibleCorrect = 20;
int incorrectParts = 3;
int parts = 100;
float percentFinished =
Mathf.Max((((float)correctParts/possibleCorrect) // Correct percent is 0.15 or 15%
- ((float)incorrectParts/parts)) // Minus incorrect percent which is .03 or 3%
* 100f, // Normalized to 100 which gives us 15% - 3% = 12%
0f); // Always a minimum of 0
I think your final (%age) score should be:
correctNumPartsOnBoard / totalNumPartsOnBoard * 100
If you have 80 correctparts and 20 incorrect then the total parts is 100 and you've got 80 of them correct so you should score 80% like this:
80 / (80+20) * 100
I have interprated the formula in wikipedia in c# code, i do get a nice normal curve, but is it rational to get values that exceeds 1? isnt it suppose to be a distribution function?
this is the C# implementation :
double up = Math.Exp(-Math.Pow(x , 2) / ( 2 * s * s ));
double down = ( s * Math.Sqrt(2 * Math.PI) );
return up / down;
i double checked it several times and it seems fine to me so whats wrong? my implementation or understanding?
for example if we define x=0 and s=0.1 this impl would return 3.989...
A distribution function, a pdf, has the property that its values are >= 0 and the integral of the pdf over -inf to +inf must be 1. But the integrand, that is the pdf, can take any value >= 0, including values greater than 1.
In other words, there is no reason, a priori, to believe that a pdf value > 1 indicates a problem.
You can think about this for the normal curve by considering what reducing the variance means. Smaller variance values concentrate the probability mass in the centre. Given that the total mass is always one, as the mass concentrates in the centre, the peak value must increase. You can see that trend in the graph the you link to.
What you should do is compare the output of your code with known good implementations. For instance, Wolfram Alpha gives the same value as you quote: http://www.wolframalpha.com/input/?i=normal+distribution+pdf+mean%3D0+standard+deviation%3D0.1+x%3D0&x=6&y=7
Do a little more testing of this nature, captured in a unit test, and you will be able to rely on your code with confidence.
Wouldn't you want something more like this?
public static double NormalDistribution(double value)
{
return (1 / Math.Sqrt(2 * Math.PI)) * Math.Exp(-Math.Pow(value, 2) / 2);
}
Yes, it's totally OK; The distribution itself (PDF) can be anything from 0 to +infinity; the thing should be in the range [0..1] is the corresponding integral(s) (e.g. CDF).
You can convince yourself if look at the case of non-random value: if the value is not a random at all and can have only one constant value the distribution degenerates (standard error is zero, mean is the value) into Dirac Delta Function: a peak of infinite hight but of zero width; integral however (CDF) from -infinity to +infinity is 1.
// If you have special functions implemented (i.e. Erf)
// outcoume is in [0..inf) range
public static Double NormalPDF(Double value, Double mean, Double sigma) {
Double v = (value - mean) / sigma;
return Math.Exp(-v * v / 2.0) / (sigma * Math.Sqrt(Math.PI * 2));
}
// outcome is in [0..1] range
public static Double NormalCDF(Double value, Double mean, Double sigma, Boolean isTwoTail) {
if (isTwoTail)
value = 1.0 - (1.0 - value) / 2.0;
//TODO: You should have Erf implemented
return 0.5 + Erf((value - mean) / (Math.Sqrt(2) * sigma)) / 2.0;
}
I have an angle say 60deg and want to generate random angle within interval say [-120,120] where the interval centred around the 60deg which be now [-60,180]
I have this code below:
http://www.cs.princeton.edu/introcs/22library/StdRandom.java.html
I'm confused because it's say that the gaussian distribution is within [0,1].
How could I pass the range [-120,120]?
The 60 angle is the relative rotation of an object the generated random angle is a predication of it's next postion
When testing the code I have angles ,say 65 ,55 if i use this angle directly it performs stranges so I take the difference 65-60 ,55-60.
Is this idea correct?
If you have a random number with a range 0 to 1, you can convert it to -120 to 120 by using:
rand_num*240 - 120
More generally, transforming any number within range [A,B] to range [C,D] involves:
num * (D-C)/(B-A) + C
I'm not sure what you mean by keeping your mean, however.
If you want a range that extends 120 in each direction, from 60, you could either do the above and add 60, or use a range [60-120,60+120] = [-60,180]
In that sense, you'd have
rand_num * 240 - 60
following from the formula given above
static void Main(string[] args)
{
Random rand = new Random();
double a = 0;
for (int i = 0; i < 1000; i++)
{
double r = rand.NextDouble() * 240 - 60;
a += r;
Console.WriteLine(string.Format("ang:{0,6:0.0} avg:{1,5:0.0}", r, a / (i + 1)));
}
Console.ReadKey();
}
If you have something that generates random numbers in a range such as [0, 1] it's easy to transform that to another range, such as [-120, 120]: you just have to multiply by the size of the target range (240 in this case) and add the start of the target range (-120 in this case).
So, for example:
java.util.Random random = new java.util.Random();
// Generate a random number in the range [-120, 120]
double value = random.nextDouble() * 240.0 - 120.0;
Is there a special reason why you are using that StdRandom class? Does the distribution of the random numbers have to be Gaussian? (That doesn't matter, the above will still work).
If it has to be centered around 60, then just add 60.
Try this:
import java.lang.Math;
public static void main(String[] args)
{
System.out.println((int)(Math.random()*(-240))+120);
}
You have C# and Java marked as tags. Kind of confusing to figure out which one you want.
I prefer this over the Random class in java.utils because you don't have to instantiate a class. Everything you need is in the static methods of the Math class.
Breakdown:
return Math.random(); // returns a double value [0, 1]
return Math.random()*-240; // returns a double value from [-240, 0]
return (int)(Math.random()*-240); // returns an integer value from [-240, 0]
return (int)(Math.random()*-240) + 120; // returns an integer value from [-120, 120]
Given the start and the end of an integer range, how do I calculate a normally distributed random integer between this range?
I realize that the normal distribution goes into -+ infinity. I guess the tails can be cutoff, so when a random gets computed outside the range, recompute. This elevates the probability of integers in the range, but as long as the this effect is tolerable (<5%), it's fine.
public class Gaussian
{
private static bool uselast = true;
private static double next_gaussian = 0.0;
private static Random random = new Random();
public static double BoxMuller()
{
if (uselast)
{
uselast = false;
return next_gaussian;
}
else
{
double v1, v2, s;
do
{
v1 = 2.0 * random.NextDouble() - 1.0;
v2 = 2.0 * random.NextDouble() - 1.0;
s = v1 * v1 + v2 * v2;
} while (s >= 1.0 || s == 0);
s = System.Math.Sqrt((-2.0 * System.Math.Log(s)) / s);
next_gaussian = v2 * s;
uselast = true;
return v1 * s;
}
}
public static double BoxMuller(double mean, double standard_deviation)
{
return mean + BoxMuller() * standard_deviation;
}
public static int Next(int min, int max)
{
return (int)BoxMuller(min + (max - min) / 2.0, 1.0);
}
}
I probably need to scale the standard deviation some how relative to the range, but don't understand how.
Answer:
// Will approximitely give a random gaussian integer between min and max so that min and max are at
// 3.5 deviations from the mean (half-way of min and max).
public static int Next(int min, int max)
{
double deviations = 3.5;
int r;
while ((r = (int)BoxMuller(min + (max - min) / 2.0, (max - min) / 2.0 / deviations)) > max || r < min)
{
}
return r;
}
If the Box-Muller method returns a "standard" normal distribution, it will have mean 0 and standard deviation 1. To transform a standard normal distribution, you multiply your random number by X to get standard deviation X, and you add Y to obtain mean Y, if memory serves me correctly.
See the Wikipedia article's section on normalizing standard normal variables (property 1) for a more formal proof.
In response to your comment, the rule of thumb is that 99.7% of a normal distribution will be within +/- 3 times the standard deviation. If you need a normal distribution from 0 to 100 for instance, than your mean will be halfway, and your SD will be (100/2)/3 = 16.667. So whatever values you get out of your Box-Muller algorithm, multiply by 16.667 to "stretch" the distribution out, then add 50 to "center" it.
John, in response to your newest comment, I'm really not sure what is the point of the Next function. It always uses a standard deviation of 1 and a mean of halfway between your min and max.
If you want a mean of Y, with ~99.7% of the numbers in the range -X to +X, then you just call BoxMuller(Y, X/3).
Well, the -2*sigma..+2*sigma will give you 95% of the bell curve.
(check the "Standard deviation and confidence intervals" section in the already mentioned wiki article).
So modify this piece:
return (int)BoxMuller(min + (max - min) / 2.0, 1.0);
and change 1.0 (standard deviation) to 2.0 (or even more if you want more than 95% coverage)
return (int)BoxMuller(min + (max - min) / 2.0, 2.0);