Free cookie consent management tool by TermsFeed Policy Generator

source: trunk/sources/HeuristicLab.StatisticalAnalysis/MannWhitneyWilcoxonTest.cs @ 1517

Last change on this file since 1517 was 1517, checked in by abeham, 16 years ago

Added some functionality to StatisticalAnalysis by implementing a MannWhitneyWilcoxonTest (a.k.a. MannWhitney U test, Mann Whitney Rank Sum test) as well as a user interface for testing it (#573)

File size: 22.4 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2009 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
4 *
5 * This file is part of HeuristicLab.
6 *
7 * HeuristicLab is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * HeuristicLab is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
19 */
20#endregion
21
22using System;
23using System.Collections.Generic;
24using System.Linq;
25using System.Text;
26using HeuristicLab.Random;
27using HeuristicLab.Core;
28
29namespace HeuristicLab.StatisticalAnalysis {
30  public class MannWhitneyWilcoxonTest : IEditable {
31
32    public IView CreateView() {
33      return CreateEditor();
34    }
35
36    public IEditor CreateEditor() {
37      return new MannWhitneyWilcoxonTestControl();
38    }
39
40    /// <summary>
41    /// Calculates the p-value of a 2-tailed Mann Whitney Wilcoxon U-test (also known as Mann Whitney rank sum test).
42    /// The test performs a ranking of the data and returns the p-value indicating the level of significance
43    /// at which the hypothesis H0 can be rejected.
44    /// Caution: This method approximates the ranks in p1 and p2 with a normal distribution and should only be called
45    /// when the sample size is at least 10 in both samples.
46    ///
47    /// </summary>
48    /// <param name="p1">Array with samples from population 1</param>
49    /// <param name="p2">Array with samples from population 2</param>
50    /// <returns>The p-value of the test</returns>
51    public static double TwoTailedTest(double[] p1, double[] p2) {
52      double rank = MannWhitneyWilcoxonTest.CalculateRankSumForP1(p1, p2);
53      return MannWhitneyWilcoxonTest.ApproximatePValue(rank, p1.Length, p2.Length, true);
54    }
55
56    /// <summary>
57    /// Calculates whether a 2-tailed Mann Whitney Wilcoxon U-test (also known as Mann Whitney rank sum test)
58    /// would reject the hypothesis H0 that the given two populations stem from the same distribution.
59    /// The alternative hypothesis would be that they come from different distributions.
60    /// The test performs a ranking of the data and decides based on the ranking whehter to reject the
61    /// hypothesis or not.
62    ///
63    /// If there are less than 20 samples in each population this decision is based on a table lookup.
64    /// If one array consists of more than 20 samples, it will approximate the distribution by a normal
65    /// distribution. In the case of the table lookup, alpha is restricted to 0.01, 0.05, and 0.1.
66    /// </summary>
67    /// <param name="p1">Array with samples from population 1</param>
68    /// <param name="p2">Array with samples from population 2</param>
69    /// <param name="alpha">The significance level. If p1 and p2 are both smaller or equal than 20, the decision is based on three tables that represent significance at 0.01, 0.05, and 0.1.</param>
70    /// <returns>True if H0 (p1 equals p2) can be rejected, False otherwise</returns>
71    public static bool TwoTailedTest(double[] p1, double[] p2, double alpha) {
72      return MannWhitneyWilcoxonTest.TwoTailedTest(p1, p2, alpha, 20);
73    }
74
75    /// <summary>
76    /// Calculates whether a 2-tailed Mann Whitney Wilcoxon U-test (also known as Mann Whitney rank sum test)
77    /// would reject the hypothesis H0 that the given two populations stem from the same distribution.
78    /// The alternative hypothesis would be that they come from different distributions.
79    /// The test performs a ranking of the data and decides based on the ranking whehter to reject the
80    /// hypothesis or not.
81    ///
82    /// If there are less than <paramref name="approximationLevel"/> samples in each population this
83    /// decision is based on a table lookup.
84    /// If one array consists of more than <paramref name="approximationLevel"/> samples, it will
85    /// approximate the distribution by a normal distribution. In the case of the table lookup,
86    /// alpha is restricted to 0.01, 0.05, and 0.1.
87    /// </summary>
88    /// <param name="p1">Array with samples from population 1</param>
89    /// <param name="p2">Array with samples from population 2</param>
90    /// <param name="alpha">The significance level. If p1 and p2 are both smaller or equal than 20,
91    /// the decision is based on three tables that represent significance at 0.01, 0.05, and 0.1.</param>
92    /// <param name="approximationLevel">Defines at which sample size to use normal approximation,
93    /// instead of the table lookup. If a higher value than 20 is specified, the value 20 will be
94    /// used instead, if 0 is specified the result will be compuated always by approximation.</param>
95    /// <returns>True if H0 (p1 equals p2) can be rejected, False otherwise</returns>
96    public static bool TwoTailedTest(double[] p1, double[] p2, double alpha, int approximationLevel) {
97      Array.Sort<double>(p1);
98      Array.Sort<double>(p2);
99      int n1 = p1.Length, n2 = p2.Length;
100
101      double R1 = MannWhitneyWilcoxonTest.CalculateRankSumForP1(p1, p2);
102
103      if (n1 <= Math.Min(approximationLevel, 20) && n2 <= Math.Min(approximationLevel, 20)) {
104        return MannWhitneyWilcoxonTest.AbsolutePValue(R1, n1, n2, alpha, true);
105      } else { // normal approximation
106        return MannWhitneyWilcoxonTest.ApproximatePValue(R1, n1, n2, true) <= alpha;
107      }
108    }
109
110    /// <summary>
111    /// Calculates the p-value of a 2-tailed Mann Whitney Wilcoxon U-test (also known as Mann Whitney rank sum test).
112    /// The test performs a ranking of the data and returns the p-value indicating the level of significance
113    /// at which the hypothesis H0 can be rejected.
114    /// Caution: This method approximates the ranks in p1 and p2 with a normal distribution and should only be called
115    /// when the sample size is at least 10 in both samples.
116    ///
117    /// </summary>
118    /// <param name="p1">Array with samples from population 1</param>
119    /// <param name="p2">Array with samples from population 2</param>
120    /// <returns>The p-value of the test</returns>
121    public static double TwoTailedTest(int[] p1, int[] p2) {
122      double rank = MannWhitneyWilcoxonTest.CalculateRankSumForP1(p1, p2);
123      return MannWhitneyWilcoxonTest.ApproximatePValue(rank, p1.Length, p2.Length, true);
124    }
125
126    /// <summary>
127    /// Calculates whether a 2-tailed Mann Whitney Wilcoxon U-test (also known as Mann Whitney rank sum test)
128    /// would reject the hypothesis H0 that the given two populations stem from the same distribution.
129    /// The alternative hypothesis would be that they come from different distributions.
130    /// The test performs a ranking of the data and decides based on the ranking whehter to reject the
131    /// hypothesis or not.
132    ///
133    /// If there are less than 20 samples in each population this decision is based on a table lookup.
134    /// If one array consists of more than 20 samples, it will approximate the distribution by a normal
135    /// distribution. In the case of the table lookup, alpha is restricted to 0.01, 0.05, and 0.1.
136    /// </summary>
137    /// <param name="p1">Array with samples from population 1</param>
138    /// <param name="p2">Array with samples from population 2</param>
139    /// <param name="alpha">The significance level. If p1 and p2 are both smaller or equal than 20, the decision is based on three tables that represent significance at 0.01, 0.05, and 0.1.</param>
140    /// <returns>True if H0 (p1 equals p2) can be rejected, False otherwise</returns>
141    public static bool TwoTailedTest(int[] p1, int[] p2, double alpha) {
142      return MannWhitneyWilcoxonTest.TwoTailedTest(p1, p2, alpha, 20);
143    }
144
145    /// <summary>
146    /// Calculates whether a 2-tailed Mann Whitney Wilcoxon U-test (also known as Mann Whitney rank sum test)
147    /// would reject the hypothesis H0 that the given two populations stem from the same distribution.
148    /// The alternative hypothesis would be that they come from different distributions.
149    /// The test performs a ranking of the data and decides based on the ranking whehter to reject the
150    /// hypothesis or not.
151    ///
152    /// If there are less than <paramref name="approximationLevel"/> samples in each population this
153    /// decision is based on a table lookup.
154    /// If one array consists of more than <paramref name="approximationLevel"/> samples, it will
155    /// approximate the distribution by a normal distribution. In the case of the table lookup,
156    /// alpha is restricted to 0.01, 0.05, and 0.1.
157    /// </summary>
158    /// <param name="p1">Array with samples from population 1</param>
159    /// <param name="p2">Array with samples from population 2</param>
160    /// <param name="alpha">The significance level. If p1 and p2 are both smaller or equal than 20,
161    /// the decision is based on three tables that represent significance at 0.01, 0.05, and 0.1.</param>
162    /// <param name="approximationLevel">Defines at which sample size to use normal approximation,
163    /// instead of the table lookup. If a higher value than 20 is specified, the value 20 will be
164    /// used instead, if 0 is specified the result will be compuated always by approximation.</param>
165    /// <returns>True if H0 (p1 equals p2) can be rejected, False otherwise</returns>
166    public static bool TwoTailedTest(int[] p1, int[] p2, double alpha, int approximationLevel) {
167      Array.Sort<int>(p1);
168      Array.Sort<int>(p2);
169      int n1 = p1.Length, n2 = p2.Length;
170     
171      double R1 = MannWhitneyWilcoxonTest.CalculateRankSumForP1(p1, p2);
172
173      if (n1 <= Math.Min(approximationLevel, 20) && n2 <= Math.Min(approximationLevel, 20)) {
174        return MannWhitneyWilcoxonTest.AbsolutePValue(R1, n1, n2, alpha, true);
175      } else {
176        return MannWhitneyWilcoxonTest.ApproximatePValue(R1, n1, n2, true) <= alpha;
177      }
178    }
179
180    private static bool AbsolutePValue(double rank, int nRank, int nOther, double alpha, bool twoTailed) {
181      double U1 = rank - (double)(nRank * (nRank + 1) / 2);
182      double U2 = (double)(nRank * nOther) - U1;
183      if (alpha <= 0.01) {
184        return (Math.Min(U1, U2) <= table001[nRank - 1, nOther - 1]);
185      } else if (alpha <= 0.05) {
186        return (Math.Min(U1, U2) <= table005[nRank - 1, nOther - 1]);
187      } else if (alpha <= 0.1) {
188        return (Math.Min(U1, U2) <= table01[nRank - 1, nOther - 1]);
189      } else throw new ArgumentException("ERROR in MannWhitneyWilcoxonTest: alpha must be <= 0.1");
190    }
191
192    private static double ApproximatePValue(double rank, int nRank, int nOther, bool twoTailed) {
193      double U1 = rank - (double)(nRank * (nRank + 1) / 2);
194      double U2 = (double)(nRank * nOther) - U1;
195
196      double mu = nRank * nOther / 2;
197      double sigma = Math.Sqrt(nRank * nOther * (nRank + nOther + 1) / 12);
198      double z = (Math.Min(U1, U2) - mu) / sigma;
199      return ProbabilityOfZValue(z);
200    }
201
202    // FIXME: when there's equality among some samples within a population the rank also needs to be averaged
203    private static double CalculateRankSumForP1(int[] p1, int[] p2) {
204      int rank = 0, p2Idx = 0;
205      int n1 = p1.Length, n2 = p2.Length;
206      double R1 = 0;
207
208      for (int i = 0; i < n1; i++) {
209        rank++;
210        if (p1[i] < p2[p2Idx]) {
211          R1 += rank;
212        } if (p1[i] == p2[p2Idx]) {
213          int startRank = rank;
214          rank++;
215          int commonRank = startRank + rank;
216          int starti = i;
217          while (i + 1 < n1 && Math.Abs(p1[i + 1] - p2[p2Idx]) < 1e-07) {
218            i++;
219            rank++;
220            commonRank += rank;
221          }
222          while (p2Idx + 1 < n2 && Math.Abs(p1[i] - p2[p2Idx + 1]) < 1e-07) {
223            p2Idx++;
224            rank++;
225            commonRank += rank;
226          }
227          p2Idx++;
228          R1 += (i - starti + 1) * commonRank / (rank - startRank + 1);
229        } else {
230          p2Idx++;
231          i--;
232        }
233        if (p2Idx == n2) {
234          i++; rank++; // calculate the rest of the ranks for p1
235          while (i < n1) {
236            R1 += rank;
237            rank++;
238            i++;
239          }
240          break;
241        }
242      }
243      return R1;
244    }
245
246    private static double CalculateRankSumForP2(int[] p1, int[] p2) {
247      int n1 = p1.Length, n2 = p2.Length;
248      double completeRankSum = (double)((n1 + n2 + 1) * (n1 + n2)) / 2.0;
249      return completeRankSum - CalculateRankSumForP1(p1, p2);
250    }
251
252    // FIXME: when there's equality among some samples within a population the rank also needs to be averaged
253    private static double CalculateRankSumForP1(double[] p1, double[] p2) {
254      int rank = 0, n1 = p1.Length, n2 = p2.Length, p2Idx = 0;
255      double R1 = 0;
256
257      for (int i = 0; i < n1; i++) {
258        rank++;
259        if (Math.Abs(p1[i] - p2[p2Idx]) < 1e-07) {
260          int startRank = rank;
261          rank++;
262          int commonRank = startRank + rank;
263          int starti = i;
264          while (i + 1 < n1 && Math.Abs(p1[i + 1] - p2[p2Idx]) < 1e-07) {
265            i++;
266            rank++;
267            commonRank += rank;
268          }
269          while (p2Idx + 1 < n2 && Math.Abs(p1[i] - p2[p2Idx + 1]) < 1e-07) {
270            p2Idx++;
271            rank++;
272            commonRank += rank;
273          }
274          p2Idx++;
275          R1 += (i - starti + 1) * commonRank / (rank - startRank + 1);
276        } else if (p1[i] < p2[p2Idx]) {
277          R1 += rank;
278        } else {
279          p2Idx++;
280          i--;
281        }
282        if (p2Idx == n2) {
283          i++; rank++; // calculate the rest of the ranks for p1
284          while (i < n1) {
285            R1 += rank;
286            rank++;
287            i++;
288          }
289          break;
290        }
291      }
292      return R1;
293    }
294
295    private static double CalculateRankSumForP2(double[] p1, double[] p2) {
296      int n1 = p1.Length, n2 = p2.Length;
297      double completeRankSum = (double)((n1 + n2 + 1) * (n1 + n2)) / 2.0;
298      return completeRankSum - CalculateRankSumForP1(p1, p2);
299    }
300
301    // taken from: http://www.fourmilab.ch/rpkp/experiments/analysis/zCalc.html (in public domain)
302    private static double ProbabilityOfZValue(double z) {
303      double y, x, w, zMax = 6.0;
304      if (Math.Abs(z) < 1e-07) {
305        x = 0.0;
306      } else {
307        y = 0.5 * Math.Abs(z);
308        if (y > (zMax * 0.5)) {
309          x = 1.0;
310        } else if (y < 1.0) {
311          w = y * y;
312          x = ((((((((0.000124818987 * w
313                   - 0.001075204047) * w + 0.005198775019) * w
314                   - 0.019198292004) * w + 0.059054035642) * w
315                   - 0.151968751364) * w + 0.319152932694) * w
316                   - 0.531923007300) * w + 0.797884560593) * y * 2.0;
317        } else {
318          y -= 2.0;
319          x = (((((((((((((-0.000045255659 * y
320                         + 0.000152529290) * y - 0.000019538132) * y
321                         - 0.000676904986) * y + 0.001390604284) * y
322                         - 0.000794620820) * y - 0.002034254874) * y
323                         + 0.006549791214) * y - 0.010557625006) * y
324                         + 0.011630447319) * y - 0.009279453341) * y
325                         + 0.005353579108) * y - 0.002141268741) * y
326                         + 0.000535310849) * y + 0.999936657524;
327        }
328      }
329      return (z > 0.0) ? ((x + 1.0) * 0.5) : ((1.0 - x) * 0.5);
330    }
331
332    #region probability tables
333    // table stems from
334    // Roy C. Milton. An Extended Table of Critical Values for the Mann-Whitney (Wilcoxon) Two-Sample Statistic. Journal of the American Statistical Association, Vol. 59, No. 307 (Sep., 1964), pp. 925-934.
335    private static int[,] table01 = new int[20, 20] {
336      /*         1   2   3   4   5   6   7   8   9  10  11  12  13  14   15   16   17   18   19   20*/
337      /*  1 */{ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  -1,  -1,  -1,  -1,   0,   0},
338      /*  2 */{ -1, -1, -1, -1,  0,  0,  0,  1,  1,  1,  1,  2,  2,  3,   3,   3,   3,   4,   4,   4},
339      /*  3 */{ -1, -1,  0,  0,  1,  2,  2,  3,  4,  4,  5,  5,  6,  7,   7,   8,   9,   9,  10,  11},
340      /*  4 */{ -1, -1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11,  12,  14,  15,  16,  17,  18},
341      /*  5 */{ -1,  0,  1,  2,  4,  5,  6,  8,  9, 11, 12, 13, 15, 16,  18,  19,  20,  22,  23,  25},
342      /*  6 */{ -1,  0,  2,  3,  5,  7,  8, 10, 12, 14, 16, 17, 19, 21,  23,  25,  26,  28,  30,  32},
343      /*  7 */{ -1,  0,  2,  4,  6,  8, 11, 13, 15, 17, 19, 21, 24, 26,  28,  30,  33,  35,  37,  39},
344      /*  8 */{ -1,  1,  3,  5,  8, 10, 13, 15, 18, 20, 23, 26, 28, 31,  33,  36,  39,  41,  44,  47},
345      /*  9 */{ -1,  1,  4,  6,  9, 12, 15, 18, 21, 24, 27, 30, 33, 36,  39,  42,  45,  48,  51,  54},
346      /* 10 */{ -1,  1,  4,  7, 11, 14, 17, 20, 24, 27, 31, 34, 37, 41,  44,  48,  51,  55,  58,  62},
347      /* 11 */{ -1,  1,  5,  8, 12, 16, 19, 23, 27, 31, 34, 38, 42, 46,  50,  54,  57,  61,  65,  69},
348      /* 12 */{ -1,  2,  5,  9, 13, 17, 21, 26, 30, 34, 38, 42, 47, 51,  55,  60,  64,  68,  72,  77},
349      /* 13 */{ -1,  2,  6, 10, 15, 19, 24, 28, 33, 37, 42, 47, 51, 56,  61,  65,  70,  75,  80,  84},
350      /* 14 */{ -1,  3,  7, 11, 16, 21, 26, 31, 36, 41, 46, 51, 56, 61,  66,  71,  77,  82,  87,  92},
351      /* 15 */{ -1,  3,  7, 12, 18, 23, 28, 33, 39, 44, 50, 55, 61, 66,  72,  77,  83,  88,  94, 100},
352      /* 16 */{ -1,  3,  8, 14, 19, 25, 30, 36, 42, 48, 54, 60, 65, 71,  77,  83,  89,  95, 101, 107},
353      /* 17 */{ -1,  3,  9, 15, 20, 26, 33, 39, 45, 51, 57, 64, 70, 77,  83,  89,  96, 102, 109, 115},
354      /* 18 */{ -1,  4,  9, 16, 22, 28, 35, 41, 48, 55, 61, 68, 75, 82,  88,  95, 102, 109, 116, 123},
355      /* 19 */{  0,  4, 10, 17, 23, 30, 37, 44, 51, 58, 65, 72, 80, 87,  94, 101, 109, 116, 123, 130},
356      /* 20 */{  0,  4, 11, 18, 25, 32, 39, 47, 54, 62, 69, 77, 84, 92, 100, 107, 115, 123, 130, 138}};
357
358    // table stems from http://math.usask.ca/~laverty/S245/Tables/wmw.pdf
359    private static int[,] table005 = new int[20, 20] {
360      /*         1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16   17   18   19   20*/
361      /*  1 */{ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  -1,  -1,  -1,  -1},
362      /*  2 */{ -1, -1, -1, -1, -1, -1, -1,  0,  0,  0,  0,  1,  1,  1,  1,  1,   2,   2,   2,   2},
363      /*  3 */{ -1, -1, -1, -1,  0,  1,  1,  2,  2,  3,  3,  4,  4,  5,  5,  6,   6,   7,   7,   8},
364      /*  4 */{ -1, -1, -1,  0,  1,  2,  3,  4,  4,  5,  6,  7,  8,  9, 10, 11,  11,  12,  13,  13},
365      /*  5 */{ -1, -1,  0,  1,  2,  3,  5,  6,  7,  8,  9, 11, 12, 13, 14, 15,  17,  18,  19,  20},
366      /*  6 */{ -1, -1,  1,  2,  3,  5,  6,  8, 10, 11, 13, 14, 16, 17, 19, 21,  22,  24,  25,  27},
367      /*  7 */{ -1, -1,  1,  3,  5,  6,  8, 10, 12, 14, 16, 18, 20, 22, 24, 26,  28,  30,  32,  34},
368      /*  8 */{ -1,  0,  2,  4,  6,  8, 10, 13, 15, 17, 19, 22, 24, 26, 29, 31,  34,  36,  38,  41},
369      /*  9 */{ -1,  0,  2,  4,  7, 10, 12, 15, 17, 21, 23, 26, 28, 31, 34, 37,  39,  42,  45,  48},
370      /* 10 */{ -1,  0,  3,  5,  8, 11, 14, 17, 20, 23, 26, 29, 33, 36, 39, 42,  45,  48,  52,  55},
371      /* 11 */{ -1,  0,  3,  6,  9, 13, 16, 19, 23, 26, 30, 33, 37, 40, 44, 47,  51,  55,  58,  62},
372      /* 12 */{ -1,  1,  4,  7, 11, 14, 18, 22, 26, 29, 33, 37, 41, 45, 49, 53,  57,  61,  65,  69},
373      /* 13 */{ -1,  1,  4,  8, 12, 16, 20, 24, 28, 33, 37, 41, 45, 50, 54, 59,  63,  67,  72,  76},
374      /* 14 */{ -1,  1,  5,  9, 13, 17, 22, 26, 31, 36, 40, 45, 50, 55, 59, 64,  67,  74,  78,  83},
375      /* 15 */{ -1,  1,  5, 10, 14, 19, 24, 29, 34, 39, 44, 49, 54, 59, 64, 70,  75,  80,  85,  90},
376      /* 16 */{ -1,  1,  6, 11, 15, 21, 26, 31, 37, 42, 47, 53, 59, 64, 70, 75,  81,  86,  92,  98},
377      /* 17 */{ -1,  2,  6, 11, 17, 22, 28, 34, 39, 45, 51, 57, 63, 67, 75, 81,  87,  93,  99, 105},
378      /* 18 */{ -1,  2,  7, 12, 18, 24, 30, 36, 42, 48, 55, 61, 67, 74, 80, 86,  93,  99, 106, 112},
379      /* 19 */{ -1,  2,  7, 13, 19, 25, 32, 38, 45, 52, 58, 65, 72, 78, 85, 92,  99, 106, 113, 119},
380      /* 20 */{ -1,  2,  8, 14, 20, 27, 34, 41, 48, 55, 62, 69, 76, 83, 90, 98, 105, 112, 119, 127}};
381
382    // table from: http://math.usask.ca/~laverty/S245/Tables/wmw.pdf
383    private static int[,] table001 = new int[20, 20] {
384      /*         1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18  19   20*/
385      /*  1 */{ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  -1},
386      /*  2 */{ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  0,   0},
387      /*  3 */{ -1, -1, -1, -1, -1, -1, -1, -1,  0,  0,  0,  1,  1,  1,  2,  2,  2,  2,  3,   3},
388      /*  4 */{ -1, -1, -1, -1, -1,  0,  0,  1,  1,  2,  2,  3,  3,  4,  5,  5,  6,  6,  7,   8},
389      /*  5 */{ -1, -1, -1, -1,  0,  1,  1,  2,  3,  4,  5,  6,  7,  7,  8,  9, 10, 11, 12,  13},
390      /*  6 */{ -1, -1, -1,  0,  1,  2,  3,  4,  5,  6,  7,  9, 10, 11, 12, 13, 15, 16, 17,  18},
391      /*  7 */{ -1, -1, -1,  0,  1,  3,  4,  6,  7,  9, 10, 12, 13, 15, 16, 18, 19, 21, 22,  24},
392      /*  8 */{ -1, -1, -1,  1,  2,  4,  6,  7,  9, 11, 13, 15, 17, 18, 20, 22, 24, 26, 28,  30},
393      /*  9 */{ -1, -1,  0,  1,  3,  5,  7,  9, 11, 13, 16, 18, 20, 22, 24, 27, 29, 31, 33,  36},
394      /* 10 */{ -1, -1,  0,  2,  4,  6,  9, 11, 13, 16, 18, 21, 24, 26, 29, 31, 34, 37, 39,  42},
395      /* 11 */{ -1, -1,  0,  2,  5,  7, 10, 13, 16, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45,  46},
396      /* 12 */{ -1, -1,  1,  3,  6,  9, 12, 15, 18, 21, 24, 27, 31, 34, 37, 41, 44, 47, 51,  54},
397      /* 13 */{ -1, -1,  1,  3,  7, 10, 13, 17, 20, 24, 27, 31, 34, 38, 42, 45, 49, 53, 56,  60},
398      /* 14 */{ -1, -1,  1,  4,  7, 11, 15, 18, 22, 26, 30, 34, 38, 42, 46, 50, 54, 58, 63,  67},
399      /* 15 */{ -1, -1,  2,  5,  8, 12, 16, 20, 24, 29, 33, 37, 42, 46, 51, 55, 60, 64, 69,  73},
400      /* 16 */{ -1, -1,  2,  5,  9, 13, 18, 22, 27, 31, 36, 41, 45, 50, 55, 60, 65, 70, 74,  79},
401      /* 17 */{ -1, -1,  2,  6, 10, 15, 19, 24, 29, 34, 39, 44, 49, 54, 60, 65, 70, 75, 81,  86},
402      /* 18 */{ -1, -1,  2,  6, 11, 16, 21, 26, 31, 37, 42, 47, 53, 58, 64, 70, 75, 81, 87,  92},
403      /* 19 */{ -1,  0,  3,  7, 12, 17, 22, 28, 33, 39, 45, 51, 56, 63, 69, 74, 81, 87, 93,  99},
404      /* 20 */{ -1,  0,  3,  8, 13, 18, 24, 30, 36, 42, 46, 54, 60, 67, 73, 79, 86, 92, 99, 105}};
405    #endregion
406
407  }
408}
Note: See TracBrowser for help on using the repository browser.