Browse Source

Make RandomVariable more idiomatic and move it out of the global Math.Numerics namespace

v2
Gustavo Guerra 14 years ago
parent
commit
0aa78338f9
  1. 2
      src/FSharp/FSharp.fsproj
  2. 87
      src/FSharp/RandomVariable.fs
  3. 4
      src/FSharpPortable/FSharpPortable.fsproj
  4. 58
      src/FSharpUnitTests/PokerTests.fs
  5. 38
      src/FSharpUnitTests/RandomVariableTests.fs

2
src/FSharp/FSharp.fsproj

@ -61,7 +61,7 @@
<Compile Include="complex.fs" /> <Compile Include="complex.fs" />
<Compile Include="q.fsi" /> <Compile Include="q.fsi" />
<Compile Include="q.fs" /> <Compile Include="q.fs" />
<Compile Include="RandomVariableMonad.fs" /> <Compile Include="RandomVariable.fs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Reference Include="FSharp.Core" /> <Reference Include="FSharp.Core" />

87
src/FSharp/RandomVariableMonad.fs → src/FSharp/RandomVariable.fs

@ -1,56 +1,57 @@
namespace MathNet.Numerics module MathNet.Numerics.Probability
#nowarn "40" #nowarn "40"
open System open System
open System.Collections open System.Collections
open System.Collections.Generic open System.Collections.Generic
open MathNet.Numerics
module RandomVariable = type Outcome<'T> = {
Value: 'T
type 'a Outcome = { Probability : BigRational }
Value: 'a
Probability : BigRational } type RandomVariable<'T> = Outcome<'T> seq
type 'a RandomVariable = 'a Outcome seq // P(A AND B) = P(A | B) * P(B)
let private bind f dist =
// P(A AND B) = P(A | B) * P(B) dist
let bind (f: 'a -> 'b RandomVariable) (dist:'a RandomVariable) = |> Seq.map (fun p1 ->
dist f p1.Value
|> Seq.map (fun p1 -> |> Seq.map (fun p2 ->
f p1.Value { Value = p2.Value;
|> Seq.map (fun p2 -> Probability =
{ Value = p2.Value; p1.Probability * p2.Probability}))
Probability = |> Seq.concat
p1.Probability * p2.Probability}))
|> Seq.concat : 'b RandomVariable /// Inject a value into the RandomVariable type
let private returnM value =
/// Sequentially compose two actions, passing any value produced by the first as an argument to the second. Seq.singleton { Value = value ; Probability = 1N/1N }
let inline (>>=) dist f = bind f dist
/// Flipped >>= type RandomVariableBuilder() =
let inline (=<<) f dist = bind f dist member this.Bind (r, f) = bind f r
member this.Return x = returnM x
/// Inject a value into the RandomVariable type member this.ReturnFrom x = x
let returnM (value:'a) =
Seq.singleton { Value = value ; Probability = 1N/1N } let randomVariable = RandomVariableBuilder()
: 'a RandomVariable
type CoinSide =
type RandomVariableMonadBuilder() = | Heads
member this.Bind (r, f) = bind f r | Tails
member this.Return x = returnM x
member this.ReturnFrom x = x [<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
[<RequireQualifiedAccess>]
let randomVariable = RandomVariableMonadBuilder() module RandomVariable =
// Create some helpers // Create some helpers
let toUniformDistribution seq : 'a RandomVariable = let toUniformDistribution seq =
let l = Seq.length seq let l = Seq.length seq
seq seq
|> Seq.map (fun e -> |> Seq.map (fun e ->
{ Value = e; { Value = e;
Probability = 1N / bignum.FromInt l }) Probability = 1N / bignum.FromInt l })
let probability (dist:'a RandomVariable) = let probability dist =
dist dist
|> Seq.map (fun o -> o.Probability) |> Seq.map (fun o -> o.Probability)
|> Seq.sum |> Seq.sum
@ -60,13 +61,9 @@ module RandomVariable =
let fairDice sides = toUniformDistribution [1..sides] let fairDice sides = toUniformDistribution [1..sides]
type CoinSide =
| Heads
| Tails
let fairCoin = toUniformDistribution [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) dist |> Seq.filter (fun o -> predicate o.Value)
let filterInAnyOrder items dist = let filterInAnyOrder items dist =
@ -74,7 +71,7 @@ module RandomVariable =
|> Seq.fold (fun d item -> filter (Seq.exists ((=) (item))) d) dist |> Seq.fold (fun d item -> filter (Seq.exists ((=) (item))) d) dist
/// Transforms a RandomVariable value by using a specified mapping function. /// Transforms a RandomVariable value by using a specified mapping function.
let map f (dist:'a RandomVariable) : 'b RandomVariable = let map f dist =
dist dist
|> Seq.map (fun o -> { Value = f o.Value; Probability = o.Probability }) |> Seq.map (fun o -> { Value = f o.Value; Probability = o.Probability })

4
src/FSharpPortable/FSharpPortable.fsproj

@ -78,8 +78,8 @@
<Compile Include="..\FSharp\q.fs"> <Compile Include="..\FSharp\q.fs">
<Link>q.fs</Link> <Link>q.fs</Link>
</Compile> </Compile>
<Compile Include="..\FSharp\RandomVariableMonad.fs"> <Compile Include="..\FSharp\RandomVariable.fs">
<Link>RandomVariableMonad.fs</Link> <Link>RandomVariable.fs</Link>
</Compile> </Compile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

58
src/FSharpUnitTests/PokerTests.fs

@ -1,7 +1,7 @@
module MathNet.Numerics.Tests.PokerTests module MathNet.Numerics.Tests.PokerTests
open MathNet.Numerics open MathNet.Numerics
open MathNet.Numerics.RandomVariable open MathNet.Numerics.Probability
open NUnit.Framework open NUnit.Framework
open FsUnit open FsUnit
@ -30,67 +30,69 @@ let isConnected c1 c2 =
[<Test>] [<Test>]
let ``When drawing from a full deck, then the probability for an Ace should equal 4/52``() = let ``When drawing from a full deck, then the probability for an Ace should equal 4/52``() =
completeDeck completeDeck
|> selectOne |> map fst |> RandomVariable.selectOne
|> filter (fun card -> value card = A) |> RandomVariable.map fst
|> probability |> RandomVariable.filter (fun card -> value card = A)
|> RandomVariable.probability
|> should equal (4N/52N) |> should equal (4N/52N)
[<Test>] [<Test>]
let ``When drawing from a full deck, then the probability should equal 1/52``() = let ``When drawing from a full deck, then the probability should equal 1/52``() =
completeDeck completeDeck
|> selectOne |> map fst |> RandomVariable.selectOne
|> filter ((=) (A,Spades)) |> RandomVariable.map fst
|> probability |> RandomVariable.filter ((=) (A,Spades))
|> RandomVariable.probability
|> should equal (1N/52N) |> should equal (1N/52N)
[<Test>] [<Test>]
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``() = 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 completeDeck
|> select 2 |> RandomVariable.select 2
|> filter ((=) [A,Clubs; A,Spades]) |> RandomVariable.filter ((=) [A,Clubs; A,Spades])
|> probability |> RandomVariable.probability
|> should equal (1N/52N * 1N/51N) |> should equal (1N/52N * 1N/51N)
[<Test>] [<Test>]
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``() = 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 completeDeck
|> select 2 |> RandomVariable.select 2
|> filterInAnyOrder [A,Clubs; A,Spades] |> RandomVariable.filterInAnyOrder [A,Clubs; A,Spades]
|> probability |> RandomVariable.probability
|> should equal ((1N/52N * 1N/51N) * 2N) |> should equal ((1N/52N * 1N/51N) * 2N)
[<Test>] [<Test>]
let ``When drawing the Ace of Spades and the Ace of Clubs, then the probability for drawing another Ace should equal 2/50``() = let ``When drawing the Ace of Spades and the Ace of Clubs, then the probability for drawing another Ace should equal 2/50``() =
completeDeck completeDeck
|> remove [A,Clubs; A,Spades] |> RandomVariable.remove [A,Clubs; A,Spades]
|> toUniformDistribution |> RandomVariable.toUniformDistribution
|> filter (fun card -> value card = A) |> RandomVariable.filter (fun card -> value card = A)
|> probability |> RandomVariable.probability
|> should equal (2N/50N) |> should equal (2N/50N)
[<Test>] [<Test>]
let ``When drawing from the full deck, then the probability for drawing a Pair preflop should equal 1/17``() = let ``When drawing from the full deck, then the probability for drawing a Pair preflop should equal 1/17``() =
completeDeck completeDeck
|> select 2 |> RandomVariable.select 2
|> filter (fun (c1::c2::_) -> isPair c1 c2) |> RandomVariable.filter (fun (c1::c2::_) -> isPair c1 c2)
|> probability |> RandomVariable.probability
|> should equal (1N/17N) |> should equal (1N/17N)
[<Test>] [<Test>]
let ``When drawing from the full deck, then the probability for drawing Suited Connectors should equal 1/25``() = let ``When drawing from the full deck, then the probability for drawing Suited Connectors should equal 1/25``() =
completeDeck completeDeck
|> select 2 |> RandomVariable.select 2
|> filter (fun (c1::c2::_) -> isSuited c1 c2 && isConnected c1 c2) |> RandomVariable.filter (fun (c1::c2::_) -> isSuited c1 c2 && isConnected c1 c2)
|> probability |> RandomVariable.probability
|> should equal (2N/51N) |> should equal (2N/51N)
[<Test>] [<Test>]
let ``When holding 3 Spades after the flop, than the probability for drawing a flush should equal 10/47*9/46``() = let ``When holding 3 Spades after the flop, than the probability for drawing a flush should equal 10/47*9/46``() =
completeDeck completeDeck
|> remove [A,Clubs; A,Spades] // preflop |> RandomVariable.remove [A,Clubs; A,Spades] // preflop
|> remove [2,Clubs; 3,Spades; 7,Spades] // flop |> RandomVariable.remove [2,Clubs; 3,Spades; 7,Spades] // flop
|> select 2 |> RandomVariable.select 2
|> filter (fun (c1::c2::_) -> suit c1 = Spades && suit c2 = Spades) |> RandomVariable.filter (fun (c1::c2::_) -> suit c1 = Spades && suit c2 = Spades)
|> probability |> RandomVariable.probability
|> should equal (10N/47N*9N/46N) |> should equal (10N/47N*9N/46N)

38
src/FSharpUnitTests/RandomVariableTests.fs

@ -1,44 +1,44 @@
module MathNet.Numerics.Tests.RandomVariableTests module MathNet.Numerics.Tests.RandomVariableTests
open MathNet.Numerics open MathNet.Numerics
open MathNet.Numerics.RandomVariable open MathNet.Numerics.Probability
open NUnit.Framework open NUnit.Framework
open FsUnit open FsUnit
[<Test>] [<Test>]
let ``When creating a empty randomVariable, then the probability should be 1``() = let ``When creating a empty randomVariable, then the probability should be 1``() =
let actual = randomVariable { return () } let actual = randomVariable { return () }
probability actual |> should equal (1N/1N) RandomVariable.probability actual |> should equal (1N/1N)
let sumOfTwoFairDices = randomVariable { let sumOfTwoFairDices = randomVariable {
let! d1 = fairDice 6 let! d1 = RandomVariable.fairDice 6
let! d2 = fairDice 6 let! d2 = RandomVariable.fairDice 6
return d1 + d2 } return d1 + d2 }
[<Test>] [<Test>]
let ``When creating two fair dices, then P(Sum of 2 dices = 7) should be 1/6``() = let ``When creating two fair dices, then P(Sum of 2 dices = 7) should be 1/6``() =
sumOfTwoFairDices sumOfTwoFairDices
|> filter ((=) 7) |> RandomVariable.filter ((=) 7)
|> probability |> RandomVariable.probability
|> should equal (1N/6N) |> should equal (1N/6N)
let fairCoinAndDice = randomVariable { let fairCoinAndDice = randomVariable {
let! d = fairDice 6 let! d = RandomVariable.fairDice 6
let! c = fairCoin let! c = RandomVariable.fairCoin
return d,c } return d,c }
[<Test>] [<Test>]
let ``When creating a fair coin and a fair dice, then P(Heads) should be 1/2``() = let ``When creating a fair coin and a fair dice, then P(Heads) should be 1/2``() =
fairCoinAndDice fairCoinAndDice
|> filter (fun (_,c) -> c = Heads) |> RandomVariable.filter (fun (_,c) -> c = Heads)
|> probability |> RandomVariable.probability
|> should equal (1N/2N) |> should equal (1N/2N)
[<Test>] [<Test>]
let ``When creating a fair coin and a fair dice, then P(Heads and dice > 3) should be 1/4``() = let ``When creating a fair coin and a fair dice, then P(Heads and dice > 3) should be 1/4``() =
fairCoinAndDice fairCoinAndDice
|> filter (fun (d,c) -> c = Heads && d > 3) |> RandomVariable.filter (fun (d,c) -> c = Heads && d > 3)
|> probability |> RandomVariable.probability
|> should equal (1N/4N) |> should equal (1N/4N)
// MontyHall Problem // MontyHall Problem
@ -49,22 +49,22 @@ type Outcome =
| Car | Car
| Goat | Goat
let firstChoice = toUniformDistribution [Car; Goat; Goat] let firstChoice = RandomVariable.toUniformDistribution [Car; Goat; Goat]
let switch firstCoice = let switch firstCoice =
match firstCoice with match firstCoice with
| Car -> | Car ->
// If you had the car and you switch ==> you lose since there are only goats left // If you had the car and you switch ==> you lose since there are only goats left
certainly Goat RandomVariable.certainly Goat
| Goat -> | Goat ->
// If you had the goat, the host has to take out another goat ==> you win // If you had the goat, the host has to take out another goat ==> you win
certainly Car RandomVariable.certainly Car
[<Test>] [<Test>]
let ``When making the first choice in a MontyHall situation, the chances to win should be 1/3``() = let ``When making the first choice in a MontyHall situation, the chances to win should be 1/3``() =
firstChoice firstChoice
|> filter ((=) Car) |> RandomVariable.filter ((=) Car)
|> probability |> RandomVariable.probability
|> should equal (1N/3N) |> should equal (1N/3N)
let montyHallWithSwitch = randomVariable { let montyHallWithSwitch = randomVariable {
@ -74,6 +74,6 @@ let montyHallWithSwitch = randomVariable {
[<Test>] [<Test>]
let ``When switching in a MontyHall situation, the chances to win should be 2/3``() = let ``When switching in a MontyHall situation, the chances to win should be 2/3``() =
montyHallWithSwitch montyHallWithSwitch
|> filter ((=) Car) |> RandomVariable.filter ((=) Car)
|> probability |> RandomVariable.probability
|> should equal (2N/3N) |> should equal (2N/3N)
Loading…
Cancel
Save