matrix_distributions.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610
  1. from math import prod
  2. from sympy.core.basic import Basic
  3. from sympy.core.numbers import pi
  4. from sympy.core.singleton import S
  5. from sympy.functions.elementary.exponential import exp
  6. from sympy.functions.special.gamma_functions import multigamma
  7. from sympy.core.sympify import sympify, _sympify
  8. from sympy.matrices import (ImmutableMatrix, Inverse, Trace, Determinant,
  9. MatrixSymbol, MatrixBase, Transpose, MatrixSet,
  10. matrix2numpy)
  11. from sympy.stats.rv import (_value_check, RandomMatrixSymbol, NamedArgsMixin, PSpace,
  12. _symbol_converter, MatrixDomain, Distribution)
  13. from sympy.external import import_module
  14. ################################################################################
  15. #------------------------Matrix Probability Space------------------------------#
  16. ################################################################################
  17. class MatrixPSpace(PSpace):
  18. """
  19. Represents probability space for
  20. Matrix Distributions.
  21. """
  22. def __new__(cls, sym, distribution, dim_n, dim_m):
  23. sym = _symbol_converter(sym)
  24. dim_n, dim_m = _sympify(dim_n), _sympify(dim_m)
  25. if not (dim_n.is_integer and dim_m.is_integer):
  26. raise ValueError("Dimensions should be integers")
  27. return Basic.__new__(cls, sym, distribution, dim_n, dim_m)
  28. distribution = property(lambda self: self.args[1])
  29. symbol = property(lambda self: self.args[0])
  30. @property
  31. def domain(self):
  32. return MatrixDomain(self.symbol, self.distribution.set)
  33. @property
  34. def value(self):
  35. return RandomMatrixSymbol(self.symbol, self.args[2], self.args[3], self)
  36. @property
  37. def values(self):
  38. return {self.value}
  39. def compute_density(self, expr, *args):
  40. rms = expr.atoms(RandomMatrixSymbol)
  41. if len(rms) > 1 or (not isinstance(expr, RandomMatrixSymbol)):
  42. raise NotImplementedError("Currently, no algorithm has been "
  43. "implemented to handle general expressions containing "
  44. "multiple matrix distributions.")
  45. return self.distribution.pdf(expr)
  46. def sample(self, size=(), library='scipy', seed=None):
  47. """
  48. Internal sample method
  49. Returns dictionary mapping RandomMatrixSymbol to realization value.
  50. """
  51. return {self.value: self.distribution.sample(size, library=library, seed=seed)}
  52. def rv(symbol, cls, args):
  53. args = list(map(sympify, args))
  54. dist = cls(*args)
  55. dist.check(*args)
  56. dim = dist.dimension
  57. pspace = MatrixPSpace(symbol, dist, dim[0], dim[1])
  58. return pspace.value
  59. class SampleMatrixScipy:
  60. """Returns the sample from scipy of the given distribution"""
  61. def __new__(cls, dist, size, seed=None):
  62. return cls._sample_scipy(dist, size, seed)
  63. @classmethod
  64. def _sample_scipy(cls, dist, size, seed):
  65. """Sample from SciPy."""
  66. from scipy import stats as scipy_stats
  67. import numpy
  68. scipy_rv_map = {
  69. 'WishartDistribution': lambda dist, size, rand_state: scipy_stats.wishart.rvs(
  70. df=int(dist.n), scale=matrix2numpy(dist.scale_matrix, float), size=size),
  71. 'MatrixNormalDistribution': lambda dist, size, rand_state: scipy_stats.matrix_normal.rvs(
  72. mean=matrix2numpy(dist.location_matrix, float),
  73. rowcov=matrix2numpy(dist.scale_matrix_1, float),
  74. colcov=matrix2numpy(dist.scale_matrix_2, float), size=size, random_state=rand_state)
  75. }
  76. sample_shape = {
  77. 'WishartDistribution': lambda dist: dist.scale_matrix.shape,
  78. 'MatrixNormalDistribution' : lambda dist: dist.location_matrix.shape
  79. }
  80. dist_list = scipy_rv_map.keys()
  81. if dist.__class__.__name__ not in dist_list:
  82. return None
  83. if seed is None or isinstance(seed, int):
  84. rand_state = numpy.random.default_rng(seed=seed)
  85. else:
  86. rand_state = seed
  87. samp = scipy_rv_map[dist.__class__.__name__](dist, prod(size), rand_state)
  88. return samp.reshape(size + sample_shape[dist.__class__.__name__](dist))
  89. class SampleMatrixNumpy:
  90. """Returns the sample from numpy of the given distribution"""
  91. ### TODO: Add tests after adding matrix distributions in numpy_rv_map
  92. def __new__(cls, dist, size, seed=None):
  93. return cls._sample_numpy(dist, size, seed)
  94. @classmethod
  95. def _sample_numpy(cls, dist, size, seed):
  96. """Sample from NumPy."""
  97. numpy_rv_map = {
  98. }
  99. sample_shape = {
  100. }
  101. dist_list = numpy_rv_map.keys()
  102. if dist.__class__.__name__ not in dist_list:
  103. return None
  104. import numpy
  105. if seed is None or isinstance(seed, int):
  106. rand_state = numpy.random.default_rng(seed=seed)
  107. else:
  108. rand_state = seed
  109. samp = numpy_rv_map[dist.__class__.__name__](dist, prod(size), rand_state)
  110. return samp.reshape(size + sample_shape[dist.__class__.__name__](dist))
  111. class SampleMatrixPymc:
  112. """Returns the sample from pymc of the given distribution"""
  113. def __new__(cls, dist, size, seed=None):
  114. return cls._sample_pymc(dist, size, seed)
  115. @classmethod
  116. def _sample_pymc(cls, dist, size, seed):
  117. """Sample from PyMC."""
  118. try:
  119. import pymc
  120. except ImportError:
  121. import pymc3 as pymc
  122. pymc_rv_map = {
  123. 'MatrixNormalDistribution': lambda dist: pymc.MatrixNormal('X',
  124. mu=matrix2numpy(dist.location_matrix, float),
  125. rowcov=matrix2numpy(dist.scale_matrix_1, float),
  126. colcov=matrix2numpy(dist.scale_matrix_2, float),
  127. shape=dist.location_matrix.shape),
  128. 'WishartDistribution': lambda dist: pymc.WishartBartlett('X',
  129. nu=int(dist.n), S=matrix2numpy(dist.scale_matrix, float))
  130. }
  131. sample_shape = {
  132. 'WishartDistribution': lambda dist: dist.scale_matrix.shape,
  133. 'MatrixNormalDistribution' : lambda dist: dist.location_matrix.shape
  134. }
  135. dist_list = pymc_rv_map.keys()
  136. if dist.__class__.__name__ not in dist_list:
  137. return None
  138. import logging
  139. logging.getLogger("pymc").setLevel(logging.ERROR)
  140. with pymc.Model():
  141. pymc_rv_map[dist.__class__.__name__](dist)
  142. samps = pymc.sample(draws=prod(size), chains=1, progressbar=False, random_seed=seed, return_inferencedata=False, compute_convergence_checks=False)['X']
  143. return samps.reshape(size + sample_shape[dist.__class__.__name__](dist))
  144. _get_sample_class_matrixrv = {
  145. 'scipy': SampleMatrixScipy,
  146. 'pymc3': SampleMatrixPymc,
  147. 'pymc': SampleMatrixPymc,
  148. 'numpy': SampleMatrixNumpy
  149. }
  150. ################################################################################
  151. #-------------------------Matrix Distribution----------------------------------#
  152. ################################################################################
  153. class MatrixDistribution(Distribution, NamedArgsMixin):
  154. """
  155. Abstract class for Matrix Distribution.
  156. """
  157. def __new__(cls, *args):
  158. args = [ImmutableMatrix(arg) if isinstance(arg, list)
  159. else _sympify(arg) for arg in args]
  160. return Basic.__new__(cls, *args)
  161. @staticmethod
  162. def check(*args):
  163. pass
  164. def __call__(self, expr):
  165. if isinstance(expr, list):
  166. expr = ImmutableMatrix(expr)
  167. return self.pdf(expr)
  168. def sample(self, size=(), library='scipy', seed=None):
  169. """
  170. Internal sample method
  171. Returns dictionary mapping RandomSymbol to realization value.
  172. """
  173. libraries = ['scipy', 'numpy', 'pymc3', 'pymc']
  174. if library not in libraries:
  175. raise NotImplementedError("Sampling from %s is not supported yet."
  176. % str(library))
  177. if not import_module(library):
  178. raise ValueError("Failed to import %s" % library)
  179. samps = _get_sample_class_matrixrv[library](self, size, seed)
  180. if samps is not None:
  181. return samps
  182. raise NotImplementedError(
  183. "Sampling for %s is not currently implemented from %s"
  184. % (self.__class__.__name__, library)
  185. )
  186. ################################################################################
  187. #------------------------Matrix Distribution Types-----------------------------#
  188. ################################################################################
  189. #-------------------------------------------------------------------------------
  190. # Matrix Gamma distribution ----------------------------------------------------
  191. class MatrixGammaDistribution(MatrixDistribution):
  192. _argnames = ('alpha', 'beta', 'scale_matrix')
  193. @staticmethod
  194. def check(alpha, beta, scale_matrix):
  195. if not isinstance(scale_matrix, MatrixSymbol):
  196. _value_check(scale_matrix.is_positive_definite, "The shape "
  197. "matrix must be positive definite.")
  198. _value_check(scale_matrix.is_square, "Should "
  199. "be square matrix")
  200. _value_check(alpha.is_positive, "Shape parameter should be positive.")
  201. _value_check(beta.is_positive, "Scale parameter should be positive.")
  202. @property
  203. def set(self):
  204. k = self.scale_matrix.shape[0]
  205. return MatrixSet(k, k, S.Reals)
  206. @property
  207. def dimension(self):
  208. return self.scale_matrix.shape
  209. def pdf(self, x):
  210. alpha, beta, scale_matrix = self.alpha, self.beta, self.scale_matrix
  211. p = scale_matrix.shape[0]
  212. if isinstance(x, list):
  213. x = ImmutableMatrix(x)
  214. if not isinstance(x, (MatrixBase, MatrixSymbol)):
  215. raise ValueError("%s should be an isinstance of Matrix "
  216. "or MatrixSymbol" % str(x))
  217. sigma_inv_x = - Inverse(scale_matrix)*x / beta
  218. term1 = exp(Trace(sigma_inv_x))/((beta**(p*alpha)) * multigamma(alpha, p))
  219. term2 = (Determinant(scale_matrix))**(-alpha)
  220. term3 = (Determinant(x))**(alpha - S(p + 1)/2)
  221. return term1 * term2 * term3
  222. def MatrixGamma(symbol, alpha, beta, scale_matrix):
  223. """
  224. Creates a random variable with Matrix Gamma Distribution.
  225. The density of the said distribution can be found at [1].
  226. Parameters
  227. ==========
  228. alpha: Positive Real number
  229. Shape Parameter
  230. beta: Positive Real number
  231. Scale Parameter
  232. scale_matrix: Positive definite real square matrix
  233. Scale Matrix
  234. Returns
  235. =======
  236. RandomSymbol
  237. Examples
  238. ========
  239. >>> from sympy.stats import density, MatrixGamma
  240. >>> from sympy import MatrixSymbol, symbols
  241. >>> a, b = symbols('a b', positive=True)
  242. >>> M = MatrixGamma('M', a, b, [[2, 1], [1, 2]])
  243. >>> X = MatrixSymbol('X', 2, 2)
  244. >>> density(M)(X).doit()
  245. exp(Trace(Matrix([
  246. [-2/3, 1/3],
  247. [ 1/3, -2/3]])*X)/b)*Determinant(X)**(a - 3/2)/(3**a*sqrt(pi)*b**(2*a)*gamma(a)*gamma(a - 1/2))
  248. >>> density(M)([[1, 0], [0, 1]]).doit()
  249. exp(-4/(3*b))/(3**a*sqrt(pi)*b**(2*a)*gamma(a)*gamma(a - 1/2))
  250. References
  251. ==========
  252. .. [1] https://en.wikipedia.org/wiki/Matrix_gamma_distribution
  253. """
  254. if isinstance(scale_matrix, list):
  255. scale_matrix = ImmutableMatrix(scale_matrix)
  256. return rv(symbol, MatrixGammaDistribution, (alpha, beta, scale_matrix))
  257. #-------------------------------------------------------------------------------
  258. # Wishart Distribution ---------------------------------------------------------
  259. class WishartDistribution(MatrixDistribution):
  260. _argnames = ('n', 'scale_matrix')
  261. @staticmethod
  262. def check(n, scale_matrix):
  263. if not isinstance(scale_matrix, MatrixSymbol):
  264. _value_check(scale_matrix.is_positive_definite, "The shape "
  265. "matrix must be positive definite.")
  266. _value_check(scale_matrix.is_square, "Should "
  267. "be square matrix")
  268. _value_check(n.is_positive, "Shape parameter should be positive.")
  269. @property
  270. def set(self):
  271. k = self.scale_matrix.shape[0]
  272. return MatrixSet(k, k, S.Reals)
  273. @property
  274. def dimension(self):
  275. return self.scale_matrix.shape
  276. def pdf(self, x):
  277. n, scale_matrix = self.n, self.scale_matrix
  278. p = scale_matrix.shape[0]
  279. if isinstance(x, list):
  280. x = ImmutableMatrix(x)
  281. if not isinstance(x, (MatrixBase, MatrixSymbol)):
  282. raise ValueError("%s should be an isinstance of Matrix "
  283. "or MatrixSymbol" % str(x))
  284. sigma_inv_x = - Inverse(scale_matrix)*x / S(2)
  285. term1 = exp(Trace(sigma_inv_x))/((2**(p*n/S(2))) * multigamma(n/S(2), p))
  286. term2 = (Determinant(scale_matrix))**(-n/S(2))
  287. term3 = (Determinant(x))**(S(n - p - 1)/2)
  288. return term1 * term2 * term3
  289. def Wishart(symbol, n, scale_matrix):
  290. """
  291. Creates a random variable with Wishart Distribution.
  292. The density of the said distribution can be found at [1].
  293. Parameters
  294. ==========
  295. n: Positive Real number
  296. Represents degrees of freedom
  297. scale_matrix: Positive definite real square matrix
  298. Scale Matrix
  299. Returns
  300. =======
  301. RandomSymbol
  302. Examples
  303. ========
  304. >>> from sympy.stats import density, Wishart
  305. >>> from sympy import MatrixSymbol, symbols
  306. >>> n = symbols('n', positive=True)
  307. >>> W = Wishart('W', n, [[2, 1], [1, 2]])
  308. >>> X = MatrixSymbol('X', 2, 2)
  309. >>> density(W)(X).doit()
  310. exp(Trace(Matrix([
  311. [-1/3, 1/6],
  312. [ 1/6, -1/3]])*X))*Determinant(X)**(n/2 - 3/2)/(2**n*3**(n/2)*sqrt(pi)*gamma(n/2)*gamma(n/2 - 1/2))
  313. >>> density(W)([[1, 0], [0, 1]]).doit()
  314. exp(-2/3)/(2**n*3**(n/2)*sqrt(pi)*gamma(n/2)*gamma(n/2 - 1/2))
  315. References
  316. ==========
  317. .. [1] https://en.wikipedia.org/wiki/Wishart_distribution
  318. """
  319. if isinstance(scale_matrix, list):
  320. scale_matrix = ImmutableMatrix(scale_matrix)
  321. return rv(symbol, WishartDistribution, (n, scale_matrix))
  322. #-------------------------------------------------------------------------------
  323. # Matrix Normal distribution ---------------------------------------------------
  324. class MatrixNormalDistribution(MatrixDistribution):
  325. _argnames = ('location_matrix', 'scale_matrix_1', 'scale_matrix_2')
  326. @staticmethod
  327. def check(location_matrix, scale_matrix_1, scale_matrix_2):
  328. if not isinstance(scale_matrix_1, MatrixSymbol):
  329. _value_check(scale_matrix_1.is_positive_definite, "The shape "
  330. "matrix must be positive definite.")
  331. if not isinstance(scale_matrix_2, MatrixSymbol):
  332. _value_check(scale_matrix_2.is_positive_definite, "The shape "
  333. "matrix must be positive definite.")
  334. _value_check(scale_matrix_1.is_square, "Scale matrix 1 should be "
  335. "be square matrix")
  336. _value_check(scale_matrix_2.is_square, "Scale matrix 2 should be "
  337. "be square matrix")
  338. n = location_matrix.shape[0]
  339. p = location_matrix.shape[1]
  340. _value_check(scale_matrix_1.shape[0] == n, "Scale matrix 1 should be"
  341. " of shape %s x %s"% (str(n), str(n)))
  342. _value_check(scale_matrix_2.shape[0] == p, "Scale matrix 2 should be"
  343. " of shape %s x %s"% (str(p), str(p)))
  344. @property
  345. def set(self):
  346. n, p = self.location_matrix.shape
  347. return MatrixSet(n, p, S.Reals)
  348. @property
  349. def dimension(self):
  350. return self.location_matrix.shape
  351. def pdf(self, x):
  352. M, U, V = self.location_matrix, self.scale_matrix_1, self.scale_matrix_2
  353. n, p = M.shape
  354. if isinstance(x, list):
  355. x = ImmutableMatrix(x)
  356. if not isinstance(x, (MatrixBase, MatrixSymbol)):
  357. raise ValueError("%s should be an isinstance of Matrix "
  358. "or MatrixSymbol" % str(x))
  359. term1 = Inverse(V)*Transpose(x - M)*Inverse(U)*(x - M)
  360. num = exp(-Trace(term1)/S(2))
  361. den = (2*pi)**(S(n*p)/2) * Determinant(U)**(S(p)/2) * Determinant(V)**(S(n)/2)
  362. return num/den
  363. def MatrixNormal(symbol, location_matrix, scale_matrix_1, scale_matrix_2):
  364. """
  365. Creates a random variable with Matrix Normal Distribution.
  366. The density of the said distribution can be found at [1].
  367. Parameters
  368. ==========
  369. location_matrix: Real ``n x p`` matrix
  370. Represents degrees of freedom
  371. scale_matrix_1: Positive definite matrix
  372. Scale Matrix of shape ``n x n``
  373. scale_matrix_2: Positive definite matrix
  374. Scale Matrix of shape ``p x p``
  375. Returns
  376. =======
  377. RandomSymbol
  378. Examples
  379. ========
  380. >>> from sympy import MatrixSymbol
  381. >>> from sympy.stats import density, MatrixNormal
  382. >>> M = MatrixNormal('M', [[1, 2]], [1], [[1, 0], [0, 1]])
  383. >>> X = MatrixSymbol('X', 1, 2)
  384. >>> density(M)(X).doit()
  385. exp(-Trace((Matrix([
  386. [-1],
  387. [-2]]) + X.T)*(Matrix([[-1, -2]]) + X))/2)/(2*pi)
  388. >>> density(M)([[3, 4]]).doit()
  389. exp(-4)/(2*pi)
  390. References
  391. ==========
  392. .. [1] https://en.wikipedia.org/wiki/Matrix_normal_distribution
  393. """
  394. if isinstance(location_matrix, list):
  395. location_matrix = ImmutableMatrix(location_matrix)
  396. if isinstance(scale_matrix_1, list):
  397. scale_matrix_1 = ImmutableMatrix(scale_matrix_1)
  398. if isinstance(scale_matrix_2, list):
  399. scale_matrix_2 = ImmutableMatrix(scale_matrix_2)
  400. args = (location_matrix, scale_matrix_1, scale_matrix_2)
  401. return rv(symbol, MatrixNormalDistribution, args)
  402. #-------------------------------------------------------------------------------
  403. # Matrix Student's T distribution ---------------------------------------------------
  404. class MatrixStudentTDistribution(MatrixDistribution):
  405. _argnames = ('nu', 'location_matrix', 'scale_matrix_1', 'scale_matrix_2')
  406. @staticmethod
  407. def check(nu, location_matrix, scale_matrix_1, scale_matrix_2):
  408. if not isinstance(scale_matrix_1, MatrixSymbol):
  409. _value_check(scale_matrix_1.is_positive_definite != False, "The shape "
  410. "matrix must be positive definite.")
  411. if not isinstance(scale_matrix_2, MatrixSymbol):
  412. _value_check(scale_matrix_2.is_positive_definite != False, "The shape "
  413. "matrix must be positive definite.")
  414. _value_check(scale_matrix_1.is_square != False, "Scale matrix 1 should be "
  415. "be square matrix")
  416. _value_check(scale_matrix_2.is_square != False, "Scale matrix 2 should be "
  417. "be square matrix")
  418. n = location_matrix.shape[0]
  419. p = location_matrix.shape[1]
  420. _value_check(scale_matrix_1.shape[0] == p, "Scale matrix 1 should be"
  421. " of shape %s x %s" % (str(p), str(p)))
  422. _value_check(scale_matrix_2.shape[0] == n, "Scale matrix 2 should be"
  423. " of shape %s x %s" % (str(n), str(n)))
  424. _value_check(nu.is_positive != False, "Degrees of freedom must be positive")
  425. @property
  426. def set(self):
  427. n, p = self.location_matrix.shape
  428. return MatrixSet(n, p, S.Reals)
  429. @property
  430. def dimension(self):
  431. return self.location_matrix.shape
  432. def pdf(self, x):
  433. from sympy.matrices.dense import eye
  434. if isinstance(x, list):
  435. x = ImmutableMatrix(x)
  436. if not isinstance(x, (MatrixBase, MatrixSymbol)):
  437. raise ValueError("%s should be an isinstance of Matrix "
  438. "or MatrixSymbol" % str(x))
  439. nu, M, Omega, Sigma = self.nu, self.location_matrix, self.scale_matrix_1, self.scale_matrix_2
  440. n, p = M.shape
  441. K = multigamma((nu + n + p - 1)/2, p) * Determinant(Omega)**(-n/2) * Determinant(Sigma)**(-p/2) \
  442. / ((pi)**(n*p/2) * multigamma((nu + p - 1)/2, p))
  443. return K * (Determinant(eye(n) + Inverse(Sigma)*(x - M)*Inverse(Omega)*Transpose(x - M))) \
  444. **(-(nu + n + p -1)/2)
  445. def MatrixStudentT(symbol, nu, location_matrix, scale_matrix_1, scale_matrix_2):
  446. """
  447. Creates a random variable with Matrix Gamma Distribution.
  448. The density of the said distribution can be found at [1].
  449. Parameters
  450. ==========
  451. nu: Positive Real number
  452. degrees of freedom
  453. location_matrix: Positive definite real square matrix
  454. Location Matrix of shape ``n x p``
  455. scale_matrix_1: Positive definite real square matrix
  456. Scale Matrix of shape ``p x p``
  457. scale_matrix_2: Positive definite real square matrix
  458. Scale Matrix of shape ``n x n``
  459. Returns
  460. =======
  461. RandomSymbol
  462. Examples
  463. ========
  464. >>> from sympy import MatrixSymbol,symbols
  465. >>> from sympy.stats import density, MatrixStudentT
  466. >>> v = symbols('v',positive=True)
  467. >>> M = MatrixStudentT('M', v, [[1, 2]], [[1, 0], [0, 1]], [1])
  468. >>> X = MatrixSymbol('X', 1, 2)
  469. >>> density(M)(X)
  470. gamma(v/2 + 1)*Determinant((Matrix([[-1, -2]]) + X)*(Matrix([
  471. [-1],
  472. [-2]]) + X.T) + Matrix([[1]]))**(-v/2 - 1)/(pi**1.0*gamma(v/2)*Determinant(Matrix([[1]]))**1.0*Determinant(Matrix([
  473. [1, 0],
  474. [0, 1]]))**0.5)
  475. References
  476. ==========
  477. .. [1] https://en.wikipedia.org/wiki/Matrix_t-distribution
  478. """
  479. if isinstance(location_matrix, list):
  480. location_matrix = ImmutableMatrix(location_matrix)
  481. if isinstance(scale_matrix_1, list):
  482. scale_matrix_1 = ImmutableMatrix(scale_matrix_1)
  483. if isinstance(scale_matrix_2, list):
  484. scale_matrix_2 = ImmutableMatrix(scale_matrix_2)
  485. args = (nu, location_matrix, scale_matrix_1, scale_matrix_2)
  486. return rv(symbol, MatrixStudentTDistribution, args)