NormalDistribution.java
- /*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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.
- */
- package org.apache.commons.statistics.distribution;
- import org.apache.commons.numbers.gamma.ErfDifference;
- import org.apache.commons.numbers.gamma.Erfc;
- import org.apache.commons.numbers.gamma.InverseErfc;
- import org.apache.commons.rng.UniformRandomProvider;
- import org.apache.commons.rng.sampling.distribution.GaussianSampler;
- import org.apache.commons.rng.sampling.distribution.ZigguratSampler;
- /**
- * Implementation of the normal (Gaussian) distribution.
- *
- * <p>The probability density function of \( X \) is:
- *
- * <p>\[ f(x; \mu, \sigma) = \frac 1 {\sigma\sqrt{2\pi}} e^{-{\frac 1 2}\left( \frac{x-\mu}{\sigma} \right)^2 } \]
- *
- * <p>for \( \mu \) the mean,
- * \( \sigma > 0 \) the standard deviation, and
- * \( x \in (-\infty, \infty) \).
- *
- * @see <a href="https://en.wikipedia.org/wiki/Normal_distribution">Normal distribution (Wikipedia)</a>
- * @see <a href="https://mathworld.wolfram.com/NormalDistribution.html">Normal distribution (MathWorld)</a>
- */
- public final class NormalDistribution extends AbstractContinuousDistribution {
- /** Mean of this distribution. */
- private final double mean;
- /** Standard deviation of this distribution. */
- private final double standardDeviation;
- /** The value of {@code log(sd) + 0.5*log(2*pi)} stored for faster computation. */
- private final double logStandardDeviationPlusHalfLog2Pi;
- /**
- * Standard deviation multiplied by sqrt(2).
- * This is used to avoid a double division when computing the value passed to the
- * error function:
- * <pre>
- * ((x - u) / sd) / sqrt(2) == (x - u) / (sd * sqrt(2)).
- * </pre>
- * <p>Note: Implementations may first normalise x and then divide by sqrt(2) resulting
- * in differences due to rounding error that show increasingly large relative
- * differences as the error function computes close to 0 in the extreme tail.
- */
- private final double sdSqrt2;
- /**
- * Standard deviation multiplied by sqrt(2 pi). Computed to high precision.
- */
- private final double sdSqrt2pi;
- /**
- * @param mean Mean for this distribution.
- * @param sd Standard deviation for this distribution.
- */
- private NormalDistribution(double mean,
- double sd) {
- this.mean = mean;
- standardDeviation = sd;
- logStandardDeviationPlusHalfLog2Pi = Math.log(sd) + Constants.HALF_LOG_TWO_PI;
- // Minimise rounding error by computing sqrt(2 * sd * sd) exactly.
- // Compute using extended precision with care to avoid over/underflow.
- sdSqrt2 = ExtendedPrecision.sqrt2xx(sd);
- // Compute sd * sqrt(2 * pi)
- sdSqrt2pi = ExtendedPrecision.xsqrt2pi(sd);
- }
- /**
- * Creates a normal distribution.
- *
- * @param mean Mean for this distribution.
- * @param sd Standard deviation for this distribution.
- * @return the distribution
- * @throws IllegalArgumentException if {@code sd <= 0}.
- */
- public static NormalDistribution of(double mean,
- double sd) {
- if (sd > 0) {
- return new NormalDistribution(mean, sd);
- }
- // zero, negative or nan
- throw new DistributionException(DistributionException.NOT_STRICTLY_POSITIVE, sd);
- }
- /**
- * Gets the standard deviation parameter of this distribution.
- *
- * @return the standard deviation.
- */
- public double getStandardDeviation() {
- return standardDeviation;
- }
- /** {@inheritDoc} */
- @Override
- public double density(double x) {
- final double z = (x - mean) / standardDeviation;
- return ExtendedPrecision.expmhxx(z) / sdSqrt2pi;
- }
- /** {@inheritDoc} */
- @Override
- public double probability(double x0,
- double x1) {
- if (x0 > x1) {
- throw new DistributionException(DistributionException.INVALID_RANGE_LOW_GT_HIGH,
- x0, x1);
- }
- final double v0 = (x0 - mean) / sdSqrt2;
- final double v1 = (x1 - mean) / sdSqrt2;
- return 0.5 * ErfDifference.value(v0, v1);
- }
- /** {@inheritDoc} */
- @Override
- public double logDensity(double x) {
- final double z = (x - mean) / standardDeviation;
- return -0.5 * z * z - logStandardDeviationPlusHalfLog2Pi;
- }
- /** {@inheritDoc} */
- @Override
- public double cumulativeProbability(double x) {
- final double dev = x - mean;
- return 0.5 * Erfc.value(-dev / sdSqrt2);
- }
- /** {@inheritDoc} */
- @Override
- public double survivalProbability(double x) {
- final double dev = x - mean;
- return 0.5 * Erfc.value(dev / sdSqrt2);
- }
- /** {@inheritDoc} */
- @Override
- public double inverseCumulativeProbability(double p) {
- ArgumentUtils.checkProbability(p);
- return mean - sdSqrt2 * InverseErfc.value(2 * p);
- }
- /** {@inheritDoc} */
- @Override
- public double inverseSurvivalProbability(double p) {
- ArgumentUtils.checkProbability(p);
- return mean + sdSqrt2 * InverseErfc.value(2 * p);
- }
- /** {@inheritDoc} */
- @Override
- public double getMean() {
- return mean;
- }
- /**
- * {@inheritDoc}
- *
- * <p>For standard deviation parameter \( \sigma \), the variance is \( \sigma^2 \).
- */
- @Override
- public double getVariance() {
- final double s = getStandardDeviation();
- return s * s;
- }
- /**
- * {@inheritDoc}
- *
- * <p>The lower bound of the support is always negative infinity.
- *
- * @return {@linkplain Double#NEGATIVE_INFINITY negative infinity}.
- */
- @Override
- public double getSupportLowerBound() {
- return Double.NEGATIVE_INFINITY;
- }
- /**
- * {@inheritDoc}
- *
- * <p>The upper bound of the support is always positive infinity.
- *
- * @return {@linkplain Double#POSITIVE_INFINITY positive infinity}.
- */
- @Override
- public double getSupportUpperBound() {
- return Double.POSITIVE_INFINITY;
- }
- /** {@inheritDoc} */
- @Override
- public ContinuousDistribution.Sampler createSampler(final UniformRandomProvider rng) {
- // Gaussian distribution sampler.
- return GaussianSampler.of(ZigguratSampler.NormalizedGaussian.of(rng),
- mean, standardDeviation)::sample;
- }
- }