defmodule Chi2fit.Distribution.Wishart do
# Copyright 2019 Pieter Rijken
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
@moduledoc """
Wishart distribution.
"""
defstruct [:pars, :dim, name: "wishart"]
@type t() :: %__MODULE__{
pars: [number()] | nil,
dim: [integer()],
name: String.t
}
end
defimpl Chi2fit.Distribution, for: Chi2fit.Distribution.Wishart do
alias Chi2fit.Distribution, as: D
alias Chi2fit.Matrix, as: M
import D.Wishart
alias D.Wishart
import Exboost.Math, only: [gamma_p: 2, tgamma: 1]
defp makerow([_],_,_pars,row), do: Enum.reverse(row)
defp makerow([{_,_,_,rprev},{i,p,g,r}|pgrrest],[{_,q}|qrest],{a,pi,gi},row) do
a = a - pi*rprev + 2*q/gi/g
makerow([{i,p,g,r}|pgrrest],qrest,{a,pi,gi},[a|row])
end
defp makemat(pgrlist, qlist), do: makemat(pgrlist,qlist,[])
defp makemat([], _, mat), do: Enum.reverse(mat)
defp makemat(pgrlist, qlist, mat) do
[{i,p,g,_}|pgrrest] = pgrlist
makemat(pgrrest,tl(qlist),[makerow(pgrlist,qlist,{0,p,g},List.duplicate(0,i))|mat])
end
defp gamma(m,x) do
:math.pow(:math.pi(),m*(m-1)/4)*Enum.reduce(1..m, 1.0, fn i,acc -> acc*tgamma(x-(i-1)/2) end)
end
@spec wishartCDF(number,number,number,number) :: (number -> number)
defp wishartCDF(mu,scale,m,n) when is_number(mu) and is_number(scale) and scale>0 do
nmin = min(m,n)
nmax = max(m,n)
alpha = (nmax - nmin - 1)/2
k = :math.pow(:math.pi(),nmin*nmin/2)/:math.pow(2,nmin*nmax/2)/gamma(nmin,nmax/2)/gamma(nmin,nmin/2)
nmat = if rem(nmin,2) == 0, do: nmin, else: nmin + 1
kprime = k*:math.pow(2,alpha*nmat + nmat*(nmat+1)/2)*Enum.reduce(1..nmat, 1, fn i,acc -> acc*tgamma(alpha+i) end)
fn
x when x == mu -> 0.0
x when x < mu -> 0.0
x when x > mu ->
xprime = (x-mu)/scale
pgrlist = 1..nmin |> Enum.map(& {&1, gamma_p(alpha+&1, xprime/2), tgamma(alpha+&1), :math.exp(-xprime/2)*:math.pow(xprime/2,alpha+&1)/tgamma(alpha+1+&1)})
qlist = 2..2*nmin-1 |> Enum.map(& {&1, :math.pow(2, -2*alpha-&1)*tgamma(2*alpha+&1)*gamma_p(2*alpha+&1,xprime)})
mata = makemat(pgrlist,qlist)
matt = M.transpose(mata)
M.subtract(mata, matt) |> M.det |> :math.sqrt |> then(& kprime*&1)
end
end
defp wishartCDF(_mu,_scale,_m,_n), do: raise ArithmeticError, "Wishart is only defined for positive scale"
def skewness(%Wishart{}), do: raise ArithmeticError, "Skewness not supported for Wishart distribution"
def kurtosis(%Wishart{}), do: raise ArithmeticError, "Kurtosis not supported for Wishart distribution"
def pdf(%Wishart{pars: nil}), do: raise ArithmeticError, "pdf not supported for Wishart distribution"
def random(%Wishart{pars: nil}), do: raise ArithmeticError, "random not supported for Wishart distribution"
def size(%Wishart{pars: nil}), do: 2
def cdf(%Wishart{pars: nil, dim: [m,n]}), do: fn x,[mu,scale] -> wishartCDF(mu,scale,m,n).(x) end
def name(model), do: model.name
end
defimpl Inspect, for: Chi2fit.Distribution.Wishart do
def inspect(dict, opts) do
import Inspect.Algebra
[m,n] = dict.dim
case dict.pars do
nil ->
"#Wishart<#{m},#{n}|[]>"
list ->
concat ["#Wishart<#{m},#{n}|", to_doc(list, opts), ">"]
end
end
end