packages feed

glicko 0.1.0.0 → 0.1.1.0

raw patch · 5 files changed

+167/−26 lines, 5 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- Ranking.Glicko.Core: glicko2Multiplier :: Double
- Ranking.Glicko.Core: computeP :: Integer -> [Player] -> [Match] -> GlickoSettings -> [Player]
+ Ranking.Glicko.Core: computeP :: Int -> [Player] -> [Match] -> GlickoSettings -> [Player]

Files

glicko.cabal view
@@ -2,8 +2,8 @@ -- documentation, see http://haskell.org/cabal/users-guide/  name:                glicko-version:             0.1.0.0-synopsis:            Haskell implementation of Glicko-2+version:             0.1.1.0+synopsis:            Glicko-2 implementation in Haskell. description:     Implementation of the rating algorithm Glicko-2 by Professor Mark E. Glickman     <http://glicko.net/glicko/glicko2.pdf>
src/Ranking/Glicko.hs view
@@ -1,3 +1,9 @@+{-|+Module      : Ranking.Glicko+License     : GPL-3+Maintainer  : prillan91@gmail.com+Stability   : experimental+-} module Ranking.Glicko   ( module Ranking.Glicko.Core   , module Ranking.Glicko.Inference
src/Ranking/Glicko/Core.hs view
@@ -1,9 +1,51 @@+{-|+Module      : Ranking.Glicko.Core+License     : GPL-3+Maintainer  : prillan91@gmail.com+Stability   : experimental++This module contains the main function, 'compute'. Use this to compute new ratings from+old ones.++>>> let ps = compute [] [Match 1 2 1 0] def+>>> ps+[ Player { _pid = 1+         , _rating = 1662.3108939062977+         , _dev = 290.31896371798047+         , _vol = 5.999967537233814e-2+         , _inactivity = 0+         , _age = 1 }+, Player { _pid = 2+         , _rating = 1337.6891060937023+         , _dev = 290.31896371798047+         , _vol = 5.999967537233814e-2+         , _inactivity = 0+         , _age = 1 }]+>>> compute ps [Match 1 3 0 0] def+[ Player { _pid = 1+         , _rating = 1623.996484575735+         , _dev = 256.3451684359266+         , _vol = 5.999869083062934e-2+         , _inactivity = 0+         , _age = 2 }+, Player { _pid = 2+         , _rating = 1337.6891060937023+         , _dev = 290.5060065906196+         , _vol = 5.999967537233814e-2+         , _inactivity = 1+         , _age = 2 }+, Player { _pid = 3+         , _rating = 1557.6214863132009+         , _dev = 286.9272058793522+         , _vol = 5.999899836136578e-2+         , _inactivity = 0+         , _age = 1 }]+-} module Ranking.Glicko.Core        ( compute        , computeP        , newToOld-       , oldToNew-       , glicko2Multiplier) where+       , oldToNew ) where  import Prelude hiding ((^)) import qualified Prelude as P@@ -22,15 +64,22 @@ (^) = (P.^)  -- Run map in parallel-pMap :: NFData b => Integer -> (a -> b) -> [a] -> [b]-pMap chunkSize f = withStrategy (parListChunk 100 rdeepseq) . map f--computeP :: Integer -> [Player] -> [Match] -> GlickoSettings -> [Player]-computeP chunkSize = compute' (pMap chunkSize)+pMap :: NFData b => Int -> (a -> b) -> [a] -> [b]+pMap chunkSize f = withStrategy (parListChunk chunkSize rdeepseq) . map f -compute :: [Player] -> [Match] -> GlickoSettings -> [Player]+-- | Computes new ratings from the previous and adds new ones using the+-- specified settings.+compute :: [Player]       -- ^ Input players+        -> [Match]        -- ^ Matches played this period+        -> GlickoSettings -- ^ Settings for computing the score values and adding new+                          -- players.+        -> [Player]       -- ^ Updated player ratings compute = compute' map +-- | Same as 'compute' but runs in parallel using the specified chunkSize+computeP :: Int -> [Player] -> [Match] -> GlickoSettings -> [Player]+computeP chunkSize = compute' (pMap chunkSize)+ -- Update all player ratings compute' :: (((PlayerId, Player) -> Player) -> [(PlayerId, Player)] -> [Player])          -> [Player]@@ -149,12 +198,12 @@       <*> pure (_scb m)   ) --- Convert ratings from Glicko to Glicko-2+-- | Convert ratings from Glicko to Glicko-2 oldToNew :: Player -> Player oldToNew p@Player{ _rating = r, _dev = d} = p { _rating = (r - 1500) / glicko2Multiplier                                               , _dev    = d / glicko2Multiplier } --- Convert ratings from Glicko-2 to Glicko+-- | Convert ratings from Glicko-2 to Glicko newToOld :: Player -> Player newToOld p@Player{ _rating = r, _dev = d} = p { _rating = r*glicko2Multiplier + 1500                                               , _dev    = d*glicko2Multiplier}
src/Ranking/Glicko/Inference.hs view
@@ -1,8 +1,42 @@+{-|+Module      : Ranking.Glicko.Inference+License     : GPL-3+Maintainer  : prillan91@gmail.com+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 { _pid = 1+       , _rating = 1464.0506705393013+       , _dev = 151.51652412385727+       , _vol = 5.9995984286488495e-2+       , _inactivity = 0+       , _age = 1 }+>>> p2+Player { _pid = 2+       , _rating = 1398.1435582337338+       , _dev = 31.67021528115062+       , _vol = 5.999912372888531e-2+       , _inactivity = 0+       , _age = 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-                                , boX-                                , fromBoX                                 , predictBoX-                                , BoX) where+                                , BoX+                                , boX+                                , fromBoX) where  import Ranking.Glicko.Core import Ranking.Glicko.Types@@ -11,25 +45,33 @@ import Statistics.Distribution import Statistics.Distribution.Normal -predict :: Player -> Player -> Double+-- | Computes the probability that Player A wins against Player B+predict :: Player -- ^ Player A+        -> Player -- ^ Player B+        -> Double predict pla plb = cumulative dist (ra - rb)   where Player { _rating = ra, _dev = da } = oldToNew pla         Player { _rating = rb, _dev = db } = oldToNew plb         dist = normalDistr 0 (1 + da + db) -- TODO: Check the above ^ +-- | Represents a match played as best-of-X games. newtype BoX = BoX Integer   deriving Show +-- | Create a best-of-X match boX :: Integer -> Maybe BoX boX n = if odd n && 0 < n && n <= 11            then Just $ BoX n            else Nothing +-- | Destruct a best-of-X match fromBoX :: BoX -> Integer fromBoX = 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 -> Player -> Double predictBoX n p1 p2 =   sum $ map (\i -> fromInteger ((z + i) `choose` i) * p^w * q^i) [0..z]
src/Ranking/Glicko/Types.hs view
@@ -1,13 +1,25 @@+{-|+Module      : Ranking.Glicko.Types+License     : GPL-3+Maintainer  : prillan91@gmail.com+Stability   : experimental++For examples, see `Ranking.Glicko.Core` and `Ranking.Glicko.Inference`.+-} {-# LANGUAGE TemplateHaskell #-} module Ranking.Glicko.Types-       ( Player(..)-       , pid, rating, dev, vol, inactivity, age+       ( -- * Data types+         Player(..)        , Match(..)-       , pla, plb, sca, scb        , PlayerId        , Score        , ScoreFunction(..)-       , GlickoSettings(..))+       , GlickoSettings(..)+       -- * Lenses+       -- ** Player+       , pid, rating, dev, vol, inactivity, age+       -- ** Match+       , pla, plb, sca, scb )        where  import           Control.DeepSeq@@ -15,12 +27,21 @@ import           Data.Default  type PlayerId = Int-data Player = Player { _pid        :: PlayerId-                     , _rating     :: Double-                     , _dev        :: Double-                     , _vol        :: Double-                     , _inactivity :: Int-                     , _age        :: Int}+-- | Data type representing a player's Glicko rating.+--+-- (NOTE: The system assumes Glicko ratings, to convert to Glicko-2+-- , use 'Ranking.Glicko.Core.oldToNew')+data Player = Player { _pid        :: PlayerId -- ^ Player id, can be anything+                     , _rating     :: Double   -- ^ Rating+                     , _dev        :: Double   -- ^ Deviation+                     , _vol        :: Double   -- ^ Volatility+                     , _inactivity :: Int      -- ^ Inactivity (not part of Glicko-2),+                                               -- keeps track of the number of rating+                                               -- updates a player has been inactive.+                     , _age        :: Int      -- ^ Age (not part of Glicko-2),+                                               -- keeps track of the number of rating+                                               -- updates since the player was added.+                     }   deriving (Show, Eq) makeLenses ''Player @@ -35,6 +56,23 @@   deriving (Show, Eq) makeLenses ''Match +-- | 'ScoreFunction's are used in 'compute' to evaluate two players performances against+-- eachother. It should obey the following laws,+--+--+-- prop>  0 <= compareScores x y+-- prop>  1 >= compareScores x y+-- prop>  compareScores x y == 1 - compareScores y x+--+--+-- The default implementation is+--+-- @+-- \\s1 s2 -> case s1 \`compare\` s2 of+--             LT -> 0+--             EQ -> 0.5+--             GT -> 1+-- @ newtype ScoreFunction = ScoreFunction { compareScores :: Score -> Score -> Double } instance Default ScoreFunction where   def = ScoreFunction $ \s1 s2 -> case s1 `compare` s2 of@@ -44,6 +82,12 @@ instance Show ScoreFunction where   show _ = "{score function}" +-- | Provides the 'Ranking.Glicko.Core.compute' function with parameters.+-- See <http://glicko.net/glicko/glicko2.pdf> for an explanation.+--+-- (NOTE: 'scoreFunction' is not a part of Glicko-2)+--+-- The default settings are as defined in the above paper. data GlickoSettings = GlickoSettings   { initialRating     :: Double   , initialDeviation  :: Double