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 +2/−2
- src/Ranking/Glicko.hs +6/−0
- src/Ranking/Glicko/Core.hs +59/−10
- src/Ranking/Glicko/Inference.hs +46/−4
- src/Ranking/Glicko/Types.hs +54/−10
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