001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.statistics.distribution;
018
019import org.apache.commons.rng.UniformRandomProvider;
020import org.apache.commons.rng.sampling.distribution.StableSampler;
021
022/**
023 * Implementation of the Cauchy distribution.
024 *
025 * <p>The probability density function of \( X \) is:
026 *
027 * <p>\[ f(x; x_0, \gamma) = { 1 \over \pi \gamma } \left[ { \gamma^2 \over (x - x_0)^2 + \gamma^2  } \right] \]
028 *
029 * <p>for \( x_0 \) the location,
030 * \( \gamma &gt; 0 \) the scale, and
031 * \( x \in (-\infty, \infty) \).
032 *
033 * @see <a href="https://en.wikipedia.org/wiki/Cauchy_distribution">Cauchy distribution (Wikipedia)</a>
034 * @see <a href="https://mathworld.wolfram.com/CauchyDistribution.html">Cauchy distribution (MathWorld)</a>
035 */
036public final class CauchyDistribution extends AbstractContinuousDistribution {
037    /** The location of this distribution. */
038    private final double location;
039    /** The scale of this distribution. */
040    private final double scale;
041    /** Density factor (scale / pi). */
042    private final double scaleOverPi;
043    /** Density factor (scale^2). */
044    private final double scale2;
045
046    /**
047     * @param location Location parameter.
048     * @param scale Scale parameter.
049     */
050    private CauchyDistribution(double location,
051                               double scale) {
052        this.scale = scale;
053        this.location = location;
054        scaleOverPi = scale / Math.PI;
055        scale2 = scale * scale;
056    }
057
058    /**
059     * Creates a Cauchy distribution.
060     *
061     * @param location Location parameter.
062     * @param scale Scale parameter.
063     * @return the distribution
064     * @throws IllegalArgumentException if {@code scale <= 0}.
065     */
066    public static CauchyDistribution of(double location,
067                                        double scale) {
068        if (scale <= 0) {
069            throw new DistributionException(DistributionException.NOT_STRICTLY_POSITIVE, scale);
070        }
071        return new CauchyDistribution(location, scale);
072    }
073
074    /**
075     * Gets the location parameter of this distribution.
076     *
077     * @return the location parameter.
078     */
079    public double getLocation() {
080        return location;
081    }
082
083    /**
084     * Gets the scale parameter of this distribution.
085     *
086     * @return the scale parameter.
087     */
088    public double getScale() {
089        return scale;
090    }
091
092    /** {@inheritDoc} */
093    @Override
094    public double density(double x) {
095        final double dev = x - location;
096        return scaleOverPi / (dev * dev + scale2);
097    }
098
099    /** {@inheritDoc} */
100    @Override
101    public double cumulativeProbability(double x) {
102        return cdf((x - location) / scale);
103    }
104
105    /** {@inheritDoc} */
106    @Override
107    public double survivalProbability(double x) {
108        return cdf(-(x - location) / scale);
109    }
110
111    /**
112     * Compute the CDF of the Cauchy distribution with location 0 and scale 1.
113     * @param x Point at which the CDF is evaluated
114     * @return CDF(x)
115     */
116    private static double cdf(double x) {
117        return 0.5 + (Math.atan(x) / Math.PI);
118    }
119
120    /**
121     * {@inheritDoc}
122     *
123     * <p>Returns {@link Double#NEGATIVE_INFINITY} when {@code p == 0}
124     * and {@link Double#POSITIVE_INFINITY} when {@code p == 1}.
125     */
126    @Override
127    public double inverseCumulativeProbability(double p) {
128        ArgumentUtils.checkProbability(p);
129        if (p == 0) {
130            return Double.NEGATIVE_INFINITY;
131        } else  if (p == 1) {
132            return Double.POSITIVE_INFINITY;
133        }
134        return location + scale * Math.tan(Math.PI * (p - 0.5));
135    }
136
137    /**
138     * {@inheritDoc}
139     *
140     * <p>Returns {@link Double#NEGATIVE_INFINITY} when {@code p == 1}
141     * and {@link Double#POSITIVE_INFINITY} when {@code p == 0}.
142     */
143    @Override
144    public double inverseSurvivalProbability(double p) {
145        ArgumentUtils.checkProbability(p);
146        if (p == 1) {
147            return Double.NEGATIVE_INFINITY;
148        } else  if (p == 0) {
149            return Double.POSITIVE_INFINITY;
150        }
151        return location - scale * Math.tan(Math.PI * (p - 0.5));
152    }
153
154    /**
155     * {@inheritDoc}
156     *
157     * <p>The mean is always undefined.
158     *
159     * @return {@link Double#NaN NaN}.
160     */
161    @Override
162    public double getMean() {
163        return Double.NaN;
164    }
165
166    /**
167     * {@inheritDoc}
168     *
169     * <p>The variance is always undefined.
170     *
171     * @return {@link Double#NaN NaN}.
172     */
173    @Override
174    public double getVariance() {
175        return Double.NaN;
176    }
177
178    /**
179     * {@inheritDoc}
180     *
181     * <p>The lower bound of the support is always negative infinity.
182     *
183     * @return {@linkplain Double#NEGATIVE_INFINITY negative infinity}.
184     */
185    @Override
186    public double getSupportLowerBound() {
187        return Double.NEGATIVE_INFINITY;
188    }
189
190    /**
191     * {@inheritDoc}
192     *
193     * <p>The upper bound of the support is always positive infinity.
194     *
195     * @return {@linkplain Double#POSITIVE_INFINITY positive infinity}.
196     */
197    @Override
198    public double getSupportUpperBound() {
199        return Double.POSITIVE_INFINITY;
200    }
201
202    /** {@inheritDoc} */
203    @Override
204    double getMedian() {
205        // Overridden for the probability(double, double) method.
206        // This is intentionally not a public method.
207        return location;
208    }
209
210    /** {@inheritDoc} */
211    @Override
212    public ContinuousDistribution.Sampler createSampler(final UniformRandomProvider rng) {
213        // Cauchy distribution =
214        // Stable distribution with alpha=1, beta=0, gamma=scale, delta=location
215        return StableSampler.of(rng, 1, 0, getScale(), getLocation())::sample;
216    }
217}