MannWhitneyUTest.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.statistics.inference;

  18. import java.lang.ref.SoftReference;
  19. import java.util.Arrays;
  20. import java.util.EnumSet;
  21. import java.util.Objects;
  22. import java.util.stream.IntStream;
  23. import org.apache.commons.numbers.combinatorics.BinomialCoefficientDouble;
  24. import org.apache.commons.statistics.distribution.NormalDistribution;
  25. import org.apache.commons.statistics.ranking.NaNStrategy;
  26. import org.apache.commons.statistics.ranking.NaturalRanking;
  27. import org.apache.commons.statistics.ranking.RankingAlgorithm;
  28. import org.apache.commons.statistics.ranking.TiesStrategy;

  29. /**
  30.  * Implements the Mann-Whitney U test (also called Wilcoxon rank-sum test).
  31.  *
  32.  * @see <a href="https://en.wikipedia.org/wiki/Mann%E2%80%93Whitney_U_test">
  33.  * Mann-Whitney U test (Wikipedia)</a>
  34.  * @since 1.1
  35.  */
  36. public final class MannWhitneyUTest {
  37.     /** Limit on sample size for the exact p-value computation for the auto mode. */
  38.     private static final int AUTO_LIMIT = 50;
  39.     /** Ranking instance. */
  40.     private static final RankingAlgorithm RANKING = new NaturalRanking(NaNStrategy.FAILED, TiesStrategy.AVERAGE);
  41.     /** Value for an unset f computation. */
  42.     private static final double UNSET = -1;
  43.     /** An object to use for synchonization when accessing the cache of F. */
  44.     private static final Object LOCK = new Object();
  45.     /** A reference to a previously computed storage for f.
  46.      * Use of a SoftReference ensures this is garbage collected before an OutOfMemoryError.
  47.      * The value should only be accessed, checked for size and optionally
  48.      * modified when holding the lock. When the storage is determined to be the correct
  49.      * size it can be returned for read/write to the array when not holding the lock. */
  50.     private static SoftReference<double[][][]> cacheF = new SoftReference<>(null);
  51.     /** Default instance. */
  52.     private static final MannWhitneyUTest DEFAULT = new MannWhitneyUTest(
  53.         AlternativeHypothesis.TWO_SIDED, PValueMethod.AUTO, true, 0);

  54.     /** Alternative hypothesis. */
  55.     private final AlternativeHypothesis alternative;
  56.     /** Method to compute the p-value. */
  57.     private final PValueMethod pValueMethod;
  58.     /** Perform continuity correction. */
  59.     private final boolean continuityCorrection;
  60.     /** Expected location shift. */
  61.     private final double mu;

  62.     /**
  63.      * Result for the Mann-Whitney U test.
  64.      *
  65.      * <p>This class is immutable.
  66.      *
  67.      * @since 1.1
  68.      */
  69.     public static final class Result extends BaseSignificanceResult {
  70.         /** Flag indicating the data has tied values. */
  71.         private final boolean tiedValues;

  72.         /**
  73.          * Create an instance.
  74.          *
  75.          * @param statistic Test statistic.
  76.          * @param tiedValues Flag indicating the data has tied values.
  77.          * @param p Result p-value.
  78.          */
  79.         Result(double statistic, boolean tiedValues, double p) {
  80.             super(statistic, p);
  81.             this.tiedValues = tiedValues;
  82.         }

  83.         /**
  84.          * {@inheritDoc}
  85.          *
  86.          * <p>This is the U<sub>1</sub> statistic. Compute the U<sub>2</sub> statistic using
  87.          * the original sample lengths {@code n} and {@code m} using:
  88.          * <pre>
  89.          * u2 = (long) n * m - u1;
  90.          * </pre>
  91.          */
  92.         @Override
  93.         public double getStatistic() {
  94.             // Note: This method is here for documentation
  95.             return super.getStatistic();
  96.         }

  97.         /**
  98.          * Return {@code true} if the data had tied values.
  99.          *
  100.          * <p>Note: The exact computation cannot be used when there are tied values.
  101.          *
  102.          * @return {@code true} if there were tied values
  103.          */
  104.         public boolean hasTiedValues() {
  105.             return tiedValues;
  106.         }
  107.     }

  108.     /**
  109.      * @param alternative Alternative hypothesis.
  110.      * @param method P-value method.
  111.      * @param continuityCorrection true to perform continuity correction.
  112.      * @param mu Expected location shift.
  113.      */
  114.     private MannWhitneyUTest(AlternativeHypothesis alternative, PValueMethod method,
  115.         boolean continuityCorrection, double mu) {
  116.         this.alternative = alternative;
  117.         this.pValueMethod = method;
  118.         this.continuityCorrection = continuityCorrection;
  119.         this.mu = mu;
  120.     }

  121.     /**
  122.      * Return an instance using the default options.
  123.      *
  124.      * <ul>
  125.      * <li>{@link AlternativeHypothesis#TWO_SIDED}
  126.      * <li>{@link PValueMethod#AUTO}
  127.      * <li>{@link ContinuityCorrection#ENABLED}
  128.      * <li>{@linkplain #withMu(double) mu = 0}
  129.      * </ul>
  130.      *
  131.      * @return default instance
  132.      */
  133.     public static MannWhitneyUTest withDefaults() {
  134.         return DEFAULT;
  135.     }

  136.     /**
  137.      * Return an instance with the configured alternative hypothesis.
  138.      *
  139.      * @param v Value.
  140.      * @return an instance
  141.      */
  142.     public MannWhitneyUTest with(AlternativeHypothesis v) {
  143.         return new MannWhitneyUTest(Objects.requireNonNull(v), pValueMethod, continuityCorrection, mu);
  144.     }

  145.     /**
  146.      * Return an instance with the configured p-value method.
  147.      *
  148.      * @param v Value.
  149.      * @return an instance
  150.      * @throws IllegalArgumentException if the value is not in the allowed options or is null
  151.      */
  152.     public MannWhitneyUTest with(PValueMethod v) {
  153.         return new MannWhitneyUTest(alternative,
  154.             Arguments.checkOption(v, EnumSet.of(PValueMethod.AUTO, PValueMethod.EXACT, PValueMethod.ASYMPTOTIC)),
  155.             continuityCorrection, mu);
  156.     }

  157.     /**
  158.      * Return an instance with the configured continuity correction.
  159.      *
  160.      * <p>If {@link ContinuityCorrection#ENABLED ENABLED}, adjust the U rank statistic by
  161.      * 0.5 towards the mean value when computing the z-statistic if a normal approximation is used
  162.      * to compute the p-value.
  163.      *
  164.      * @param v Value.
  165.      * @return an instance
  166.      */
  167.     public MannWhitneyUTest with(ContinuityCorrection v) {
  168.         return new MannWhitneyUTest(alternative, pValueMethod,
  169.             Objects.requireNonNull(v) == ContinuityCorrection.ENABLED, mu);
  170.     }

  171.     /**
  172.      * Return an instance with the configured location shift {@code mu}.
  173.      *
  174.      * @param v Value.
  175.      * @return an instance
  176.      * @throws IllegalArgumentException if the value is not finite
  177.      */
  178.     public MannWhitneyUTest withMu(double v) {
  179.         return new MannWhitneyUTest(alternative, pValueMethod, continuityCorrection, Arguments.checkFinite(v));
  180.     }

  181.     /**
  182.      * Computes the Mann-Whitney U statistic comparing two independent
  183.      * samples possibly of different length.
  184.      *
  185.      * <p>This statistic can be used to perform a Mann-Whitney U test evaluating the
  186.      * null hypothesis that the two independent samples differ by a location shift of {@code mu}.
  187.      *
  188.      * <p>This returns the U<sub>1</sub> statistic. Compute the U<sub>2</sub> statistic using:
  189.      * <pre>
  190.      * u2 = (long) x.length * y.length - u1;
  191.      * </pre>
  192.      *
  193.      * @param x First sample values.
  194.      * @param y Second sample values.
  195.      * @return Mann-Whitney U<sub>1</sub> statistic
  196.      * @throws IllegalArgumentException if {@code x} or {@code y} are zero-length; or contain
  197.      * NaN values.
  198.      * @see #withMu(double)
  199.      */
  200.     public double statistic(double[] x, double[] y) {
  201.         checkSamples(x, y);

  202.         final double[] z = concatenateSamples(mu, x, y);
  203.         final double[] ranks = RANKING.apply(z);

  204.         // The ranks for x is in the first x.length entries in ranks because x
  205.         // is in the first x.length entries in z
  206.         final double sumRankX = Arrays.stream(ranks).limit(x.length).sum();

  207.         // U1 = R1 - (n1 * (n1 + 1)) / 2 where R1 is sum of ranks for sample 1,
  208.         // e.g. x, n1 is the number of observations in sample 1.
  209.         return sumRankX - ((long) x.length * (x.length + 1)) * 0.5;
  210.     }

  211.     /**
  212.      * Performs a Mann-Whitney U test comparing the location for two independent
  213.      * samples. The location is specified using {@link #withMu(double) mu}.
  214.      *
  215.      * <p>The test is defined by the {@link AlternativeHypothesis}.
  216.      * <ul>
  217.      * <li>'two-sided': the distribution underlying {@code (x - mu)} is not equal to the
  218.      * distribution underlying {@code y}.
  219.      * <li>'greater': the distribution underlying {@code (x - mu)} is stochastically greater than
  220.      * the distribution underlying {@code y}.
  221.      * <li>'less': the distribution underlying {@code (x - mu)} is stochastically less than
  222.      * the distribution underlying {@code y}.
  223.      * </ul>
  224.      *
  225.      * <p>If the p-value method is {@linkplain PValueMethod#AUTO auto} an exact p-value is
  226.      * computed if the samples contain less than 50 values; otherwise a normal
  227.      * approximation is used.
  228.      *
  229.      * <p>Computation of the exact p-value is only valid if there are no tied
  230.      * ranks in the data; otherwise the p-value resorts to the asymptotic
  231.      * approximation using a tie correction and an optional continuity correction.
  232.      *
  233.      * <p><strong>Note: </strong>
  234.      * Exact computation requires tabulation of values not exceeding size
  235.      * {@code (n+1)*(m+1)*(u+1)} where {@code u} is the minimum of the U<sub>1</sub> and
  236.      * U<sub>2</sub> statistics and {@code n} and {@code m} are the sample sizes.
  237.      * This may use a very large amount of memory and result in an {@link OutOfMemoryError}.
  238.      * Exact computation requires a finite binomial coefficient {@code binom(n+m, m)}
  239.      * which is limited to {@code n+m <= 1029} for any {@code n} and {@code m},
  240.      * or {@code min(n, m) <= 37} for any {@code max(n, m)}.
  241.      * An {@link OutOfMemoryError} is not expected using the
  242.      * limits configured for the {@linkplain PValueMethod#AUTO auto} p-value computation
  243.      * as the maximum required memory is approximately 23 MiB.
  244.      *
  245.      * @param x First sample values.
  246.      * @param y Second sample values.
  247.      * @return test result
  248.      * @throws IllegalArgumentException if {@code x} or {@code y} are zero-length; or contain
  249.      * NaN values.
  250.      * @throws OutOfMemoryError if the exact computation is <em>user-requested</em> for
  251.      * large samples and there is not enough memory.
  252.      * @see #statistic(double[], double[])
  253.      * @see #withMu(double)
  254.      * @see #with(AlternativeHypothesis)
  255.      * @see #with(ContinuityCorrection)
  256.      */
  257.     public Result test(double[] x, double[] y) {
  258.         // Computation as above. The ranks are required for tie correction.
  259.         checkSamples(x, y);
  260.         final double[] z = concatenateSamples(mu, x, y);
  261.         final double[] ranks = RANKING.apply(z);
  262.         final double sumRankX = Arrays.stream(ranks).limit(x.length).sum();
  263.         final double u1 = sumRankX - ((long) x.length * (x.length + 1)) * 0.5;

  264.         final double c = WilcoxonSignedRankTest.calculateTieCorrection(ranks);
  265.         final boolean tiedValues = c != 0;

  266.         PValueMethod method = pValueMethod;
  267.         final int n = x.length;
  268.         final int m = y.length;
  269.         if (method == PValueMethod.AUTO && Math.max(n, m) < AUTO_LIMIT) {
  270.             method = PValueMethod.EXACT;
  271.         }
  272.         // Exact p requires no ties.
  273.         // The method will fail-fast if the computation is not possible due
  274.         // to the size of the data.
  275.         double p = method == PValueMethod.EXACT && !tiedValues ?
  276.             calculateExactPValue(u1, n, m, alternative) : -1;
  277.         if (p < 0) {
  278.             p = calculateAsymptoticPValue(u1, n, m, c);
  279.         }
  280.         return new Result(u1, tiedValues, p);
  281.     }

  282.     /**
  283.      * Ensures that the provided arrays fulfil the assumptions.
  284.      *
  285.      * @param x First sample values.
  286.      * @param y Second sample values.
  287.      * @throws IllegalArgumentException if {@code x} or {@code y} are zero-length.
  288.      */
  289.     private static void checkSamples(double[] x, double[] y) {
  290.         Arguments.checkValuesRequiredSize(x.length, 1);
  291.         Arguments.checkValuesRequiredSize(y.length, 1);
  292.     }

  293.     /**
  294.      * Concatenate the samples into one array. Subtract {@code mu} from the first sample.
  295.      *
  296.      * @param mu Expected difference between means.
  297.      * @param x First sample values.
  298.      * @param y Second sample values.
  299.      * @return concatenated array
  300.      */
  301.     private static double[] concatenateSamples(double mu, double[] x, double[] y) {
  302.         final double[] z = new double[x.length + y.length];
  303.         System.arraycopy(x, 0, z, 0, x.length);
  304.         System.arraycopy(y, 0, z, x.length, y.length);
  305.         if (mu != 0) {
  306.             for (int i = 0; i < x.length; i++) {
  307.                 z[i] -= mu;
  308.             }
  309.         }
  310.         return z;
  311.     }

  312.     /**
  313.      * Calculate the asymptotic p-value using a Normal approximation.
  314.      *
  315.      * @param u Mann-Whitney U value.
  316.      * @param n1 Number of subjects in first sample.
  317.      * @param n2 Number of subjects in second sample.
  318.      * @param c Tie-correction
  319.      * @return two-sided asymptotic p-value
  320.      */
  321.     private double calculateAsymptoticPValue(double u, int n1, int n2, double c) {
  322.         // Use long to avoid overflow
  323.         final long n1n2 = (long) n1 * n2;
  324.         final long n = (long) n1 + n2;

  325.         // https://en.wikipedia.org/wiki/Mann%E2%80%93Whitney_U_test#Normal_approximation_and_tie_correction
  326.         final double e = n1n2 * 0.5;
  327.         final double variance = (n1n2 / 12.0) * ((n + 1.0) - c / n / (n - 1));

  328.         double z = u - e;
  329.         if (continuityCorrection) {
  330.             // +/- 0.5 is a continuity correction towards the expected.
  331.             if (alternative == AlternativeHypothesis.GREATER_THAN) {
  332.                 z -= 0.5;
  333.             } else if (alternative == AlternativeHypothesis.LESS_THAN) {
  334.                 z += 0.5;
  335.             } else {
  336.                 // two-sided. Shift towards the expected of zero.
  337.                 // Use of signum ignores x==0 (i.e. not copySign(0.5, z))
  338.                 z -= Math.signum(z) * 0.5;
  339.             }
  340.         }
  341.         z /= Math.sqrt(variance);

  342.         final NormalDistribution standardNormal = NormalDistribution.of(0, 1);
  343.         if (alternative == AlternativeHypothesis.GREATER_THAN) {
  344.             return standardNormal.survivalProbability(z);
  345.         }
  346.         if (alternative == AlternativeHypothesis.LESS_THAN) {
  347.             return standardNormal.cumulativeProbability(z);
  348.         }
  349.         // two-sided
  350.         return 2 * standardNormal.survivalProbability(Math.abs(z));
  351.     }

  352.     /**
  353.      * Calculate the exact p-value. If the value cannot be computed this returns -1.
  354.      *
  355.      * <p>Note: Computation may run out of memory during array allocation, or method
  356.      * recursion.
  357.      *
  358.      * @param u Mann-Whitney U value.
  359.      * @param m Number of subjects in first sample.
  360.      * @param n Number of subjects in second sample.
  361.      * @param alternative Alternative hypothesis.
  362.      * @return exact p-value (or -1) (two-sided, greater, or less using the options)
  363.      */
  364.     // package-private for testing
  365.     static double calculateExactPValue(double u, int m, int n, AlternativeHypothesis alternative) {
  366.         // Check the computation can be attempted.
  367.         // u must be an integer
  368.         if ((int) u != u) {
  369.             return -1;
  370.         }
  371.         // Note: n+m will not overflow as we concatenated the samples to a single array.
  372.         final double binom = BinomialCoefficientDouble.value(n + m, m);
  373.         if (binom == Double.POSITIVE_INFINITY) {
  374.             return -1;
  375.         }

  376.         // Use u_min for the CDF.
  377.         final int u1 = (int) u;
  378.         final int u2 = (int) ((long) m * n - u1);
  379.         // Use m < n to support symmetry.
  380.         final int n1 = Math.min(m, n);
  381.         final int n2 = Math.max(m, n);

  382.         // Return the correct side:
  383.         if (alternative == AlternativeHypothesis.GREATER_THAN) {
  384.             // sf(u1 - 1)
  385.             return sf(u1 - 1, u2 + 1, n1, n2, binom);
  386.         }
  387.         if (alternative == AlternativeHypothesis.LESS_THAN) {
  388.             // cdf(u1)
  389.             return cdf(u1, u2, n1, n2, binom);
  390.         }
  391.         // two-sided: 2 * sf(max(u1, u2) - 1) or 2 * cdf(min(u1, u2))
  392.         final double p = 2 * computeCdf(Math.min(u1, u2), n1, n2, binom);
  393.         // Clip to range: [0, 1]
  394.         return Math.min(1, p);
  395.     }

  396.     /**
  397.      * Compute the cumulative density function of the Mann-Whitney U1 statistic.
  398.      * The U2 statistic is passed for convenience to exploit symmetry in the distribution.
  399.      *
  400.      * @param u1 Mann-Whitney U1 statistic
  401.      * @param u2 Mann-Whitney U2 statistic
  402.      * @param m First sample size.
  403.      * @param n Second sample size.
  404.      * @param binom binom(n+m, m) (must be finite)
  405.      * @return {@code Pr(X <= k)}
  406.      */
  407.     private static double cdf(int u1, int u2, int m, int n, double binom) {
  408.         // Exploit symmetry. Note the distribution is discrete thus requiring (u2 - 1).
  409.         return u2 > u1 ?
  410.             computeCdf(u1, m, n, binom) :
  411.             1 - computeCdf(u2 - 1, m, n, binom);
  412.     }

  413.     /**
  414.      * Compute the survival function of the Mann-Whitney U1 statistic.
  415.      * The U2 statistic is passed for convenience to exploit symmetry in the distribution.
  416.      *
  417.      * @param u1 Mann-Whitney U1 statistic
  418.      * @param u2 Mann-Whitney U2 statistic
  419.      * @param m First sample size.
  420.      * @param n Second sample size.
  421.      * @param binom binom(n+m, m) (must be finite)
  422.      * @return {@code Pr(X > k)}
  423.      */
  424.     private static double sf(int u1, int u2, int m, int n, double binom) {
  425.         // Opposite of the CDF
  426.         return u2 > u1 ?
  427.             1 - computeCdf(u1, m, n, binom) :
  428.             computeCdf(u2 - 1, m, n, binom);
  429.     }

  430.     /**
  431.      * Compute the cumulative density function of the Mann-Whitney U statistic.
  432.      *
  433.      * <p>This should be called with the lower of U1 or U2 for computational efficiency.
  434.      *
  435.      * <p>Uses the recursive formula provided in Bucchianico, A.D, (1999)
  436.      * Combinatorics, computer algebra and the Wilcoxon-Mann-Whitney test, Journal
  437.      * of Statistical Planning and Inference, Volume 79, Issue 2, 349-364.
  438.      *
  439.      * @param k Mann-Whitney U statistic
  440.      * @param m First sample size.
  441.      * @param n Second sample size.
  442.      * @param binom binom(n+m, m) (must be finite)
  443.      * @return {@code Pr(X <= k)}
  444.      */
  445.     private static double computeCdf(int k, int m, int n, double binom) {
  446.         // Theorem 2.5:
  447.         // f(m, n, k) = 0 if k < 0, m < 0, n < 0, k > nm
  448.         if (k < 0) {
  449.             return 0;
  450.         }
  451.         // Recursively compute f(m, n, k)
  452.         final double[][][] f = getF(m, n, k);

  453.         // P(X=k) = f(m, n, k) / binom(m+n, m)
  454.         // P(X<=k) = sum_0^k (P(X=i))

  455.         // Called with k = min(u1, u2) : max(p) ~ 0.5 so no need to clip to [0, 1]
  456.         return IntStream.rangeClosed(0, k).mapToDouble(i -> fmnk(f, m, n, i)).sum() / binom;
  457.     }

  458.     /**
  459.      * Gets the storage for f(m, n, k).
  460.      *
  461.      * <p>This may be cached for performance.
  462.      *
  463.      * @param m M.
  464.      * @param n N.
  465.      * @param k K.
  466.      * @return the storage for f
  467.      */
  468.     private static double[][][] getF(int m, int n, int k) {
  469.         // Obtain any previous computation of f and expand it if required.
  470.         // F is only modified within this synchronized block.
  471.         // Any concurrent threads using a reference returned by this method
  472.         // will not receive an index out-of-bounds as f is only ever expanded.
  473.         synchronized (LOCK) {
  474.             // Note: f(x<m, y<n, z<k) is always the same.
  475.             // Cache the array and re-use any previous computation.
  476.             double[][][] f = cacheF.get();

  477.             // Require:
  478.             // f = new double[m + 1][n + 1][k + 1]
  479.             // f(m, n, 0) == 1; otherwise -1 if not computed
  480.             // m+n <= 1029 for any m,n; k < mn/2 (due to symmetry using min(u1, u2))
  481.             // Size m=n=515: approximately 516^2 * 515^2/2 = 398868 doubles ~ 3.04 GiB
  482.             if (f == null) {
  483.                 f = new double[m + 1][n + 1][k + 1];
  484.                 for (final double[][] a : f) {
  485.                     for (final double[] b : a) {
  486.                         initialize(b);
  487.                     }
  488.                 }
  489.                 // Cache for reuse.
  490.                 cacheF = new SoftReference<>(f);
  491.                 return f;
  492.             }

  493.             // Grow if required: m1 < m+1 => m1-(m+1) < 0 => m1 - m < 1
  494.             final int m1 = f.length;
  495.             final int n1 = f[0].length;
  496.             final int k1 = f[0][0].length;
  497.             final boolean growM = m1 - m < 1;
  498.             final boolean growN = n1 - n < 1;
  499.             final boolean growK = k1 - k < 1;
  500.             if (growM | growN | growK) {
  501.                 // Some part of the previous f is too small.
  502.                 // Atomically grow without destroying the previous computation.
  503.                 // Any other thread using the previous f will not go out of bounds
  504.                 // by keeping the new f dimensions at least as large.
  505.                 // Note: Doing this in-place allows the memory to be gradually
  506.                 // increased rather than allocating a new [m + 1][n + 1][k + 1]
  507.                 // and copying all old values.
  508.                 final int sn = Math.max(n1, n + 1);
  509.                 final int sk = Math.max(k1, k + 1);
  510.                 if (growM) {
  511.                     // Entirely new region
  512.                     f = Arrays.copyOf(f, m + 1);
  513.                     for (int x = m1; x <= m; x++) {
  514.                         f[x] = new double[sn][sk];
  515.                         for (final double[] b : f[x]) {
  516.                             initialize(b);
  517.                         }
  518.                     }
  519.                 }
  520.                 // Expand previous in place if required
  521.                 if (growN) {
  522.                     for (int x = 0; x < m1; x++) {
  523.                         f[x] = Arrays.copyOf(f[x], sn);
  524.                         for (int y = n1; y < sn; y++) {
  525.                             final double[] b = f[x][y] = new double[sk];
  526.                             initialize(b);
  527.                         }
  528.                     }
  529.                 }
  530.                 if (growK) {
  531.                     for (int x = 0; x < m1; x++) {
  532.                         for (int y = 0; y < n1; y++) {
  533.                             final double[] b = f[x][y] = Arrays.copyOf(f[x][y], sk);
  534.                             for (int z = k1; z < sk; z++) {
  535.                                 b[z] = UNSET;
  536.                             }
  537.                         }
  538.                     }
  539.                 }
  540.                 // Avoided an OutOfMemoryError. Cache for reuse.
  541.                 cacheF = new SoftReference<>(f);
  542.             }
  543.             return f;
  544.         }
  545.     }

  546.     /**
  547.      * Initialize the array for f(m, n, x).
  548.      * Set value to 1 for x=0; otherwise {@link #UNSET}.
  549.      *
  550.      * @param fmn Array.
  551.      */
  552.     private static void initialize(double[] fmn) {
  553.         Arrays.fill(fmn, UNSET);
  554.         // f(m, n, 0) == 1 if m >= 0, n >= 0
  555.         fmn[0] = 1;
  556.     }

  557.     /**
  558.      * Compute f(m; n; k), the number of subsets of {0; 1; ...; n} with m elements such
  559.      * that the elements of this subset add up to k.
  560.      *
  561.      * <p>The function is computed recursively.
  562.      *
  563.      * @param f Tabulated values of f[m][n][k].
  564.      * @param m M
  565.      * @param n N
  566.      * @param k K
  567.      * @return f(m; n; k)
  568.      */
  569.     private static double fmnk(double[][][] f, int m, int n, int k) {
  570.         // Theorem 2.5:
  571.         // Omit conditions that will not be met: k > mn
  572.         // f(m, n, k) = 0 if k < 0, m < 0, n < 0
  573.         if ((k | m | n) < 0) {
  574.             return 0;
  575.         }
  576.         // Compute on demand
  577.         double fmnk = f[m][n][k];
  578.         if (fmnk < 0) {
  579.             // f(m, n, 0) == 1 if m >= 0, n >= 0
  580.             // This is already computed.

  581.             // Recursion from formula (3):
  582.             // f(m, n, k) = f(m-1, n, k-n) + f(m, n-1, k)
  583.             f[m][n][k] = fmnk = fmnk(f, m - 1, n, k - n) + fmnk(f, m, n - 1, k);
  584.         }
  585.         return fmnk;
  586.     }
  587. }