type Probability = private Probability of double module Probability = let create ( probability : double ) : Probability option = if probability >= 0.0 && probability <= 1.0 then Some ( Probability probability ) else None let getProbability ( Probability probability ) = probability // Creating let prob = Probability.create ( 1.0 / 2.0 ) // Getting the value match prob with | Some p -> Probability.getProbability p | None -> nan type Distribution<'T> = seq< 'T * Probability > module UniformDistribution = let private uniformRandomNumberGenerator = System.Random() let create ( events : seq< int > ) : Distribution = seq { let countOfSequence = Seq.length events let distributionSequence = events |> Seq.map( fun e -> e, Probability.create ( 1.0 / double( countOfSequence ))) |> Seq.filter( fun ( e, p ) -> p.IsSome ) |> Seq.map( fun (e, p) -> e, p.Value ) yield! distributionSequence } let drawOneFromDistribution ( uniformDistribution : Distribution ) : int * Probability = let countOfDistribution = Seq.length uniformDistribution let idx = uniformRandomNumberGenerator.Next( 0, countOfDistribution ) uniformDistribution |> Seq.item idx let drawOneFromEvents ( events : seq< int > ) : int * Probability = let distribution = create events drawOneFromDistribution distribution let diceRollDistribution = UniformDistribution.create [ 1..6 ] printfn "%A" ( Seq.toList diceRollDistribution ) printfn "%A" ( UniformDistribution.drawOneFromDistribution diceRollDistribution ) type Outcome = Win | Lose type Door = A | B | C | NA type GameState = { winningDoor : Door; chosenDoor : Door; openedDoor : Door } let doors = [ A; B; C ] let startGameState : GameState = { winningDoor = NA; chosenDoor = NA; openedDoor = NA } let hidePrize ( state : GameState ) : GameState = let winningDoorIdx = fst ( UniformDistribution.drawOneFromEvents ( [ 1..3 ] )) - 1 { state with winningDoor = doors.[ winningDoorIdx ] } hidePrize startGameState let initializeGame : GameState = hidePrize startGameState initializeGame let chooseDoor ( state : GameState ) ( door : Door ) : GameState = { state with chosenDoor = door } let chooseRandomDoor ( state : GameState ) : GameState = let chosenDoorIdx = fst ( UniformDistribution.drawOneFromEvents ( [ 1..3 ] )) - 1 { state with chosenDoor = doors.[ chosenDoorIdx ] } let chosenRandomDoor = chooseRandomDoor initializeGame chosenRandomDoor let openDoor ( state : GameState ) : GameState = // Choose the Non-Winning Door that hasn't been chosen by the contestant. let doorToOpen = doors |> List.except [ state.winningDoor; state.chosenDoor ] |> List.item 0 { state with openedDoor = doorToOpen } let postOpenDoor = openDoor ( chosenRandomDoor ) postOpenDoor type Strategy = GameState -> Outcome let switch ( state : GameState ) : Outcome = let doorToSwitchTo = doors |> List.except [ state.chosenDoor; state.openedDoor ] |> List.item 0 if doorToSwitchTo = state.winningDoor then Win else Lose let stay ( state : GameState ) : Outcome = if state.chosenDoor = state.winningDoor then Win else Lose printfn "On Switch: %A" ( switch postOpenDoor ) printfn "On Stay: %A" ( stay postOpenDoor ) let simulationCount = 10000 let simulateMontyHall ( strategy : Strategy ) : Outcome = let game = initializeGame |> chooseRandomDoor |> openDoor strategy( game ) simulateMontyHall switch #load "Paket.fsx" Paket.Package([ "XPlot.Plotly" ]) #load "XPlot.Plotly.fsx" #load "XPlot.Plotly.Paket.fsx" open XPlot.Plotly let generateDistributionOfStaying ( numberOfTrials : int ) : Outcome seq = let mutable list = [] for i in 1 .. numberOfTrials do list <- list @ [ simulateMontyHall stay ] Seq.ofList list let simulationOfStaying = generateDistributionOfStaying simulationCount let winCountOfStaying = simulationOfStaying |> Seq.filter( fun x -> x = Win ) |> Seq.length let xAxisStaying = [ "Win"; "Loss" ] let yAxisStaying = [ winCountOfStaying; ( Seq.length simulationOfStaying - winCountOfStaying )] let stayingData = List.zip xAxisStaying yAxisStaying let optionsStaying = Layout( title = "Outcome Count of Staying" ) stayingData |> Chart.Bar |> Chart.WithOptions optionsStaying |> Chart.WithHeight 500 |> Chart.WithWidth 700 let probabilityOfWinByStaying = ( double winCountOfStaying ) / ( double simulationCount ) printfn "Probability of Winning by Staying with Chosen Door: %A" probabilityOfWinByStaying let generateDistributionOfSwitching ( numberOfTrials : int ) : Outcome list = let mutable list = [] for i in 1 .. numberOfTrials do list <- list @ [ simulateMontyHall switch ] list let simulationOfSwitching = generateDistributionOfSwitching simulationCount let winCountOfSwitching = simulationOfSwitching |> Seq.filter( fun x -> x = Win ) |> Seq.length let xAxisSwitching = [ "Win"; "Loss" ] let yAxisSwitching = [ winCountOfSwitching; ( Seq.length simulationOfSwitching - winCountOfSwitching )] let switchingData = List.zip xAxisSwitching yAxisSwitching let optionsSwitching = Layout( title = "Outcome Count of Switching" ) switchingData |> Chart.Bar |> Chart.WithOptions optionsSwitching |> Chart.WithHeight 500 |> Chart.WithWidth 700 let probabilityOfWinBySwitching = ( double winCountOfSwitching ) / ( double simulationCount ) printfn "Probability of Winning by Switching with Chosen Door: %A" probabilityOfWinBySwitching Paket.Package([ "FsLab" ]) #load "FsLab.fsx" open MathNet.Numerics.Distributions // Layout A [] let numberOfVisitorsLayoutA = 100 [] let numberOfSubscribersA = 4 // Layout B [] let numberOfVisitorsLayoutB = 40 [] let numberOfSubscribersB = 2 [] let numberOfSimulationSteps = 10000 // For Layout A: Uniform Distribution let uniformPriorSampler : seq< double > = let continuousUniform = ContinuousUniform( 0.0, 1.0 ) seq { while true do yield continuousUniform.Sample() } let priorSamplerLayoutA = uniformPriorSampler |> Seq.take numberOfSimulationSteps priorSamplerLayoutA // For Layout B: Normal Distribution let normalPriorSampler ( mu : double ) ( sigma : double ) : seq< double > = let normalDistribution = Normal( mu, sigma ) seq { while true do let sample = normalDistribution.Sample() if sample >= 0.0 && sample <= 1.0 then yield sample else () } // We choose the mu and sigma to be some arbitarily alue. // NOTE: these parameters can be tuned. let priorSamplerLayoutB = normalPriorSampler 0.05 0.01 // Expect a right skewed distribution wrt a uniform distribution |> Seq.take numberOfSimulationSteps priorSamplerLayoutB let overlaidTrace1Prior = Histogram( x = priorSamplerLayoutA, opacity = 0.75, name = "Layout A" ) let overlaidTrace2Prior = Histogram( x = priorSamplerLayoutB, opacity = 0.75, name = "Layout B" ) let overlaidLayoutPrior = Layout( barmode = "overlay", title = "Prior Distributions: A vs. B" ) [overlaidTrace1Prior; overlaidTrace2Prior] |> Chart.Plot |> Chart.WithLayout overlaidLayoutPrior |> Chart.WithWidth 700 |> Chart.WithHeight 500 open System let randomNumberGenerator = Random() let simulateSubscription ( priorSample : double ) ( nVisitors : int ) : int = [ 1..nVisitors ] |> List.map( fun x -> randomNumberGenerator.NextDouble() ) |> List.filter( fun d -> d < priorSample ) |> List.sum |> int // Test the Simulated Subscriptions. printfn "%A" ( simulateSubscription 0.01 numberOfSimulationSteps ) printfn "%A" ( simulateSubscription 0.02 numberOfSimulationSteps ) printfn "%A" ( simulateSubscription 0.03 numberOfSimulationSteps ) let simulate ( priorSample : double ) ( numberOfVisitors : int ) : int = simulateSubscription priorSample numberOfVisitors let applySimulationLayoutA ( priorSample : double ) : int = simulate priorSample numberOfVisitorsLayoutA let applySimulationLayoutB ( priorSample : double ) : int = simulate priorSample numberOfVisitorsLayoutB let posteriorDistribution ( numberOfSubscriptions : int ) ( priorSampler : seq ) ( simulate : double -> int ) : seq = seq { for p in priorSampler do if simulate p = numberOfSubscriptions then yield p else () } let posteriorASeq = posteriorDistribution numberOfSubscribersA uniformPriorSampler applySimulationLayoutA let posteriorLayoutA = posteriorASeq |> Seq.take numberOfSimulationSteps Histogram( x = posteriorLayoutA ) |> Chart.Plot |> Chart.WithTitle("Posterior Distribution: A") |> Chart.WithWidth 700 |> Chart.WithHeight 500 let posteriorBSeq = posteriorDistribution numberOfSubscribersB uniformPriorSampler applySimulationLayoutB let posteriorLayoutB = posteriorBSeq |> Seq.take numberOfSimulationSteps posteriorLayoutB Histogram( x = posteriorLayoutB ) |> Chart.Plot |> Chart.WithTitle("Posterior Distribution: B") |> Chart.WithWidth 700 |> Chart.WithHeight 500 let overlaidTrace1Posterior = Histogram( x = posteriorLayoutA, opacity = 0.75, name = "Layout A" ) let overlaidTrace2Posterior = Histogram( x = posteriorLayoutB, opacity = 0.75, name = "Layout B" ) let overlaidLayoutPosterior = Layout( barmode = "overlay", title = "Posterior Distributions: A vs. B" ) [overlaidTrace1Posterior; overlaidTrace2Posterior] |> Chart.Plot |> Chart.WithLayout overlaidLayoutPosterior |> Chart.WithWidth 700 |> Chart.WithHeight 500 let subscriptionFraction = let countOfCasesWhereLayoutBIsPrefered = posteriorLayoutA |> Seq.zip posteriorLayoutB |> Seq.filter( fun ( a, b ) -> b > a ) |> Seq.length |> double countOfCasesWhereLayoutBIsPrefered / ( double numberOfSimulationSteps ) subscriptionFraction