Free cookie consent management tool by TermsFeed Policy Generator

source: branches/HeuristicLab.Hive_Milestone3/sources/HeuristicLab.StatisticalAnalysis/3.2/MannWhitneyWilcoxonTest.cs @ 5143

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

Fixed a small bug in the MannWhitneyWilcoxonTest (#573)

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