PageRenderTime 27ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/trueskill/mathematics.py

http://github.com/sublee/trueskill
Python | 260 lines | 205 code | 37 blank | 18 comment | 65 complexity | 973473af264bc6e78431664c8c156329 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. # -*- coding: utf-8 -*-
  2. """
  3. trueskill.mathematics
  4. ~~~~~~~~~~~~~~~~~~~~~
  5. This module contains basic mathematics functions and objects for TrueSkill
  6. algorithm. If you have not scipy, this module provides the fallback.
  7. :copyright: (c) 2012-2016 by Heungsub Lee.
  8. :license: BSD, see LICENSE for more details.
  9. """
  10. from __future__ import absolute_import
  11. import copy
  12. import math
  13. try:
  14. from numbers import Number
  15. except ImportError:
  16. Number = (int, long, float, complex)
  17. from six import iterkeys
  18. __all__ = ['Gaussian', 'Matrix', 'inf']
  19. inf = float('inf')
  20. class Gaussian(object):
  21. """A model for the normal distribution."""
  22. #: Precision, the inverse of the variance.
  23. pi = 0
  24. #: Precision adjusted mean, the precision multiplied by the mean.
  25. tau = 0
  26. def __init__(self, mu=None, sigma=None, pi=0, tau=0):
  27. if mu is not None:
  28. if sigma is None:
  29. raise TypeError('sigma argument is needed')
  30. elif sigma == 0:
  31. raise ValueError('sigma**2 should be greater than 0')
  32. pi = sigma ** -2
  33. tau = pi * mu
  34. self.pi = pi
  35. self.tau = tau
  36. @property
  37. def mu(self):
  38. """A property which returns the mean."""
  39. return self.pi and self.tau / self.pi
  40. @property
  41. def sigma(self):
  42. """A property which returns the the square root of the variance."""
  43. return math.sqrt(1 / self.pi) if self.pi else inf
  44. def __mul__(self, other):
  45. pi, tau = self.pi + other.pi, self.tau + other.tau
  46. return Gaussian(pi=pi, tau=tau)
  47. def __truediv__(self, other):
  48. pi, tau = self.pi - other.pi, self.tau - other.tau
  49. return Gaussian(pi=pi, tau=tau)
  50. __div__ = __truediv__ # for Python 2
  51. def __eq__(self, other):
  52. return self.pi == other.pi and self.tau == other.tau
  53. def __lt__(self, other):
  54. return self.mu < other.mu
  55. def __le__(self, other):
  56. return self.mu <= other.mu
  57. def __gt__(self, other):
  58. return self.mu > other.mu
  59. def __ge__(self, other):
  60. return self.mu >= other.mu
  61. def __repr__(self):
  62. return 'N(mu={:.3f}, sigma={:.3f})'.format(self.mu, self.sigma)
  63. def _repr_latex_(self):
  64. latex = r'\mathcal{{ N }}( {:.3f}, {:.3f}^2 )'.format(self.mu, self.sigma)
  65. return '$%s$' % latex
  66. class Matrix(list):
  67. """A model for matrix."""
  68. def __init__(self, src, height=None, width=None):
  69. if callable(src):
  70. f, src = src, {}
  71. size = [height, width]
  72. if not height:
  73. def set_height(height):
  74. size[0] = height
  75. size[0] = set_height
  76. if not width:
  77. def set_width(width):
  78. size[1] = width
  79. size[1] = set_width
  80. try:
  81. for (r, c), val in f(*size):
  82. src[r, c] = val
  83. except TypeError:
  84. raise TypeError('A callable src must return an interable '
  85. 'which generates a tuple containing '
  86. 'coordinate and value')
  87. height, width = tuple(size)
  88. if height is None or width is None:
  89. raise TypeError('A callable src must call set_height and '
  90. 'set_width if the size is non-deterministic')
  91. if isinstance(src, list):
  92. is_number = lambda x: isinstance(x, Number)
  93. unique_col_sizes = set(map(len, src))
  94. everything_are_number = filter(is_number, sum(src, []))
  95. if len(unique_col_sizes) != 1 or not everything_are_number:
  96. raise ValueError('src must be a rectangular array of numbers')
  97. two_dimensional_array = src
  98. elif isinstance(src, dict):
  99. if not height or not width:
  100. w = h = 0
  101. for r, c in iterkeys(src):
  102. if not height:
  103. h = max(h, r + 1)
  104. if not width:
  105. w = max(w, c + 1)
  106. if not height:
  107. height = h
  108. if not width:
  109. width = w
  110. two_dimensional_array = []
  111. for r in range(height):
  112. row = []
  113. two_dimensional_array.append(row)
  114. for c in range(width):
  115. row.append(src.get((r, c), 0))
  116. else:
  117. raise TypeError('src must be a list or dict or callable')
  118. super(Matrix, self).__init__(two_dimensional_array)
  119. @property
  120. def height(self):
  121. return len(self)
  122. @property
  123. def width(self):
  124. return len(self[0])
  125. def transpose(self):
  126. height, width = self.height, self.width
  127. src = {}
  128. for c in range(width):
  129. for r in range(height):
  130. src[c, r] = self[r][c]
  131. return type(self)(src, height=width, width=height)
  132. def minor(self, row_n, col_n):
  133. height, width = self.height, self.width
  134. if not (0 <= row_n < height):
  135. raise ValueError('row_n should be between 0 and %d' % height)
  136. elif not (0 <= col_n < width):
  137. raise ValueError('col_n should be between 0 and %d' % width)
  138. two_dimensional_array = []
  139. for r in range(height):
  140. if r == row_n:
  141. continue
  142. row = []
  143. two_dimensional_array.append(row)
  144. for c in range(width):
  145. if c == col_n:
  146. continue
  147. row.append(self[r][c])
  148. return type(self)(two_dimensional_array)
  149. def determinant(self):
  150. height, width = self.height, self.width
  151. if height != width:
  152. raise ValueError('Only square matrix can calculate a determinant')
  153. tmp, rv = copy.deepcopy(self), 1.
  154. for c in range(width - 1, 0, -1):
  155. pivot, r = max((abs(tmp[r][c]), r) for r in range(c + 1))
  156. pivot = tmp[r][c]
  157. if not pivot:
  158. return 0.
  159. tmp[r], tmp[c] = tmp[c], tmp[r]
  160. if r != c:
  161. rv = -rv
  162. rv *= pivot
  163. fact = -1. / pivot
  164. for r in range(c):
  165. f = fact * tmp[r][c]
  166. for x in range(c):
  167. tmp[r][x] += f * tmp[c][x]
  168. return rv * tmp[0][0]
  169. def adjugate(self):
  170. height, width = self.height, self.width
  171. if height != width:
  172. raise ValueError('Only square matrix can be adjugated')
  173. if height == 2:
  174. a, b = self[0][0], self[0][1]
  175. c, d = self[1][0], self[1][1]
  176. return type(self)([[d, -b], [-c, a]])
  177. src = {}
  178. for r in range(height):
  179. for c in range(width):
  180. sign = -1 if (r + c) % 2 else 1
  181. src[r, c] = self.minor(r, c).determinant() * sign
  182. return type(self)(src, height, width)
  183. def inverse(self):
  184. if self.height == self.width == 1:
  185. return type(self)([[1. / self[0][0]]])
  186. return (1. / self.determinant()) * self.adjugate()
  187. def __add__(self, other):
  188. height, width = self.height, self.width
  189. if (height, width) != (other.height, other.width):
  190. raise ValueError('Must be same size')
  191. src = {}
  192. for r in range(height):
  193. for c in range(width):
  194. src[r, c] = self[r][c] + other[r][c]
  195. return type(self)(src, height, width)
  196. def __mul__(self, other):
  197. if self.width != other.height:
  198. raise ValueError('Bad size')
  199. height, width = self.height, other.width
  200. src = {}
  201. for r in range(height):
  202. for c in range(width):
  203. src[r, c] = sum(self[r][x] * other[x][c]
  204. for x in range(self.width))
  205. return type(self)(src, height, width)
  206. def __rmul__(self, other):
  207. if not isinstance(other, Number):
  208. raise TypeError('The operand should be a number')
  209. height, width = self.height, self.width
  210. src = {}
  211. for r in range(height):
  212. for c in range(width):
  213. src[r, c] = other * self[r][c]
  214. return type(self)(src, height, width)
  215. def __repr__(self):
  216. return '{}({})'.format(type(self).__name__, super(Matrix, self).__repr__())
  217. def _repr_latex_(self):
  218. rows = [' && '.join(['%.3f' % cell for cell in row]) for row in self]
  219. latex = r'\begin{matrix} %s \end{matrix}' % r'\\'.join(rows)
  220. return '$%s$' % latex