/trueskill/mathematics.py
Python | 260 lines | 205 code | 37 blank | 18 comment | 65 complexity | 973473af264bc6e78431664c8c156329 MD5 | raw file
Possible License(s): BSD-3-Clause
- # -*- coding: utf-8 -*-
- """
- trueskill.mathematics
- ~~~~~~~~~~~~~~~~~~~~~
- This module contains basic mathematics functions and objects for TrueSkill
- algorithm. If you have not scipy, this module provides the fallback.
- :copyright: (c) 2012-2016 by Heungsub Lee.
- :license: BSD, see LICENSE for more details.
- """
- from __future__ import absolute_import
- import copy
- import math
- try:
- from numbers import Number
- except ImportError:
- Number = (int, long, float, complex)
- from six import iterkeys
- __all__ = ['Gaussian', 'Matrix', 'inf']
- inf = float('inf')
- class Gaussian(object):
- """A model for the normal distribution."""
- #: Precision, the inverse of the variance.
- pi = 0
- #: Precision adjusted mean, the precision multiplied by the mean.
- tau = 0
- def __init__(self, mu=None, sigma=None, pi=0, tau=0):
- if mu is not None:
- if sigma is None:
- raise TypeError('sigma argument is needed')
- elif sigma == 0:
- raise ValueError('sigma**2 should be greater than 0')
- pi = sigma ** -2
- tau = pi * mu
- self.pi = pi
- self.tau = tau
- @property
- def mu(self):
- """A property which returns the mean."""
- return self.pi and self.tau / self.pi
- @property
- def sigma(self):
- """A property which returns the the square root of the variance."""
- return math.sqrt(1 / self.pi) if self.pi else inf
- def __mul__(self, other):
- pi, tau = self.pi + other.pi, self.tau + other.tau
- return Gaussian(pi=pi, tau=tau)
- def __truediv__(self, other):
- pi, tau = self.pi - other.pi, self.tau - other.tau
- return Gaussian(pi=pi, tau=tau)
- __div__ = __truediv__ # for Python 2
- def __eq__(self, other):
- return self.pi == other.pi and self.tau == other.tau
- def __lt__(self, other):
- return self.mu < other.mu
- def __le__(self, other):
- return self.mu <= other.mu
- def __gt__(self, other):
- return self.mu > other.mu
- def __ge__(self, other):
- return self.mu >= other.mu
- def __repr__(self):
- return 'N(mu={:.3f}, sigma={:.3f})'.format(self.mu, self.sigma)
- def _repr_latex_(self):
- latex = r'\mathcal{{ N }}( {:.3f}, {:.3f}^2 )'.format(self.mu, self.sigma)
- return '$%s$' % latex
- class Matrix(list):
- """A model for matrix."""
- def __init__(self, src, height=None, width=None):
- if callable(src):
- f, src = src, {}
- size = [height, width]
- if not height:
- def set_height(height):
- size[0] = height
- size[0] = set_height
- if not width:
- def set_width(width):
- size[1] = width
- size[1] = set_width
- try:
- for (r, c), val in f(*size):
- src[r, c] = val
- except TypeError:
- raise TypeError('A callable src must return an interable '
- 'which generates a tuple containing '
- 'coordinate and value')
- height, width = tuple(size)
- if height is None or width is None:
- raise TypeError('A callable src must call set_height and '
- 'set_width if the size is non-deterministic')
- if isinstance(src, list):
- is_number = lambda x: isinstance(x, Number)
- unique_col_sizes = set(map(len, src))
- everything_are_number = filter(is_number, sum(src, []))
- if len(unique_col_sizes) != 1 or not everything_are_number:
- raise ValueError('src must be a rectangular array of numbers')
- two_dimensional_array = src
- elif isinstance(src, dict):
- if not height or not width:
- w = h = 0
- for r, c in iterkeys(src):
- if not height:
- h = max(h, r + 1)
- if not width:
- w = max(w, c + 1)
- if not height:
- height = h
- if not width:
- width = w
- two_dimensional_array = []
- for r in range(height):
- row = []
- two_dimensional_array.append(row)
- for c in range(width):
- row.append(src.get((r, c), 0))
- else:
- raise TypeError('src must be a list or dict or callable')
- super(Matrix, self).__init__(two_dimensional_array)
- @property
- def height(self):
- return len(self)
- @property
- def width(self):
- return len(self[0])
- def transpose(self):
- height, width = self.height, self.width
- src = {}
- for c in range(width):
- for r in range(height):
- src[c, r] = self[r][c]
- return type(self)(src, height=width, width=height)
- def minor(self, row_n, col_n):
- height, width = self.height, self.width
- if not (0 <= row_n < height):
- raise ValueError('row_n should be between 0 and %d' % height)
- elif not (0 <= col_n < width):
- raise ValueError('col_n should be between 0 and %d' % width)
- two_dimensional_array = []
- for r in range(height):
- if r == row_n:
- continue
- row = []
- two_dimensional_array.append(row)
- for c in range(width):
- if c == col_n:
- continue
- row.append(self[r][c])
- return type(self)(two_dimensional_array)
- def determinant(self):
- height, width = self.height, self.width
- if height != width:
- raise ValueError('Only square matrix can calculate a determinant')
- tmp, rv = copy.deepcopy(self), 1.
- for c in range(width - 1, 0, -1):
- pivot, r = max((abs(tmp[r][c]), r) for r in range(c + 1))
- pivot = tmp[r][c]
- if not pivot:
- return 0.
- tmp[r], tmp[c] = tmp[c], tmp[r]
- if r != c:
- rv = -rv
- rv *= pivot
- fact = -1. / pivot
- for r in range(c):
- f = fact * tmp[r][c]
- for x in range(c):
- tmp[r][x] += f * tmp[c][x]
- return rv * tmp[0][0]
- def adjugate(self):
- height, width = self.height, self.width
- if height != width:
- raise ValueError('Only square matrix can be adjugated')
- if height == 2:
- a, b = self[0][0], self[0][1]
- c, d = self[1][0], self[1][1]
- return type(self)([[d, -b], [-c, a]])
- src = {}
- for r in range(height):
- for c in range(width):
- sign = -1 if (r + c) % 2 else 1
- src[r, c] = self.minor(r, c).determinant() * sign
- return type(self)(src, height, width)
- def inverse(self):
- if self.height == self.width == 1:
- return type(self)([[1. / self[0][0]]])
- return (1. / self.determinant()) * self.adjugate()
- def __add__(self, other):
- height, width = self.height, self.width
- if (height, width) != (other.height, other.width):
- raise ValueError('Must be same size')
- src = {}
- for r in range(height):
- for c in range(width):
- src[r, c] = self[r][c] + other[r][c]
- return type(self)(src, height, width)
- def __mul__(self, other):
- if self.width != other.height:
- raise ValueError('Bad size')
- height, width = self.height, other.width
- src = {}
- for r in range(height):
- for c in range(width):
- src[r, c] = sum(self[r][x] * other[x][c]
- for x in range(self.width))
- return type(self)(src, height, width)
- def __rmul__(self, other):
- if not isinstance(other, Number):
- raise TypeError('The operand should be a number')
- height, width = self.height, self.width
- src = {}
- for r in range(height):
- for c in range(width):
- src[r, c] = other * self[r][c]
- return type(self)(src, height, width)
- def __repr__(self):
- return '{}({})'.format(type(self).__name__, super(Matrix, self).__repr__())
- def _repr_latex_(self):
- rows = [' && '.join(['%.3f' % cell for cell in row]) for row in self]
- latex = r'\begin{matrix} %s \end{matrix}' % r'\\'.join(rows)
- return '$%s$' % latex