{-|
Module      : Ranking.Glicko.Inference
License     : GPL-3
Maintainer  : rasmus@precenth.eu
Stability   : experimental

This module provides functions for predicting the outcome of a game between two players.

Example usage:

>>> :l test/Paper.hs
>>> :m + Data.Default
>>> let p1:p2:_ = compute players matches def
>>> p1
Player { playerId = 1
       , playerRating = 1464.0506705393013
       , playerDev = 151.51652412385727
       , playerVol = 5.9995984286488495e-2
       , playerInactivity = 0
       , playerAge = 1 }
>>> p2
Player { playerId = 2
       , playerRating = 1398.1435582337338
       , playerDev = 31.67021528115062
       , playerVol = 5.999912372888531e-2
       , playerInactivity = 0
       , playerAge = 1 }
>>> predict p1 p2
0.5732533698644847     -- Player 1 has a 57.3% chance of winning a single game.
>>> let Just f = boX 5
>>> predictBoX f p1 p2
0.6353973157904573     -- Player 1 has a 63.5% chance of winning a best-of-five match.

-}
module Ranking.Glicko.Inference ( predict
                                , predictBoX
                                , BoX
                                , boX
                                , fromBoX) where

import Ranking.Glicko.Core
import Ranking.Glicko.Types

import Data.Coerce (coerce)
import Statistics.Distribution
import Statistics.Distribution.Normal

-- | Computes the probability that Player A wins against Player B
predict :: Player 1 -- ^ Player A
        -> Player 1 -- ^ Player B
        -> Double
predict :: Player 1 -> Player 1 -> Double
predict Player 1
pla Player 1
plb = NormalDistribution -> Double -> Double
forall d. Distribution d => d -> Double -> Double
cumulative NormalDistribution
dist (Double
ra Double -> Double -> Double
forall a. Num a => a -> a -> a
- Double
rb)
  where Player { playerRating :: forall (version :: Nat). Player version -> Double
playerRating = Double
ra, playerDev :: forall (version :: Nat). Player version -> Double
playerDev = Double
da } = Player 1 -> Player 2
oldToNew Player 1
pla
        Player { playerRating :: forall (version :: Nat). Player version -> Double
playerRating = Double
rb, playerDev :: forall (version :: Nat). Player version -> Double
playerDev = Double
db } = Player 1 -> Player 2
oldToNew Player 1
plb
        dist :: NormalDistribution
dist = Double -> Double -> NormalDistribution
normalDistr Double
0 (Double
1 Double -> Double -> Double
forall a. Num a => a -> a -> a
+ Double
da Double -> Double -> Double
forall a. Num a => a -> a -> a
+ Double
db)
-- TODO: Check the above ^

-- | Represents a match played as best-of-X games.
newtype BoX = BoX Integer
  deriving Int -> BoX -> ShowS
[BoX] -> ShowS
BoX -> String
(Int -> BoX -> ShowS)
-> (BoX -> String) -> ([BoX] -> ShowS) -> Show BoX
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [BoX] -> ShowS
$cshowList :: [BoX] -> ShowS
show :: BoX -> String
$cshow :: BoX -> String
showsPrec :: Int -> BoX -> ShowS
$cshowsPrec :: Int -> BoX -> ShowS
Show

-- | Create a best-of-X match
boX :: Integer -> Maybe BoX
boX :: Integer -> Maybe BoX
boX Integer
n = if Integer -> Bool
forall a. Integral a => a -> Bool
odd Integer
n Bool -> Bool -> Bool
&& Integer
0 Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
< Integer
n Bool -> Bool -> Bool
&& Integer
n Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
<= Integer
11
           then BoX -> Maybe BoX
forall a. a -> Maybe a
Just (BoX -> Maybe BoX) -> BoX -> Maybe BoX
forall a b. (a -> b) -> a -> b
$ Integer -> BoX
BoX Integer
n
           else Maybe BoX
forall a. Maybe a
Nothing

-- | Destruct a best-of-X match
fromBoX :: BoX -> Integer
fromBoX :: BoX -> Integer
fromBoX = BoX -> Integer
coerce
{-# INLINE fromBoX #-}

-- | Same as 'predict', but computes the probability that
-- Player A wins a match played as best-of-X games.
predictBoX :: BoX -> Player 1 -> Player 1 -> Double
predictBoX :: BoX -> Player 1 -> Player 1 -> Double
predictBoX BoX
n Player 1
p1 Player 1
p2 =
  [Double] -> Double
forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
sum ([Double] -> Double) -> [Double] -> Double
forall a b. (a -> b) -> a -> b
$ (Integer -> Double) -> [Integer] -> [Double]
forall a b. (a -> b) -> [a] -> [b]
map (\Integer
i -> Integer -> Double
forall a. Num a => Integer -> a
fromInteger ((Integer
z Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
+ Integer
i) Integer -> Integer -> Integer
`choose` Integer
i) Double -> Double -> Double
forall a. Num a => a -> a -> a
* Double
pDouble -> Integer -> Double
forall a b. (Num a, Integral b) => a -> b -> a
^Integer
w Double -> Double -> Double
forall a. Num a => a -> a -> a
* Double
qDouble -> Integer -> Double
forall a b. (Num a, Integral b) => a -> b -> a
^Integer
i) [Integer
0..Integer
z]
  where p :: Double
p  = Player 1 -> Player 1 -> Double
predict Player 1
p1 Player 1
p2
        q :: Double
q  = Double
1 Double -> Double -> Double
forall a. Num a => a -> a -> a
- Double
p
        w :: Integer
w  = (Integer
n' Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
+ Integer
1) Integer -> Integer -> Integer
forall a. Integral a => a -> a -> a
`div` Integer
2
        z :: Integer
z  = Integer
w Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
- Integer
1
        n' :: Integer
n' = BoX -> Integer
fromBoX BoX
n

choose :: Integer -> Integer -> Integer
Integer
n choose :: Integer -> Integer -> Integer
`choose` Integer
k
  | Integer
k Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
> Integer
n     = Integer
0
  | Integer
k' Integer -> Integer -> Bool
forall a. Eq a => a -> a -> Bool
== Integer
0   = Integer
1
  | Bool
otherwise = Integer
p1 Integer -> Integer -> Integer
forall a. Integral a => a -> a -> a
`div` Integer
p2
  where k' :: Integer
k' = Integer -> Integer -> Integer
forall a. Ord a => a -> a -> a
min Integer
k (Integer
n Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
- Integer
k)
        p1 :: Integer
p1 = [Integer] -> Integer
forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
product ([Integer] -> Integer)
-> ([Integer] -> [Integer]) -> [Integer] -> Integer
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Integer -> Integer) -> [Integer] -> [Integer]
forall a b. (a -> b) -> [a] -> [b]
map (\Integer
i -> Integer
n Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
- Integer
i) ([Integer] -> Integer) -> [Integer] -> Integer
forall a b. (a -> b) -> a -> b
$ [Integer
0..Integer
k' Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
- Integer
1]
        p2 :: Integer
p2 = [Integer] -> Integer
forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
product [Integer
1..Integer
k']