Math.NET 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

(**
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