Browse Source

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

pull/61/head
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="q.fsi" />
<Compile Include="q.fs" />
<Compile Include="RandomVariableMonad.fs" />
<Compile Include="RandomVariable.fs" />
</ItemGroup>
<ItemGroup>
<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"
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
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
[<RequireQualifiedAccess>]
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 })

4
src/FSharpPortable/FSharpPortable.fsproj

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

58
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 =
[<Test>]
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)
[<Test>]
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)
[<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``() =
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)
[<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``() =
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)
[<Test>]
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)
[<Test>]
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)
[<Test>]
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)
[<Test>]
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)

38
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
[<Test>]
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 }
[<Test>]
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 }
[<Test>]
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)
[<Test>]
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
[<Test>]
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 {
[<Test>]
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)
Loading…
Cancel
Save