/*========================================================================= * * Copyright NumFOCUS * * 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 * * https://www.apache.org/licenses/LICENSE-2.0.txt * * 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. * *=========================================================================*/ #include "itkTDistribution.h" #include "itkGaussianDistribution.h" #include "vnl/vnl_erf.h" #include "itkMath.h" extern "C" double dbetai_(double * x, double * pin, double * qin); extern "C" double dgamma_(double * x); namespace itk { namespace Statistics { TDistribution::TDistribution() { m_Parameters = ParametersType(1); m_Parameters[0] = 1.0; } void TDistribution::SetDegreesOfFreedom(SizeValueType dof) { bool modified = false; if (m_Parameters.GetSize() > 0) { if (Math::NotExactlyEquals(m_Parameters[0], static_cast(dof))) { modified = true; } } if (m_Parameters.GetSize() != 1) { m_Parameters = ParametersType(1); } m_Parameters[0] = static_cast(dof); if (modified) { this->Modified(); } } SizeValueType TDistribution::GetDegreesOfFreedom() const { if (m_Parameters.GetSize() != 1) { itkGenericExceptionMacro("Invalid number of parameters to describe distribution. Expected 1 parameter, but got " << m_Parameters.size() << " parameters."); } return static_cast(m_Parameters[0]); } double TDistribution::PDF(double x, SizeValueType degreesOfFreedom) { auto dof = static_cast(degreesOfFreedom); double dofplusoneon2 = 0.5 * (dof + 1.0); double dofon2 = 0.5 * dof; double pdf; pdf = (dgamma_(&dofplusoneon2) / dgamma_(&dofon2)) / (std::sqrt(dof * itk::Math::pi) * std::pow(1.0 + ((x * x) / dof), dofplusoneon2)); return pdf; } double TDistribution::PDF(double x, const ParametersType & p) { if (p.GetSize() != 1) { itkGenericExceptionMacro("Invalid number of parameters to describe distribution. Expected 1 parameter, but got " << p.size() << " parameters."); } return TDistribution::PDF(x, static_cast(p[0])); } double TDistribution::CDF(double x, SizeValueType degreesOfFreedom) { double bx; double pin, qin; double dof; // Based on Abramowitz and Stegun 26.7.1, which gives the probability // that the absolute value of a random variable with a Student-t // distribution is less than or equal to a specified t. // // P[|x| <= t] = 1 - Ix(v/2, 1/2) // // where Ix is the incomplete beta function and v is the number of // degrees of freedom in the Student-t distribution and x is // v / (v + t^2). // // To calculate the cdf of the Student-t we need to convert // this formula. For an x >= 0, // // P[|x| <= t] = \int_{-t}^{t} p(x) dx // = 2 \int_0^t p(x) dx // // The cdf of the Student-t is // // P[x <= t] = \int_{-\inf}^t p(x) dx // = 0.5 + \int_0^t p(x) dx (for x >= 0) // = 0.5 + 0.5 * P[|x| < t] (from above) // = 0.5 + 0.5 * (1 - Ix(v/2. 1/2)) // = 1 - 0.5 * Ix(v/2, 1/2) // dof = static_cast(degreesOfFreedom); bx = dof / (dof + (x * x)); pin = dof / 2.0; qin = 0.5; if (x >= 0.0) { return 1.0 - 0.5 * dbetai_(&bx, &pin, &qin); } else { return 0.5 * dbetai_(&bx, &pin, &qin); } } double TDistribution::CDF(double x, const ParametersType & p) { if (p.GetSize() != 1) { itkGenericExceptionMacro("Invalid number of parameters to describe distribution. Expected 1 parameter, but got " << p.size() << " parameters."); } return TDistribution::CDF(x, static_cast(p[0])); } double TDistribution::InverseCDF(double p, SizeValueType degreesOfFreedom) { if (p <= 0.0) { return itk::NumericTraits::NonpositiveMin(); } else if (p >= 1.0) { return itk::NumericTraits::max(); } double x; double dof, dof2, dof3, dof4; double gaussX, gaussX3, gaussX5, gaussX7, gaussX9; // Based on Abramowitz and Stegun 26.7.5 dof = static_cast(degreesOfFreedom); dof2 = dof * dof; dof3 = dof * dof2; dof4 = dof * dof3; gaussX = GaussianDistribution::InverseCDF(p); gaussX3 = std::pow(gaussX, 3.0); gaussX5 = std::pow(gaussX, 5.0); gaussX7 = std::pow(gaussX, 7.0); gaussX9 = std::pow(gaussX, 9.0); x = gaussX + (gaussX3 + gaussX) / (4.0 * dof) + (5.0 * gaussX5 + 16.0 * gaussX3 + 3 * gaussX) / (96.0 * dof2) + (3.0 * gaussX7 + 19.0 * gaussX5 + 17.0 * gaussX3 - 15.0 * gaussX) / (384.0 * dof3) + (79.0 * gaussX9 + 776.0 * gaussX7 + 1482.0 * gaussX5 - 1920.0 * gaussX3 - 945.0 * gaussX) / (92160.0 * dof4); // The polynomial approximation above is only accurate for large degrees // of freedom. We'll improve the approximation by a few Newton // iterations. // // 0 iterations, error = 1 at 1 degree of freedom // 3 iterations, error = 10^-10 at 1 degree of freedom // 100 iterations, error = 10^-12 at 1 degree of freedom // // 0 iterations, error = 10^-2 at 11 degrees of freedom // 3 iterations, error = 10^-11 at 11 degrees of freedom // 100 iterations, error = 10^-12 at 11 degrees of freedom // // // We are trying to find the zero of // // f(x) = p - tcdf(x) = 0; // // So, // // x(n+1) = x(n) - f(x(n)) / f'(x(n)) // = x(n) + (p - tcdf(x)) / tpdf(x) // // Note that f'(x) = - tpdf(x) // double delta; for (unsigned int newt = 0; newt < 3; ++newt) { delta = (p - TDistribution::CDF(x, degreesOfFreedom)) / TDistribution::PDF(x, degreesOfFreedom); x += delta; } return x; } double TDistribution::InverseCDF(double p, const ParametersType & params) { if (params.GetSize() != 1) { itkGenericExceptionMacro("Invalid number of parameters to describe distribution. Expected 1 parameter, but got " << params.size() << " parameters."); } return TDistribution::InverseCDF(p, static_cast(params[0])); } double TDistribution::EvaluatePDF(double x) const { if (m_Parameters.GetSize() != 1) { itkGenericExceptionMacro("Invalid number of parameters to describe distribution. Expected 1 parameter, but got " << m_Parameters.size() << " parameters."); } return TDistribution::PDF(x, static_cast(m_Parameters[0])); } double TDistribution::EvaluatePDF(double x, const ParametersType & p) const { if (p.GetSize() != 1) { itkGenericExceptionMacro("Invalid number of parameters to describe distribution. Expected 1 parameter, but got " << p.size() << " parameters."); } return TDistribution::PDF(x, static_cast(p[0])); } double TDistribution::EvaluatePDF(double x, SizeValueType degreesOfFreedom) const { return TDistribution::PDF(x, degreesOfFreedom); } double TDistribution::EvaluateCDF(double x) const { if (m_Parameters.GetSize() != 1) { itkGenericExceptionMacro("Invalid number of parameters to describe distribution. Expected 1 parameter, but got " << m_Parameters.size() << " parameters."); } return TDistribution::CDF(x, static_cast(m_Parameters[0])); } double TDistribution::EvaluateCDF(double x, const ParametersType & p) const { if (p.GetSize() != 1) { itkGenericExceptionMacro("Invalid number of parameters to describe distribution. Expected 1 parameter, but got " << p.size() << " parameters."); } return TDistribution::CDF(x, static_cast(p[0])); } double TDistribution::EvaluateCDF(double x, SizeValueType degreesOfFreedom) const { return TDistribution::CDF(x, degreesOfFreedom); } double TDistribution::EvaluateInverseCDF(double p) const { if (m_Parameters.GetSize() != 1) { itkGenericExceptionMacro("Invalid number of parameters to describe distribution. Expected 1 parameter, but got " << m_Parameters.size() << " parameters."); } return TDistribution::InverseCDF(p, static_cast(m_Parameters[0])); } double TDistribution::EvaluateInverseCDF(double p, const ParametersType & params) const { if (params.GetSize() != 1) { itkGenericExceptionMacro("Invalid number of parameters to describe distribution. Expected 1 parameter, but got " << params.size() << " parameters."); } return TDistribution::InverseCDF(p, static_cast(params[0])); } double TDistribution::EvaluateInverseCDF(double p, SizeValueType degreesOfFreedom) const { return TDistribution::InverseCDF(p, degreesOfFreedom); } bool TDistribution::HasVariance() const { if (m_Parameters.GetSize() == 1) { if (m_Parameters[0] > 2) { return true; } } else { itkGenericExceptionMacro("Invalid number of parameters to describe distribution. Expected 1 parameter, but got " << m_Parameters.size() << " parameters."); } return false; } double TDistribution::GetMean() const { return 0.0; } double TDistribution::GetVariance() const { if (m_Parameters.GetSize() == 1) { if (m_Parameters[0] > 2) { auto dof = static_cast(m_Parameters[0]); return dof / (dof - 2.0); } else { return NumericTraits::quiet_NaN(); } } else { itkGenericExceptionMacro("Invalid number of parameters to describe distribution. Expected 1 parameter, but got " << m_Parameters.size() << " parameters."); } } void TDistribution::PrintSelf(std::ostream & os, Indent indent) const { Superclass::PrintSelf(os, indent); if (m_Parameters.GetSize() > 0) { os << indent << "Degrees of freedom: " << static_cast(m_Parameters[0]) << std::endl; } else { os << indent << "Degrees of freedom: (unknown)" << std::endl; } } } // end of namespace Statistics } // end namespace itk