forked from tsai/mathnet-numerics
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
210 lines
7.4 KiB
210 lines
7.4 KiB
(**
|
|
|
|
Math.NET Numerics: Random Numbers and Probability Distributions
|
|
===============================================================
|
|
|
|
The .Net base class library provides a pseudo-random number generator
|
|
for non-cryptography use in the form of the `System.Numerics` class.
|
|
Math.NET Numerics provides a few alternatives with different characteristics
|
|
in randomness, bias, sequence length and performance. All these classes
|
|
inherit from System.Random so you can use them as a replacement for
|
|
System.Random even in third-party code.
|
|
|
|
All random number generators generate numbers in a more-or-less uniform
|
|
distribution. Often you need to sample random numbers with a different
|
|
distribution, e.g. a Gaussian. You can do that with one of the distribution
|
|
classes, or in F# also using the `Sample` module. The distribution classes
|
|
also provide a lot of other functionality around probability distributions,
|
|
like parameter estimation or evaluating the commulative distribution function.
|
|
|
|
Initializtation
|
|
---------------
|
|
|
|
We need to reference Math.NET Numerics and the F# modules, and open
|
|
the namespaces for random numbers and probability distributions:
|
|
|
|
*)
|
|
|
|
#r "../../out/lib/Net40/MathNet.Numerics.dll"
|
|
#r "../../out/lib/Net40/MathNet.Numerics.FSharp.dll"
|
|
|
|
open MathNet.Numerics.Random
|
|
open MathNet.Numerics.Distributions
|
|
|
|
(**
|
|
|
|
Random Number Generators
|
|
------------------------
|
|
|
|
Other than for cryptographic random numbers where you'd never want to provide
|
|
a seed, all pseudo-random numbers can be initialized with a custom seed.
|
|
The same seed causes the same number sequence to be generated, which can be
|
|
very useful if you need results to be reproducible, e.g. in testing/verification.
|
|
|
|
If no seed is provided, System.Random uses a time based seed equivalent to the
|
|
one below. This means that all instances created within a short timeframe
|
|
(which typically spans around a thousand CPU clock cycles) will generate
|
|
exactly the same sequence. This can happen easily e.g. in parallel computing
|
|
and is often unwanted. That's why all number generators created using
|
|
Math.NET Numerics routines are by default initialized with a seed that combines
|
|
the time with a Guid (which are supposed to be generated uniquely, worldwide).
|
|
|
|
*)
|
|
|
|
let someTimeSeed = RandomSeed.Time()
|
|
let someGuidSeed = RandomSeed.Guid()
|
|
|
|
(**
|
|
|
|
Random number generators can be created using the `Random` module. Most functions
|
|
optionally accept a manual seed when using the variant with the Seed-suffix.
|
|
Some of them, like xor-shift, also have a variant with a Custom-suffix that
|
|
allow to pass additional parameters specific to that generator.
|
|
|
|
Note that the generators should be reused when generating multiple numbers.
|
|
If you'd create a new generator each time, the numbers it generates would be
|
|
exactly as random as your seed - and thus not very random at all.
|
|
However, generators are not automatically thread-safe. They *are* thread-safe
|
|
in Math.NET Numerics by default, but that can be disabled either using a
|
|
boolean argument at creation, or by setting `Control.ThreadSafeRandomNumberGenerators`.
|
|
|
|
*)
|
|
|
|
let a = Random.system ()
|
|
let b = Random.systemSeed (RandomSeed.Time())
|
|
let b2 = Random.systemSeed someGuidSeed
|
|
let c = Random.crypto ()
|
|
let d = Random.mersenneTwister ()
|
|
let e = Random.mersenneTwisterWith 1000 true (* thread-safe *)
|
|
let f = Random.xorshift ()
|
|
let g = Random.xorshiftCustom someTimeSeed false 916905990L 13579L 362436069L 77465321L
|
|
let h = Random.wh2006 ()
|
|
let i = Random.palf ()
|
|
|
|
// Generate some uniform random values
|
|
let values = (
|
|
a.Next(),
|
|
b.NextFullRangeInt32(),
|
|
c.NextFullRangeInt64(),
|
|
d.NextInt64(),
|
|
e.NextDouble(),
|
|
f.NextDecimal()
|
|
)
|
|
|
|
(**
|
|
|
|
Probability Distributions
|
|
-------------------------
|
|
|
|
Non-uniform probability distributions can be created using their normal constructor,
|
|
some also offer static functions if there are multiple ways to parametrize them.
|
|
|
|
Since probability distributions can also be sampled to generate random numbers
|
|
with the configured distribution, all constructors optionally accept a random generator
|
|
as last argument.
|
|
|
|
*)
|
|
|
|
// some probability distributions
|
|
let normal = Normal.WithMeanVariance(3.0, 1.5, g)
|
|
let exponential = Exponential(2.4)
|
|
let gamma = Gamma(2.0, 1.5, Random.crypto())
|
|
let cauchy = Cauchy(0.0, 1.0, Random.mrg32k3aWith 10 false)
|
|
let poisson = Poisson(3.0)
|
|
let geometric = Geometric(0.8, Random.system())
|
|
|
|
// sample some random rumbers from these distributions
|
|
let continuous = [
|
|
yield normal.Sample()
|
|
yield exponential.Sample()
|
|
yield! gamma.Samples() |> Seq.take 10
|
|
]
|
|
let discrete = [
|
|
poisson.Sample()
|
|
poisson.Sample()
|
|
geometric.Sample()
|
|
]
|
|
|
|
|
|
// direct sampling (without creating a distribution object)
|
|
let u = Normal.Sample(Random.system(), 2.0, 4.0)
|
|
let v = Laplace.Samples(Random.mersenneTwister(), 1.0, 3.0) |> Seq.take 100 |> List.ofSeq
|
|
let w = Rayleigh.Sample(c, 1.5)
|
|
let x = Hypergeometric.Sample(h, 100, 20, 5)
|
|
|
|
(**
|
|
|
|
Specifically for F# there is also a `Sample` module that allow a somewhat
|
|
more functional view on the distributions by allowing them to be curried such that
|
|
the random source is passed in as last arguments. This way distributions can
|
|
be combined and transformed arbitrarily:
|
|
|
|
*)
|
|
|
|
/// Transform a sample distribution
|
|
let s1 rng = tanh (Sample.normal 2.0 0.5 rng)
|
|
|
|
/// Alternative way where we transform the function instead of its result
|
|
let s1alt rng = Sample.transform tanh (Sample.normal 2.0 0.5) rng
|
|
|
|
/// Alternative way that works exactly the same but operates on functions generating sequences
|
|
let s1seq rng = Sample.transformSeq tanh (Sample.normalSeq 2.0 0.5) rng
|
|
|
|
/// The same with multiple distributions:
|
|
let s2 rng = (Sample.normal 2.0 1.5 rng) * (Sample.cauchy 2.0 0.5 rng)
|
|
let s2alt rng = Sample.transform2 (*) (Sample.normal 2.0 1.5) (Sample.cauchy 2.0 0.5) rng
|
|
let s2seq rng = Sample.transformSeq2 (*) (Sample.normalSeq 2.0 1.5) (Sample.cauchySeq 2.0 0.5) rng
|
|
|
|
Seq.take 10 (s2seq (Random.system())) |> Seq.toArray
|
|
|
|
(**
|
|
|
|
Let's do some random walks, using distributions and random sources defined above:
|
|
|
|
*)
|
|
|
|
Seq.scan (+) 0.0 (normal.Samples()) |> Seq.take 10 |> Seq.toArray
|
|
Seq.scan (+) 0.0 (Sample.normalSeq 0.0 0.5 a) |> Seq.take 10 |> Seq.toArray
|
|
Seq.scan (+) 0.0 (s1seq a) |> Seq.take 10 |> Seq.toArray
|
|
|
|
(**
|
|
|
|
Distributions can not just be used to generate random samples.
|
|
You can use them to evaluate distribution properties or functions
|
|
with the given parametrization.
|
|
|
|
*)
|
|
|
|
// distribution properties of the gamma dist we configured above
|
|
let gammaStats = (
|
|
gamma.Mean,
|
|
gamma.Variance,
|
|
gamma.StdDev,
|
|
gamma.Entropy,
|
|
gamma.Skewness,
|
|
gamma.Mode
|
|
)
|
|
|
|
// probability distribution functions of the normal dist we configured above
|
|
let nd = normal.Density(4.0) (* pdf *)
|
|
let ndLn = normal.DensityLn(4.0) (* ln(pdf) *)
|
|
let nc = normal.CumulativeDistribution(4.0) (* cdf *)
|
|
let nic = normal.InverseCumulativeDistribution(0.7) (* invcdf *)
|
|
|
|
// Distribution functions can also be evaluated without creating an object,
|
|
// but then you have to pass in the distribution parameters as first arguments:
|
|
let nd2 = Normal.PDF(3.0, sqrt 1.5, 4.0)
|
|
let ndLn2 = Normal.PDFLn(3.0, sqrt 1.5, 4.0)
|
|
let nc2 = Normal.CDF(3.0, sqrt 1.5, 4.0)
|
|
let nic2 = Normal.InvCDF(3.0, sqrt 1.5, 0.7)
|
|
|
|
(**
|
|
|
|
Some of the distributions also have routines for maximum-likelihood parameter
|
|
estimation from a set of samples:
|
|
|
|
*)
|
|
|
|
let estimation = LogNormal.Estimate([| 2.0; 1.5; 2.1; 1.2; 3.0; 2.4; 1.8 |])
|
|
let mean, variance = estimation.Mean, estimation.Variance
|
|
let moreSamples = estimation.Samples() |> Seq.take 10 |> Seq.toArray
|
|
|