// // Math.NET Numerics, part of the Math.NET Project // http://numerics.mathdotnet.com // http://github.com/mathnet/mathnet-numerics // // Copyright (c) 2009-2020 Math.NET // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation // files (the "Software"), to deal in the Software without // restriction, including without limitation the rights to use, // copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following // conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. // using IStation.Numerics.Random; using System; using System.Collections.Generic; namespace IStation.Numerics.Distributions { /// /// Continuous Univariate Skewed Generalized Error Distribution (SGED). /// Implements the univariate SSkewed Generalized Error Distribution. For details about this /// distribution, see /// /// Wikipedia - Generalized Error Distribution. /// It includes Laplace, Normal and Student-t distributions. /// This is the distribution with q=Inf. /// /// This implementation is based on the R package dsgt and corresponding viginette, see /// https://cran.r-project.org/web/packages/sgt/vignettes/sgt.pdf. Compared to that /// implementation, the options for mean adjustment and variance adjustment are always true. /// The location (μ) is the mean of the distribution. /// The scale (σ) squared is the variance of the distribution. /// /// The distribution will use the by /// default. Users can get/set the random number generator by using the /// property. /// The statistics classes will check all the incoming parameters /// whether they are in the allowed range. public class SkewedGeneralizedError : IContinuousDistribution { System.Random _random; readonly double _skewness; /// /// Initializes a new instance of the SkewedGeneralizedError class. This is a generalized error distribution /// with location=0.0, scale=1.0, skew=0.0 and p=2.0 (a standard normal distribution). /// public SkewedGeneralizedError() { _random = SystemRandomSource.Default; Location = 0.0; Scale = 1.0; Skew = 0.0; P = 2.0; } /// /// Initializes a new instance of the SkewedGeneralizedT class with a particular location, scale, skew /// and kurtosis parameters. Different parameterizations result in different distributions. /// /// The location (μ) of the distribution. /// The scale (σ) of the distribution. Range: σ > 0. /// The skew, 1 > λ > -1 /// Parameter that controls kurtosis. Range: p > 0 public SkewedGeneralizedError(double location, double scale, double skew, double p) { if (!IsValidParameterSet(location, scale, skew, p)) { throw new ArgumentException("Invalid parametrization for the distribution."); } _random = SystemRandomSource.Default; Location = location; Scale = scale; Skew = skew; P = p; _skewness = CalculateSkewness(); } /// /// Gets or sets the random number generator which is used to draw random samples. /// public System.Random RandomSource { get => _random; set => _random = value ?? SystemRandomSource.Default; } /// /// A string representation of the distribution. /// /// a string representation of the distribution. public override string ToString() { return $"SkewedGeneralizedError(μ = {Location}, σ = {Scale}, λ = { Skew }, p = {P}"; } /// /// Tests whether the provided values are valid parameters for this distribution. /// /// The location (μ) of the distribution. /// The scale (σ) of the distribution. Range: σ > 0. /// The skew, 1 > λ > -1 /// Parameter that controls kurtosis. Range: p > 0 public static bool IsValidParameterSet(double location, double scale, double skew, double p) { return scale > 0.0 && skew > -1.0 && skew < 1.0 && p > 0.0 && !double.IsNaN(location); } /// /// Gets the location (μ) of the Skewed Generalized t-distribution. /// public double Location { get; private set; } /// /// Gets the scale (σ) of the Skewed Generalized t-distribution. Range: σ > 0. /// public double Scale { get; private set; } /// /// Gets the skew (λ) of the Skewed Generalized t-distribution. Range: 1 > λ > -1. /// public double Skew { get; private set; } /// /// Gets the parameter that controls the kurtosis of the distribution. Range: p > 0. /// public double P { get; private set; } // No skew implies Median=Mode=Mean public double Mode => Skew == 0 ? Mean : Mean - AdjustAddend(AdjustScale(Scale, Skew, P), Skew, P); public double Minimum => double.NegativeInfinity; public double Maximum => double.PositiveInfinity; // Mean=Location due to our adjustments made public double Mean => Location; // Variance=Scale*Scale due to our adjustments made public double Variance => Scale * Scale; public double StdDev => Scale; public double Entropy => throw new NotImplementedException(); public double Skewness => _skewness; // No skew implies Median=Mode=Mean // Else find it via the point where CDF gives 0.5 public double Median => Skew == 0 ? Mean : InverseCumulativeDistribution(0.5); double CalculateSkewness() { if (Skew == 0) { return 0.0; } var piPow = Math.Pow(Constants.Pi, 3.0 / 2.0); var g1 = SpecialFunctions.Gamma(1.0 / P); var g2 = SpecialFunctions.Gamma(0.5 + 1.0 / P); var g3 = SpecialFunctions.Gamma(3.0 / P); var g4 = SpecialFunctions.Gamma(4.0 / P); var t1 = Skew * Scale * Scale * Scale / (piPow * g1); var t2 = Math.Pow(2.0, (6.0 + P) / P) * Skew * Skew * Math.Pow(g2, 3.0) * g1; var t3 = 3.0 * Math.Pow(4.0, 1.0 / P) * Constants.Pi * (1.0 + 3.0 * Skew * Skew) * g2 * g3; var t4 = 4.0 * piPow * (1.0 + Skew * Skew) * g4; return t1 * (t2 - t3 + t4); } static double AdjustScale(double scale, double skew, double p) { var g1 = SpecialFunctions.Gamma(3.0 / p); var g2 = SpecialFunctions.Gamma(0.5 + 1.0 / p); var g3 = SpecialFunctions.Gamma(1.0 / p); var g4 = SpecialFunctions.Gamma(1.0 / p); var n1 = Constants.Pi * (1.0 + 3.0 * skew * skew) * g1; var n2 = Math.Pow(16.0, 1.0 / p) * skew * skew * Math.Pow(g2, 2) * g3; var d = Constants.Pi * g4; return scale / Math.Sqrt((n1 - n2) / d); } static double AdjustX(double x, double scale, double skew, double p) { return x + AdjustAddend(scale, skew, p); } static double AdjustAddend(double scale, double skew, double p) { return (Math.Pow(2.0, 2.0 / p) * scale * skew * SpecialFunctions.Gamma(1.0 / 2.0 + 1.0 / p)) / Math.Sqrt(Constants.Pi); } public static double PDF(double location, double scale, double skew, double p, double x) { if (!IsValidParameterSet(location, scale, skew, p)) { throw new ArgumentException("Invalid parametrization for the distribution."); } scale = AdjustScale(scale, skew, p); x = AdjustX(x, scale, skew, p); // p / (2 * sigma * gamma(1 / p) * exp((abs(x - mu) / (sigma * (1 + lambda * sgn(x - mu)))) ^ p)) var d1 = Math.Abs(x - location); var d2 = scale * (1.0 + skew * Math.Sign(x - location)); var d3 = 2.0 * scale * SpecialFunctions.Gamma(1.0 / p); return p / (Math.Exp(Math.Pow(d1 / d2, p)) * d3); } public static double PDFLn(double location, double scale, double skew, double p, double x) { if (!IsValidParameterSet(location, scale, skew, p)) { throw new ArgumentException("Invalid parametrization for the distribution."); } scale = AdjustScale(scale, skew, p); x = AdjustX(x, scale, skew, p); return Math.Log(p) - Math.Log(2.0) - Math.Log(scale) - SpecialFunctions.GammaLn(1.0 / p) - Math.Pow(Math.Abs(x - location) / (scale * (1.0 + skew * Math.Sign(x - location))), p); } public static double CDF(double location, double scale, double skew, double p, double x) { if (!IsValidParameterSet(location, scale, skew, p)) { throw new ArgumentException("Invalid parametrization for the distribution."); } scale = AdjustScale(scale, skew, p); x = AdjustX(x, scale, skew, p) - location; var flip = x < 0; if (flip) { skew = -skew; x = -x; } var res = (1.0 - skew) / 2.0 + (1.0 + skew) / 2.0 * Gamma.CDF(1.0 / p, 1.0, Math.Pow(x / (scale * (1.0 + skew)), p)); return flip ? 1.0 - res : res; } public static double InvCDF(double location, double scale, double skew, double p, double pr) { if (!IsValidParameterSet(location, scale, skew, p)) { throw new ArgumentException("Invalid parametrization for the distribution."); } scale = AdjustScale(scale, skew, p); var flip = pr < (1.0 - skew) / 2.0; var lambda = skew; if (flip) { pr = 1.0 - pr; lambda = -lambda; } var res = scale * (1.0 + lambda) * Math.Pow(Gamma.InvCDF(1.0 / p, 1.0, 2 * pr / (1.0 + lambda) + (lambda - 1.0) / (lambda + 1.0)), 1.0 / p); if (flip) res = -res; res += location; return res - AdjustAddend(scale, skew, p); } public double InverseCumulativeDistribution(double p) { return InvCDF(Location, Scale, Skew, P, p); } public double CumulativeDistribution(double x) { return CDF(Location, Scale, Skew, P, x); } public double Density(double x) { return PDF(Location, Scale, Skew, P, x); } public double DensityLn(double x) { return PDFLn(Location, Scale, Skew, P, x); } public double Sample() { return SampleUnchecked(_random, Location, Scale, Skew, P); } public void Samples(double[] values) { SamplesUnchecked(_random, values, Location, Scale, Skew, P); } public IEnumerable Samples() { return SamplesUnchecked(_random, Location, Scale, Skew, P); } static double SampleUnchecked(System.Random rnd, double location, double scale, double skew, double p) { var u = ContinuousUniform.Sample(rnd, 0, 1); return InvCDF(location, scale, skew, p, u); } static void SamplesUnchecked(System.Random rnd, double[] values, double location, double scale, double skew, double p) { for (int i = 0; i < values.Length; i++) { values[i] = SampleUnchecked(rnd, location, scale, skew, p); } } static IEnumerable SamplesUnchecked(System.Random rnd, double location, double scale, double skew, double p) { while (true) { yield return SampleUnchecked(rnd, location, scale, skew, p); } } /// /// Generates a sample from the Skew Generalized Error distribution. /// /// The random number generator to use. /// The location (μ) of the distribution. /// The scale (σ) of the distribution. Range: σ > 0. /// The skew, 1 > λ > -1 /// Parameter that controls kurtosis. Range: p > 0 /// a sample from the distribution. public static double Sample(System.Random rnd, double location, double scale, double skew, double p) { if (!IsValidParameterSet(location, scale, skew, p)) { throw new ArgumentException("Invalid parametrization for the distribution."); } return SampleUnchecked(rnd, location, scale, skew, p); } /// /// Generates a sequence of samples from the Skew Generalized Error distribution using inverse transform. /// /// The random number generator to use. /// The location (μ) of the distribution. /// The scale (σ) of the distribution. Range: σ > 0. /// The skew, 1 > λ > -1 /// Parameter that controls kurtosis. Range: p > 0 /// a sequence of samples from the distribution. public static IEnumerable Samples(System.Random rnd, double location, double scale, double skew, double p) { if (!IsValidParameterSet(location, scale, skew, p)) { throw new ArgumentException("Invalid parametrization for the distribution."); } return SamplesUnchecked(rnd, location, scale, skew, p); } /// /// Fills an array with samples from the Skew Generalized Error distribution using inverse transform. /// /// The random number generator to use. /// The array to fill with the samples. /// The location (μ) of the distribution. /// The scale (σ) of the distribution. Range: σ > 0. /// The skew, 1 > λ > -1 /// Parameter that controls kurtosis. Range: p > 0 /// a sequence of samples from the distribution. public static void Samples(System.Random rnd, double[] values, double location, double scale, double skew, double p) { if (!IsValidParameterSet(location, scale, skew, p)) { throw new ArgumentException("Invalid parametrization for the distribution."); } SamplesUnchecked(rnd, values, location, scale, skew, p); } /// /// Generates a sample from the Skew Generalized Error distribution. /// /// The location (μ) of the distribution. /// The scale (σ) of the distribution. Range: σ > 0. /// The skew, 1 > λ > -1 /// Parameter that controls kurtosis. Range: p > 0 /// a sample from the distribution. public static double Sample(double location, double scale, double skew, double p) { if (!IsValidParameterSet(location, scale, skew, p)) { throw new ArgumentException("Invalid parametrization for the distribution."); } return SampleUnchecked(SystemRandomSource.Default, location, scale, skew, p); } /// /// Generates a sequence of samples from the Skew Generalized Error distribution using inverse transform. /// /// The location (μ) of the distribution. /// The scale (σ) of the distribution. Range: σ > 0. /// The skew, 1 > λ > -1 /// Parameter that controls kurtosis. Range: p > 0 /// a sequence of samples from the distribution. public static IEnumerable Samples(double location, double scale, double skew, double p) { if (!IsValidParameterSet(location, scale, skew, p)) { throw new ArgumentException("Invalid parametrization for the distribution."); } return SamplesUnchecked(SystemRandomSource.Default, location, scale, skew, p); } /// /// Fills an array with samples from the Skew Generalized Error distribution using inverse transform. /// /// The array to fill with the samples. /// The location (μ) of the distribution. /// The scale (σ) of the distribution. Range: σ > 0. /// The skew, 1 > λ > -1 /// Parameter that controls kurtosis. Range: p > 0 /// a sequence of samples from the distribution. public static void Samples(double[] values, double location, double scale, double skew, double p) { if (!IsValidParameterSet(location, scale, skew, p)) { throw new ArgumentException("Invalid parametrization for the distribution."); } SamplesUnchecked(SystemRandomSource.Default, values, location, scale, skew, p); } } }