defmodule Chi2fit.Distribution.SEP 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 """
The Skew Exponential Power cumulative distribution (Azzalini).
## Options
`:method` - the integration method to use, :gauss and :romberg types are supported, see below
`:tolerance` - re-iterate until the tolerance is reached (only for :romberg)
`:points` - the number of points to use in :gauss method
## Integration methods
`:gauss` - n-point Gauss rule,
`:gauss2` - n-point Guass rule with tanh transformation,
`:gauss3` - n-point Gauss rule with linear transformstion,
`:romberg` - Romberg integration,
`:romberg2` - Romberg integration with tanh transformation,
`:romberg3` - Romberg integration with linear transformstion.
"""
defstruct [:pars, :offset, options: [], name: "sep"]
@type t() :: %__MODULE__{
pars: [number()] | nil,
offset: number() | nil,
options: Keyword.t,
name: String.t
}
end
defimpl Chi2fit.Distribution, for: Chi2fit.Distribution.SEP do
alias Chi2fit.Distribution, as: D
import D.SEP
alias D.SEP
import Chi2fit.Math, only: [integrate: 5]
@pi :math.pi()
@spec sepCDF(a :: float,b :: float,lambda :: float,alpha :: float, options :: Keyword.t) :: (number -> number)
defp sepCDF(a,b,lambda,alpha,options) do
method = options[:method] || :romberg2
endpoint = if method in [:gauss2,:gauss3,:romberg2,:romberg3], do: :infinity, else: 1000.0
fn
x ->
result2 = integrate(method, sepPDF(a,b,lambda,alpha), 0.0, x, options)
result3 = integrate(method, sepPDF(a,b,lambda,alpha), 0.0, endpoint, options)
result2/result3
end
end
@spec sepPDF(a::float,b::float,lambda::float,alpha::float) :: (number -> number)
defp sepPDF(a,b,lambda,alpha) do
fn x ->
z = (x-a)/b
t = :math.pow(abs(z),alpha/2.0)
w = lambda*:math.sqrt(2.0/alpha)*t
if z > 0.0 do
:math.exp(-t*t/alpha) * 0.5 * ( 1.0 + :math.erf(w/:math.sqrt(2.0)) )
else
:math.exp(-t*t/alpha) * 0.5 * ( :math.erfc(w/:math.sqrt(2.0)) )
end
end
end
def skewness(%SEP{pars: nil}) do
fn [_a,_b,lambda,_alpha] ->
delta = lambda/:math.sqrt(1+lambda*lambda)
0.5*(4-@pi)*:math.pow(delta*:math.sqrt(2/@pi),3)/:math.pow(1-2*delta*delta/@pi,1.5)
end
end
def kurtosis(%SEP{pars: nil}) do
fn [_a,_b,lambda,_alpha] ->
delta = lambda/:math.sqrt(1+lambda*lambda)
2*(@pi-3)*:math.pow(delta*:math.sqrt(2/@pi),4)/:math.pow(1-2*delta*delta/@pi,2)
end
end
def size(%SEP{offset: nil}), do: 4
def size(%SEP{offset: offset}) when is_number(offset), do: 3
def cdf(%SEP{pars: nil, options: options}), do: fn x,[a,b,lambda,alpha] -> sepCDF(a,b,lambda,alpha,options).(x) end
def pdf(%SEP{pars: nil}), do: fn x,[a,b,lambda,alpha] -> sepPDF(a,b,lambda,alpha).(x) end
def random(%SEP{}), do: raise D.FunctionNotSupportedError, message: "random is not supported for the SEP distribution"
def name(model), do: model.name
end
defimpl Inspect, for: Chi2fit.Distribution.SEP do
import Inspect.Algebra
def inspect(dict, opts) do
case {dict.pars,dict.offset} do
{nil,nil} ->
"#SEP<>"
{nil,offset} ->
concat ["#SEP<", to_doc("offset=#{offset}", opts), ">"]
{[scale,lambda,alpha],nil} ->
concat ["#SEP<", to_doc("scale=#{scale}, lambda=#{lambda}, alpha=#{alpha}", opts), ">"]
{[scale,lambda,alpha],offset} ->
concat ["#SEP<", to_doc("offset=#{offset}, scale=#{scale}, lambda=#{lambda}, alpha=#{alpha}", opts), ">"]
{list,offset} ->
concat ["#SEP<", "offset=#{offset}, ", to_doc(list, opts), ">"]
end
end
end