diff --git a/src/FSharp/FSharp.fsproj b/src/FSharp/FSharp.fsproj index b1b485eb..7711a025 100644 --- a/src/FSharp/FSharp.fsproj +++ b/src/FSharp/FSharp.fsproj @@ -61,7 +61,7 @@ - + diff --git a/src/FSharp/RandomVariableMonad.fs b/src/FSharp/RandomVariable.fs similarity index 50% rename from src/FSharp/RandomVariableMonad.fs rename to src/FSharp/RandomVariable.fs index da2842ec..9bbd1e09 100644 --- a/src/FSharp/RandomVariableMonad.fs +++ b/src/FSharp/RandomVariable.fs @@ -1,56 +1,57 @@ -namespace MathNet.Numerics +module MathNet.Numerics.Probability #nowarn "40" open System open System.Collections open System.Collections.Generic +open MathNet.Numerics -module RandomVariable = - - type 'a Outcome = { - Value: 'a - Probability : BigRational } - - type 'a RandomVariable = 'a Outcome seq - - // P(A AND B) = P(A | B) * P(B) - let bind (f: 'a -> 'b RandomVariable) (dist:'a RandomVariable) = - dist - |> Seq.map (fun p1 -> - f p1.Value - |> Seq.map (fun p2 -> - { Value = p2.Value; - Probability = - p1.Probability * p2.Probability})) - |> Seq.concat : 'b RandomVariable - - /// Sequentially compose two actions, passing any value produced by the first as an argument to the second. - let inline (>>=) dist f = bind f dist - /// Flipped >>= - let inline (=<<) f dist = bind f dist - - /// Inject a value into the RandomVariable type - let returnM (value:'a) = - Seq.singleton { Value = value ; Probability = 1N/1N } - : 'a RandomVariable - - type RandomVariableMonadBuilder() = - member this.Bind (r, f) = bind f r - member this.Return x = returnM x - member this.ReturnFrom x = x - - let randomVariable = RandomVariableMonadBuilder() - +type Outcome<'T> = { + Value: 'T + Probability : BigRational } + +type RandomVariable<'T> = Outcome<'T> seq + +// P(A AND B) = P(A | B) * P(B) +let private bind f dist = + dist + |> Seq.map (fun p1 -> + f p1.Value + |> Seq.map (fun p2 -> + { Value = p2.Value; + Probability = + p1.Probability * p2.Probability})) + |> Seq.concat + +/// Inject a value into the RandomVariable type +let private returnM value = + Seq.singleton { Value = value ; Probability = 1N/1N } + +type RandomVariableBuilder() = + member this.Bind (r, f) = bind f r + member this.Return x = returnM x + member this.ReturnFrom x = x + +let randomVariable = RandomVariableBuilder() + +type CoinSide = + | Heads + | Tails + +[] +[] +module RandomVariable = + // Create some helpers - let toUniformDistribution seq : 'a RandomVariable = + let toUniformDistribution seq = let l = Seq.length seq seq |> Seq.map (fun e -> { Value = e; Probability = 1N / bignum.FromInt l }) - let probability (dist:'a RandomVariable) = + let probability dist = dist |> Seq.map (fun o -> o.Probability) |> Seq.sum @@ -60,13 +61,9 @@ module RandomVariable = let fairDice sides = toUniformDistribution [1..sides] - type CoinSide = - | Heads - | Tails - let fairCoin = toUniformDistribution [Heads; Tails] - let filter predicate (dist:'a RandomVariable) : 'a RandomVariable = + let filter predicate dist = dist |> Seq.filter (fun o -> predicate o.Value) let filterInAnyOrder items dist = @@ -74,7 +71,7 @@ module RandomVariable = |> Seq.fold (fun d item -> filter (Seq.exists ((=) (item))) d) dist /// Transforms a RandomVariable value by using a specified mapping function. - let map f (dist:'a RandomVariable) : 'b RandomVariable = + let map f dist = dist |> Seq.map (fun o -> { Value = f o.Value; Probability = o.Probability }) diff --git a/src/FSharpPortable/FSharpPortable.fsproj b/src/FSharpPortable/FSharpPortable.fsproj index 481ad7d9..63aaba14 100644 --- a/src/FSharpPortable/FSharpPortable.fsproj +++ b/src/FSharpPortable/FSharpPortable.fsproj @@ -78,8 +78,8 @@ q.fs - - RandomVariableMonad.fs + + RandomVariable.fs diff --git a/src/FSharpUnitTests/PokerTests.fs b/src/FSharpUnitTests/PokerTests.fs index 4276d06b..48382db9 100644 --- a/src/FSharpUnitTests/PokerTests.fs +++ b/src/FSharpUnitTests/PokerTests.fs @@ -1,7 +1,7 @@ module MathNet.Numerics.Tests.PokerTests open MathNet.Numerics -open MathNet.Numerics.RandomVariable +open MathNet.Numerics.Probability open NUnit.Framework open FsUnit @@ -30,67 +30,69 @@ let isConnected c1 c2 = [] let ``When drawing from a full deck, then the probability for an Ace should equal 4/52``() = completeDeck - |> selectOne |> map fst - |> filter (fun card -> value card = A) - |> probability + |> RandomVariable.selectOne + |> RandomVariable.map fst + |> RandomVariable.filter (fun card -> value card = A) + |> RandomVariable.probability |> should equal (4N/52N) [] let ``When drawing from a full deck, then the probability should equal 1/52``() = completeDeck - |> selectOne |> map fst - |> filter ((=) (A,Spades)) - |> probability + |> RandomVariable.selectOne + |> RandomVariable.map fst + |> RandomVariable.filter ((=) (A,Spades)) + |> RandomVariable.probability |> should equal (1N/52N) [] let ``When drawing from a full deck, then the probability for the Ace of Clubs and Ace of Spaces (in order) should equal 1/52 * 1/51``() = completeDeck - |> select 2 - |> filter ((=) [A,Clubs; A,Spades]) - |> probability + |> RandomVariable.select 2 + |> RandomVariable.filter ((=) [A,Clubs; A,Spades]) + |> RandomVariable.probability |> should equal (1N/52N * 1N/51N) [] let ``When drawing from a full deck, then the probability for the Ace of Clubs and Ace of Spaces (in any order) should equal (1/52 * 1/51) * 2``() = completeDeck - |> select 2 - |> filterInAnyOrder [A,Clubs; A,Spades] - |> probability + |> RandomVariable.select 2 + |> RandomVariable.filterInAnyOrder [A,Clubs; A,Spades] + |> RandomVariable.probability |> should equal ((1N/52N * 1N/51N) * 2N) [] let ``When drawing the Ace of Spades and the Ace of Clubs, then the probability for drawing another Ace should equal 2/50``() = completeDeck - |> remove [A,Clubs; A,Spades] - |> toUniformDistribution - |> filter (fun card -> value card = A) - |> probability + |> RandomVariable.remove [A,Clubs; A,Spades] + |> RandomVariable.toUniformDistribution + |> RandomVariable.filter (fun card -> value card = A) + |> RandomVariable.probability |> should equal (2N/50N) [] let ``When drawing from the full deck, then the probability for drawing a Pair preflop should equal 1/17``() = completeDeck - |> select 2 - |> filter (fun (c1::c2::_) -> isPair c1 c2) - |> probability + |> RandomVariable.select 2 + |> RandomVariable.filter (fun (c1::c2::_) -> isPair c1 c2) + |> RandomVariable.probability |> should equal (1N/17N) [] let ``When drawing from the full deck, then the probability for drawing Suited Connectors should equal 1/25``() = completeDeck - |> select 2 - |> filter (fun (c1::c2::_) -> isSuited c1 c2 && isConnected c1 c2) - |> probability + |> RandomVariable.select 2 + |> RandomVariable.filter (fun (c1::c2::_) -> isSuited c1 c2 && isConnected c1 c2) + |> RandomVariable.probability |> should equal (2N/51N) [] let ``When holding 3 Spades after the flop, than the probability for drawing a flush should equal 10/47*9/46``() = completeDeck - |> remove [A,Clubs; A,Spades] // preflop - |> remove [2,Clubs; 3,Spades; 7,Spades] // flop - |> select 2 - |> filter (fun (c1::c2::_) -> suit c1 = Spades && suit c2 = Spades) - |> probability + |> RandomVariable.remove [A,Clubs; A,Spades] // preflop + |> RandomVariable.remove [2,Clubs; 3,Spades; 7,Spades] // flop + |> RandomVariable.select 2 + |> RandomVariable.filter (fun (c1::c2::_) -> suit c1 = Spades && suit c2 = Spades) + |> RandomVariable.probability |> should equal (10N/47N*9N/46N) \ No newline at end of file diff --git a/src/FSharpUnitTests/RandomVariableTests.fs b/src/FSharpUnitTests/RandomVariableTests.fs index e9a80f20..93305aab 100644 --- a/src/FSharpUnitTests/RandomVariableTests.fs +++ b/src/FSharpUnitTests/RandomVariableTests.fs @@ -1,44 +1,44 @@ module MathNet.Numerics.Tests.RandomVariableTests open MathNet.Numerics -open MathNet.Numerics.RandomVariable +open MathNet.Numerics.Probability open NUnit.Framework open FsUnit [] let ``When creating a empty randomVariable, then the probability should be 1``() = let actual = randomVariable { return () } - probability actual |> should equal (1N/1N) + RandomVariable.probability actual |> should equal (1N/1N) let sumOfTwoFairDices = randomVariable { - let! d1 = fairDice 6 - let! d2 = fairDice 6 + let! d1 = RandomVariable.fairDice 6 + let! d2 = RandomVariable.fairDice 6 return d1 + d2 } [] let ``When creating two fair dices, then P(Sum of 2 dices = 7) should be 1/6``() = sumOfTwoFairDices - |> filter ((=) 7) - |> probability + |> RandomVariable.filter ((=) 7) + |> RandomVariable.probability |> should equal (1N/6N) let fairCoinAndDice = randomVariable { - let! d = fairDice 6 - let! c = fairCoin + let! d = RandomVariable.fairDice 6 + let! c = RandomVariable.fairCoin return d,c } [] let ``When creating a fair coin and a fair dice, then P(Heads) should be 1/2``() = fairCoinAndDice - |> filter (fun (_,c) -> c = Heads) - |> probability + |> RandomVariable.filter (fun (_,c) -> c = Heads) + |> RandomVariable.probability |> should equal (1N/2N) [] let ``When creating a fair coin and a fair dice, then P(Heads and dice > 3) should be 1/4``() = fairCoinAndDice - |> filter (fun (d,c) -> c = Heads && d > 3) - |> probability + |> RandomVariable.filter (fun (d,c) -> c = Heads && d > 3) + |> RandomVariable.probability |> should equal (1N/4N) // MontyHall Problem @@ -49,22 +49,22 @@ type Outcome = | Car | Goat -let firstChoice = toUniformDistribution [Car; Goat; Goat] +let firstChoice = RandomVariable.toUniformDistribution [Car; Goat; Goat] let switch firstCoice = match firstCoice with | Car -> // If you had the car and you switch ==> you lose since there are only goats left - certainly Goat + RandomVariable.certainly Goat | Goat -> // If you had the goat, the host has to take out another goat ==> you win - certainly Car + RandomVariable.certainly Car [] let ``When making the first choice in a MontyHall situation, the chances to win should be 1/3``() = firstChoice - |> filter ((=) Car) - |> probability + |> RandomVariable.filter ((=) Car) + |> RandomVariable.probability |> should equal (1N/3N) let montyHallWithSwitch = randomVariable { @@ -74,6 +74,6 @@ let montyHallWithSwitch = randomVariable { [] let ``When switching in a MontyHall situation, the chances to win should be 2/3``() = montyHallWithSwitch - |> filter ((=) Car) - |> probability + |> RandomVariable.filter ((=) Car) + |> RandomVariable.probability |> should equal (2N/3N) \ No newline at end of file