Skip to content

Instantly share code, notes, and snippets.

@Malix-Labs
Last active February 17, 2025 01:07
Show Gist options
  • Save Malix-Labs/6c1503369d7d7845d3973eeba25902c9 to your computer and use it in GitHub Desktop.
Save Malix-Labs/6c1503369d7d7845d3973eeba25902c9 to your computer and use it in GitHub Desktop.
Bertrand Paradox

Betrand Paradox

Haskell impelmentation (arithmetics only) of the Bertrand Paradox

import System.Random
import Control.Monad
import Data.Fixed
import Text.Printf
-- Type alias for a Point (for simplicity, in 2D)
type Point = (Double, Double)
-- Radius of the circle (assume unit circle for simplicity)
circleRadius :: Double
circleRadius = 1.0
-- Side length of the inscribed equilateral triangle in a unit circle is sqrt(3)
triangleSideLength :: Double
triangleSideLength = sqrt 3.0
-- Distance between two points
distance :: Point -> Point -> Double
distance (x1, y1) (x2, y2) = sqrt ((x2 - x1)^2 + (y2 - y1)^2)
-- Generate a random point on the circumference of a unit circle
randomPointOnCircle :: RandomGen g => g -> (Point, g)
randomPointOnCircle gen =
let (angle, gen') = randomR (0, 2 * pi) gen
x = circleRadius * cos angle
y = circleRadius * sin angle
in ((x, y), gen')
-- Generate a random point within the circle (uniform distribution over the area)
randomPointInCircle :: RandomGen g => g -> (Point, g)
randomPointInCircle gen =
let (r, gen') = randomR (0, circleRadius) gen -- Incorrect: leads to non-uniform distribution
(angle, gen'') = randomR (0, 2 * pi) gen'
x = r * cos angle
y = r * sin angle
in ((x, y), gen'')
-- Correct method for uniform distribution within the circle using squared radius
randomPointInCircleUniform :: RandomGen g => g -> (Point, g)
randomPointInCircleUniform gen =
let (rSquared, gen') = randomR (0, circleRadius^2) gen
r = sqrt rSquared
(angle, gen'') = randomR (0, 2 * pi) gen'
x = r * cos angle
y = r * sin angle
in ((x, y), gen'')
-- Method 1: Random Endpoints
-- Choose two points randomly on the circumference and form a chord.
bertrandMethod1 :: RandomGen g => Int -> g -> ([Double], g)
bertrandMethod1 numChords gen = runMethod gen numChords $ \g -> do
let (p1, g') = randomPointOnCircle g
(p2, g'') = randomPointOnCircle g'
chordLength = distance p1 p2
return (chordLength, g'')
-- Method 2: Random Radius
-- Choose a radius, then choose a point randomly on that radius,
-- and construct a chord perpendicular to the radius at that point.
bertrandMethod2 :: RandomGen g => Int -> g -> ([Double], g)
bertrandMethod2 numChords gen = runMethod gen numChords $ \g -> do
let (r, g') = randomR (0, circleRadius) gen -- Distance from center along the radius
chordLength = 2 * sqrt (circleRadius^2 - r^2)
return (chordLength, g')
-- Method 3: Random Midpoint
-- Choose a point randomly within the circle as the midpoint of the chord.
-- The chord is the unique chord with this point as its midpoint.
bertrandMethod3 :: RandomGen g => Int -> g -> ([Double], g)
bertrandMethod3 numChords gen = runMethod gen numChords $ \g -> do
let (midPoint, g') = randomPointInCircleUniform g
-- Distance from center to midpoint is simply the distance from origin (0,0) to midpoint
r = distance (0,0) midPoint
chordLength = 2 * sqrt (circleRadius^2 - r^2)
return (chordLength, g')
-- Helper function to run a Bertrand method simulation multiple times
runMethod :: RandomGen g => g -> Int -> (g -> IO (Double, g)) -> IO ([Double], g)
runMethod initialGen numChords method = do
(chordLengths, finalGen) <- foldM (\(lengths, gen) _ -> do
(length, nextGen) <- method gen
return (lengths ++ [length], nextGen)
) ([], initialGen) [1..numChords]
return (chordLengths, finalGen)
-- Calculate the probability of a chord being longer than the triangle side
calculateProbability :: [Double] -> Double
calculateProbability chordLengths =
let longerChords = length $ filter (> triangleSideLength) chordLengths
totalChords = length chordLengths
in fromIntegral longerChords / fromIntegral totalChords
-- Perform the Bertrand Paradox simulation for a given method and number of trials
simulateBertrand :: String -> (RandomGen g => Int -> g -> ([Double], g)) -> Int -> IO ()
simulateBertrand methodName method numTrials = do
gen <- getStdGen
let (chordLengths, _) = runMethod gen numTrials $ \g -> method 1 g >>= \(lengths, nextGen) -> return (head lengths, nextGen) -- slightly less efficient, but keeps interface consistent with foldM
let probability = calculateProbability chordLengths
printf "Method: %s\n" methodName
printf "Number of trials: %d\n" numTrials
printf "Probability (Chord > Triangle Side): %0.4f\n\n" probability
main :: IO ()
main = do
let numTrials = 100000 -- Number of chords to generate for each method
simulateBertrand "Method 1: Random Endpoints" bertrandMethod1 numTrials
simulateBertrand "Method 2: Random Radius" bertrandMethod2 numTrials
simulateBertrand "Method 3: Random Midpoint" bertrandMethod3 numTrials
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment