]> git.sesse.net Git - foosball/blob - foosrank.cpp
Parametrize initial rating/RD instead of hardcoding it.
[foosball] / foosrank.cpp
1 #include <cstdio>
2 #include <cmath>
3 #include <cassert>
4
5 #include <vector>
6 #include <algorithm>
7
8 #include <complex>
9 #include <fftw3.h>
10
11 #define USE_LOGISTIC_DISTRIBUTION 0
12
13 // step sizes
14 static const double int_step_size = 75.0;
15
16 // rating constant (see below)
17 static const double rating_constant = 455.0;
18
19 using namespace std;
20
21 static double prob_score_real(int k, int a, double binomial, double rd_norm);
22 static double prodai(int k, int a);
23 static double fac(int x);
24
25 #if USE_LOGISTIC_DISTRIBUTION
26 // sech²(x)
27 static double sech2(double x)
28 {
29         double e = exp(2.0 * x);
30         return 4.0 * e / ((e+1.0) * (e+1.0));
31 }
32 #endif
33
34 #if 0
35 // probability of match ending k-a (k>a) when winnerR - loserR = RD
36 //
37 //   +inf  
38 //     / 
39 //    |
40 //    | Poisson[lambda1, t](a) * Erlang[lambda2, k](t) dt
41 //    |
42 //   /
43 // -inf
44 //
45 // where lambda1 = 1.0, lambda2 = 2^(rd/455)
46 //
47 // The constant of 455 is chosen carefully so to match with the
48 // Glicko/Bradley-Terry assumption that a player rated 400 points over
49 // his/her opponent will win with a probability of 10/11 =~ 0.90909. 
50 //
51 static double prob_score(int k, int a, double rd)
52 {
53         return prob_score_real(k, a, prodai(k, a) / fac(k-1), rd/rating_constant);
54 }
55 #endif
56
57 // computes x^a, probably more efficiently than pow(x, a) (but requires that a
58 // is n unsigned integer)
59 static double intpow(double x, unsigned a)
60 {
61         double result = 1.0;
62
63         while (a > 0) {
64                 if (a & 1) {
65                         result *= x;
66                 }
67                 a >>= 1;
68                 x *= x;
69         }
70
71         return result;
72 }
73
74 // Same, but takes in binomial(a+k-1, k-1) as an argument in
75 // addition to a. Faster if you already have that precomputed, and assumes rd
76 // is already divided by 455.
77 static double prob_score_real(int k, int a, double binomial, double rd_norm)
78 {
79         double nom = binomial * intpow(pow(2.0, rd_norm), a); 
80         double denom = intpow(1.0 + pow(2.0, rd_norm), k+a);
81         return nom/denom;
82 }
83
84 // Calculates Product(a+i, i=1..k-1) (see above).
85 static double prodai(int k, int a)
86 {
87         double prod = 1.0;
88         for (int i = 1; i < k; ++i)
89                 prod *= (a+i);
90         return prod;
91 }
92
93 static double fac(int x)
94 {
95         double prod = 1.0;
96         for (int i = 2; i <= x; ++i)
97                 prod *= i;
98         return prod;
99 }
100
101 static void compute_opponent_rating_pdf(int k, int a, double mu2, double sigma2, double winfac, vector<pair<double, double> > &result)
102 {
103         double binomial_precompute = prodai(k, a) / fac(k-1);
104         winfac /= rating_constant;
105
106         int sz = (6000.0 - 0.0) / int_step_size;
107         double h = (6000.0 - 0.0) / sz;
108
109         fftw_plan f1, f2, b;
110         complex<double> *func1, *func2, *res;
111
112         func1 = reinterpret_cast<complex<double> *>(fftw_malloc(sz*2*sizeof(complex<double>)));
113         func2 = reinterpret_cast<complex<double> *>(fftw_malloc(sz*2*sizeof(complex<double>)));
114         res = reinterpret_cast<complex<double> *>(fftw_malloc(sz*2*sizeof(complex<double>)));
115         f1 = fftw_plan_dft_1d(sz*2,
116                 reinterpret_cast<fftw_complex*>(func1),
117                 reinterpret_cast<fftw_complex*>(func1),
118                 FFTW_FORWARD,
119                 FFTW_MEASURE);
120         f2 = fftw_plan_dft_1d(sz*2,
121                 reinterpret_cast<fftw_complex*>(func2),
122                 reinterpret_cast<fftw_complex*>(func2),
123                 FFTW_FORWARD,
124                 FFTW_MEASURE);
125         b = fftw_plan_dft_1d(sz*2,
126                 reinterpret_cast<fftw_complex*>(res),
127                 reinterpret_cast<fftw_complex*>(res),
128                 FFTW_BACKWARD,
129                 FFTW_MEASURE);
130         
131         // start off by zero
132         for (int i = 0; i < sz*2; ++i) {
133                 func1[i].real() = func1[i].imag() = func2[i].real() = func2[i].imag() = 0.0;
134         }
135
136 #if USE_LOGISTIC_DISTRIBUTION
137         double invsigma2 = 1.0 / sigma2;
138 #else
139         double invsq2sigma2 = 1.0 / (sqrt(2.0) * sigma2);
140 #endif
141         for (int i = 0; i < sz; ++i) {
142                 double x1 = 0.0 + h*i;
143
144                 // opponent's pdf
145 #if USE_LOGISTIC_DISTRIBUTION
146                 double z = (x1 - mu2) * invsigma2;
147                 func1[i].real() = sech2(0.5 * z);
148 #else
149                 double z = (x1 - mu2) * invsq2sigma2;
150                 func1[i].real() = exp(-z*z);
151 #endif
152
153                 double x2 = -3000.0 + h*i;
154                 func2[(i - sz/2 + sz*2)%(sz*2)].real() = prob_score_real(k, a, binomial_precompute, x2*winfac);
155         }
156
157         result.reserve(sz*2);
158
159         // convolve
160         fftw_execute(f1);
161         fftw_execute(f2);
162         for (int i = 0; i < sz*2; ++i) {
163                 res[i] = func1[i] * func2[i];
164         }
165         fftw_execute(b);
166
167         result.reserve(sz);
168         for (int i = 0; i < sz; ++i) {
169                 double r1 = i*h;
170                 result.push_back(make_pair(r1, abs(res[i])));
171         }
172 }
173
174 // normalize the curve so we know that A ~= 1
175 static void normalize(vector<pair<double, double> > &curve)
176 {
177         double peak = 0.0;
178         for (vector<pair<double, double> >::const_iterator i = curve.begin(); i != curve.end(); ++i) {
179                 peak = max(peak, i->second);
180         }
181
182         double invpeak = 1.0 / peak;
183         for (vector<pair<double, double> >::iterator i = curve.begin(); i != curve.end(); ++i) {
184                 i->second *= invpeak;
185         }
186 }
187
188 // computes matA^T * matB
189 static void mat_mul_trans(double *matA, unsigned ah, unsigned aw,
190                           double *matB, unsigned bh, unsigned bw,
191                           double *result)
192 {
193         assert(ah == bh);
194         for (unsigned y = 0; y < bw; ++y) {
195                 for (unsigned x = 0; x < aw; ++x) {
196                         double sum = 0.0;
197                         for (unsigned c = 0; c < ah; ++c) {
198                                 sum += matA[x*ah + c] * matB[y*bh + c];
199                         }
200                         result[y*bw + x] = sum;
201                 }
202         }
203 }
204
205 // solves Ax = B by Gauss-Jordan elimination, where A is an NxN matrix,
206 // x is a column vector of length N and B is a row vector of length N.
207 // Destroys its input in the process.
208 template<int N>
209 static void solve_matrix(double *A, double *x, double *B)
210 {
211         for (int i = 0; i < N; ++i) {
212                 for (int j = i+1; j < N; ++j) {
213                         // row j -= row i * (a[i,j] / a[i,i])
214                         double f = A[j+i*N] / A[i+i*N];
215
216                         A[j+i*N] = 0.0;
217                         for (int k = i+1; k < N; ++k) {
218                                 A[j+k*N] -= A[i+k*N] * f;
219                         }
220
221                         B[j] -= B[i] * f;
222                 }
223         }
224
225         // back-substitute
226         for (int i = N; i --> 0; ) {
227                 for (int j = i; j --> 0; ) {
228                         // row j -= row i * (a[j,j] / a[j,i])
229                         double f = A[i+j*N] / A[j+j*N];
230                         
231                         // A[j+i*N] = 0.0;
232                         B[j] -= B[i] * f;
233                 }
234         }
235
236         // normalize
237         for (int i = 0; i < N; ++i) {
238                 x[i] = B[i] / A[i+i*N];
239         }
240 }
241
242 // Give an OK starting estimate for the least squares, by numerical integration
243 // of statistical moments.
244 static void estimate_musigma(vector<pair<double, double> > &curve, double &mu_result, double &sigma_result)
245 {
246         double h = (curve.back().first - curve.front().first) / (curve.size() - 1);
247
248         double area = curve.front().second;
249         double ex = curve.front().first * curve.front().second;
250         double ex2 = curve.front().first * curve.front().first * curve.front().second;
251
252         for (unsigned i = 1; i < curve.size() - 1; i += 2) {
253                 double x = curve[i].first;
254                 double y = curve[i].second;
255                 area += 4.0 * y;
256                 ex += 4.0 * x * y;
257                 ex2 += 4.0 * x * x * y;
258         }
259         for (unsigned i = 2; i < curve.size() - 1; i += 2) {
260                 double x = curve[i].first;
261                 double y = curve[i].second;
262                 area += 2.0 * y;
263                 ex += 2.0 * x * y;
264                 ex2 += 2.0 * x * x * y;
265         }
266         
267         area += curve.back().second;
268         ex += curve.back().first * curve.back().second;
269         ex2 += curve.back().first * curve.back().first * curve.back().second;
270
271         area = (h/3.0) * area;
272         ex = (h/3.0) * ex / area;
273         ex2 = (h/3.0) * ex2 / area;
274
275         mu_result = ex;
276         sigma_result = sqrt(ex2 - ex * ex);
277 }
278         
279 // Find best fit of the data in curves to a Gaussian pdf, based on the
280 // given initial estimates. Works by nonlinear least squares, iterating
281 // until we're below a certain threshold.
282 //
283 // Note that the algorithm blows up quite hard if the initial estimate is
284 // not good enough. Use estimate_musigma to get a reasonable starting
285 // estimate.
286 static void least_squares(vector<pair<double, double> > &curve, double mu1, double sigma1, double &mu_result, double &sigma_result)
287 {
288         double A = 1.0;
289         double mu = mu1;
290         double sigma = sigma1;
291
292         // column-major
293         double matA[curve.size() * 3];  // N x 3
294         double dbeta[curve.size()];     // N x 1
295
296         // A^T * A: 3xN * Nx3 = 3x3
297         double matATA[3*3];
298
299         // A^T * dβ: 3xN * Nx1 = 3x1
300         double matATdb[3];
301
302         double dlambda[3];
303
304         for ( ;; ) {
305                 //printf("A=%f mu=%f sigma=%f\n", A, mu, sigma);
306
307                 // fill in A (depends only on x_i, A, mu, sigma -- not y_i)
308                 for (unsigned i = 0; i < curve.size(); ++i) {
309                         double x = curve[i].first;
310
311 #if USE_LOGISTIC_DISTRIBUTION
312                         // df/dA(x_i)
313                         matA[i + 0 * curve.size()] = sech2(0.5 * (x-mu)/sigma);
314
315                         // df/dµ(x_i)
316                         matA[i + 1 * curve.size()] = A * matA[i + 0 * curve.size()]
317                                 * tanh(0.5 * (x-mu)/sigma) / sigma;
318
319                         // df/dσ(x_i)
320                         matA[i + 2 * curve.size()] = 
321                                 matA[i + 1 * curve.size()] * (x-mu)/sigma;
322 #else
323                         // df/dA(x_i)
324                         matA[i + 0 * curve.size()] = 
325                                 exp(-(x-mu)*(x-mu)/(2.0*sigma*sigma));
326
327                         // df/dµ(x_i)
328                         matA[i + 1 * curve.size()] =
329                                 A * (x-mu)/(sigma*sigma) * matA[i + 0 * curve.size()];
330
331                         // df/dσ(x_i)
332                         matA[i + 2 * curve.size()] = 
333                                 matA[i + 1 * curve.size()] * (x-mu)/sigma;
334 #endif
335                 }
336
337                 // find dβ
338                 for (unsigned i = 0; i < curve.size(); ++i) {
339                         double x = curve[i].first;
340                         double y = curve[i].second;
341
342 #if USE_LOGISTIC_DISTRIBUTION
343                         dbeta[i] = y - A * sech2(0.5 * (x-mu)/sigma);
344 #else
345                         dbeta[i] = y - A * exp(- (x-mu)*(x-mu)/(2.0*sigma*sigma));
346 #endif
347                 }
348
349                 // compute a and b
350                 mat_mul_trans(matA, curve.size(), 3, matA, curve.size(), 3, matATA);
351                 mat_mul_trans(matA, curve.size(), 3, dbeta, curve.size(), 1, matATdb);
352
353                 // solve
354                 solve_matrix<3>(matATA, dlambda, matATdb);
355
356                 A += dlambda[0];
357                 mu += dlambda[1];
358                 sigma += dlambda[2];
359
360                 // terminate when we're down to three digits
361                 if (fabs(dlambda[0]) <= 1e-3 && fabs(dlambda[1]) <= 1e-3 && fabs(dlambda[2]) <= 1e-3)
362                         break;
363         }
364
365         mu_result = mu;
366         sigma_result = sigma;
367 }
368
369 static void compute_new_rating(double mu1, double sigma1, double mu2, double sigma2, int score1, int score2, double &mu, double &sigma, double &probability)
370 {
371         vector<pair<double, double> > curve;
372
373         if (score1 > score2) {
374                 compute_opponent_rating_pdf(score1, score2, mu2, sigma2, -1.0, curve);
375         } else {
376                 compute_opponent_rating_pdf(score2, score1, mu2, sigma2, 1.0, curve);
377         }
378
379         // multiply in the gaussian
380         for (unsigned i = 0; i < curve.size(); ++i) {
381                 double r1 = curve[i].first;
382
383                 // my pdf
384                 double z = (r1 - mu1) / sigma1;
385 #if USE_LOGISTIC_DISTRIBUTION
386                 curve[i].second *= sech2(0.5 * z);
387 #else
388                 double gaussian = exp(-(z*z/2.0));
389                 curve[i].second *= gaussian;
390 #endif
391         }
392         
393         // Compute the overall probability of the given result, by integrating
394         // the entire resulting pdf. Note that since we're actually evaluating
395         // a double integral, we'll need to multiply by h² instead of h.
396         {
397                 double h = (curve.back().first - curve.front().first) / (curve.size() - 1);
398                 double sum = curve.front().second;
399                 for (unsigned i = 1; i < curve.size() - 1; i += 2) {
400                         sum += 4.0 * curve[i].second;
401                 }
402                 for (unsigned i = 2; i < curve.size() - 1; i += 2) {
403                         sum += 2.0 * curve[i].second;
404                 }
405                 sum += curve.back().second;
406                 sum *= h * h / 3.0;
407         
408                 // FFT convolution multiplication factor (FFTW computes unnormalized
409                 // transforms)
410                 sum /= (curve.size() * 2);      
411
412                 // pdf normalization factors
413 #if USE_LOGISTIC_DISTRIBUTION
414                 sum /= (sigma1 * 4.0);
415                 sum /= (sigma2 * 4.0);
416 #else
417                 sum /= (sigma1 * sqrt(2.0 * M_PI));
418                 sum /= (sigma2 * sqrt(2.0 * M_PI));
419 #endif
420
421                 probability = sum;
422         }
423
424         double mu_est, sigma_est;
425         normalize(curve);
426         estimate_musigma(curve, mu_est, sigma_est);
427         least_squares(curve, mu_est, sigma_est, mu, sigma);
428 }
429
430 static void compute_new_double_rating(double mu1, double sigma1, double mu2, double sigma2, double mu3, double sigma3, double mu4, double sigma4, int score1, int score2, double &mu, double &sigma, double &probability)
431 {
432         vector<pair<double, double> > curve, newcurve;
433         double mu_t = mu3 + mu4;
434         double sigma_t = sqrt(sigma3*sigma3 + sigma4*sigma4);
435                         
436         if (score1 > score2) {
437                 compute_opponent_rating_pdf(score1, score2, mu_t, sigma_t, -1.0, curve);
438         } else {
439                 compute_opponent_rating_pdf(score2, score1, mu_t, sigma_t, 1.0, curve);
440         }
441
442         newcurve.reserve(curve.size());
443
444         // iterate over r1
445         double h = 3000.0 / curve.size();
446         for (unsigned i = 0; i < curve.size(); ++i) {
447                 double sum = 0.0;
448
449                 // could be anything, but this is a nice start
450                 //double r1 = curve[i].first;
451                 double r1 = i * h;
452
453                 // iterate over r2
454 #if USE_LOGISTIC_DISTRIBUTION
455                 double invsigma2 = 1.0 / sigma2;
456 #else
457                 double invsq2sigma2 = 1.0 / (sqrt(2.0) * sigma2);
458 #endif
459                 for (unsigned j = 0; j < curve.size(); ++j) {
460                         double r1plusr2 = curve[j].first;
461                         double r2 = r1plusr2 - r1;
462
463 #if USE_LOGISTIC_DISTRIBUTION
464                         double z = (r2 - mu2) * invsigma2;
465                         double gaussian = sech2(0.5 * z);
466 #else   
467                         double z = (r2 - mu2) * invsq2sigma2;
468                         double gaussian = exp(-z*z);
469 #endif
470                         sum += curve[j].second * gaussian;
471                 }
472
473 #if USE_LOGISTIC_DISTRIBUTION
474                 double z = (r1 - mu1) / sigma1;
475                 double gaussian = sech2(0.5 * z);
476 #else
477                 double z = (r1 - mu1) / sigma1;
478                 double gaussian = exp(-(z*z/2.0));
479 #endif
480                 newcurve.push_back(make_pair(r1, gaussian * sum));
481         }
482
483         // Compute the overall probability of the given result, by integrating
484         // the entire resulting pdf. Note that since we're actually evaluating
485         // a triple integral, we'll need to multiply by 4h³ (no idea where the
486         // 4 factor comes from, probably from the 0..6000 range somehow) instead
487         // of h.
488         {
489                 double h = (newcurve.back().first - newcurve.front().first) / (newcurve.size() - 1);
490                 double sum = newcurve.front().second;
491                 for (unsigned i = 1; i < newcurve.size() - 1; i += 2) {
492                         sum += 4.0 * newcurve[i].second;
493                 }
494                 for (unsigned i = 2; i < newcurve.size() - 1; i += 2) {
495                         sum += 2.0 * newcurve[i].second;
496                 }
497                 sum += newcurve.back().second;
498
499                 sum *= 4.0 * h * h * h / 3.0;
500         
501                 // FFT convolution multiplication factor (FFTW computes unnormalized
502                 // transforms)
503                 sum /= (newcurve.size() * 2);   
504
505                 // pdf normalization factors
506 #if USE_LOGISTIC_DISTRIBUTION
507                 sum /= (sigma1 * 4.0);
508                 sum /= (sigma2 * 4.0);
509                 sum /= (sigma_t * 4.0);
510 #else
511                 sum /= (sigma1 * sqrt(2.0 * M_PI));
512                 sum /= (sigma2 * sqrt(2.0 * M_PI));
513                 sum /= (sigma_t * sqrt(2.0 * M_PI));
514 #endif
515
516                 probability = sum;
517         }
518
519         double mu_est, sigma_est;
520         normalize(newcurve);
521         estimate_musigma(newcurve, mu_est, sigma_est);
522         least_squares(newcurve, mu_est, sigma_est, mu, sigma);
523 }
524
525 int main(int argc, char **argv)
526 {
527         FILE *fp = fopen("fftw-wisdom", "rb");
528         if (fp != NULL) {
529                 fftw_import_wisdom_from_file(fp);
530                 fclose(fp);
531         }
532
533         double mu1 = atof(argv[1]);
534         double sigma1 = atof(argv[2]);
535         double mu2 = atof(argv[3]);
536         double sigma2 = atof(argv[4]);
537
538         if (argc > 10) {
539                 double mu3 = atof(argv[5]);
540                 double sigma3 = atof(argv[6]);
541                 double mu4 = atof(argv[7]);
542                 double sigma4 = atof(argv[8]);
543                 int score1 = atoi(argv[9]);
544                 int score2 = atoi(argv[10]);
545                 double mu, sigma, probability;
546                 compute_new_double_rating(mu1, sigma1, mu2, sigma2, mu3, sigma3, mu4, sigma4, score1, score2, mu, sigma, probability);
547                 if (score1 > score2) {
548                         printf("%f %f %f\n", mu, sigma, probability);
549                 } else {
550                         printf("%f %f %f\n", mu, sigma, probability);
551                 }
552         } else if (argc > 8) {
553                 double mu3 = atof(argv[5]);
554                 double sigma3 = atof(argv[6]);
555                 double mu4 = atof(argv[7]);
556                 double sigma4 = atof(argv[8]);
557                 int k = atoi(argv[9]);
558
559                 // assess all possible scores
560                 for (int i = 0; i < k; ++i) {
561                         double newmu1_1, newmu1_2, newmu2_1, newmu2_2;
562                         double newsigma1_1, newsigma1_2, newsigma2_1, newsigma2_2;
563                         double probability;
564                         compute_new_double_rating(mu1, sigma1, mu2, sigma2, mu3, sigma3, mu4, sigma4, k, i, newmu1_1, newsigma1_1, probability);
565                         compute_new_double_rating(mu2, sigma2, mu1, sigma1, mu3, sigma3, mu4, sigma4, k, i, newmu1_2, newsigma1_2, probability);
566                         compute_new_double_rating(mu3, sigma3, mu4, sigma4, mu1, sigma1, mu2, sigma2, i, k, newmu2_1, newsigma2_1, probability);
567                         compute_new_double_rating(mu4, sigma4, mu3, sigma3, mu1, sigma1, mu2, sigma2, i, k, newmu2_2, newsigma2_2, probability);
568                         printf("%u-%u,%f,%+f,%+f,%+f,%+f\n",
569                                 k, i, probability, newmu1_1-mu1, newmu1_2-mu2,
570                                 newmu2_1-mu3, newmu2_2-mu4);
571                 }
572                 for (int i = k; i --> 0; ) {
573                         double newmu1_1, newmu1_2, newmu2_1, newmu2_2;
574                         double newsigma1_1, newsigma1_2, newsigma2_1, newsigma2_2;
575                         double probability;
576                         compute_new_double_rating(mu1, sigma1, mu2, sigma2, mu3, sigma3, mu4, sigma4, k, i, newmu1_1, newsigma1_1, probability);
577                         compute_new_double_rating(mu1, sigma1, mu2, sigma2, mu3, sigma3, mu4, sigma4, i, k, newmu1_1, newsigma1_1, probability);
578                         compute_new_double_rating(mu2, sigma2, mu1, sigma1, mu3, sigma3, mu4, sigma4, i, k, newmu1_2, newsigma1_2, probability);
579                         compute_new_double_rating(mu3, sigma3, mu4, sigma4, mu1, sigma1, mu2, sigma2, k, i, newmu2_1, newsigma2_1, probability);
580                         compute_new_double_rating(mu4, sigma4, mu3, sigma3, mu1, sigma1, mu2, sigma2, k, i, newmu2_2, newsigma2_2, probability);
581                         printf("%u-%u,%f,%+f,%+f,%+f,%+f\n",
582                                 i, k, probability, newmu1_1-mu1, newmu1_2-mu2,
583                                 newmu2_1-mu3, newmu2_2-mu4);
584                 }
585         } else if (argc > 6) {
586                 int score1 = atoi(argv[5]);
587                 int score2 = atoi(argv[6]);
588                 double mu, sigma, probability;
589                 compute_new_rating(mu1, sigma1, mu2, sigma2, score1, score2, mu, sigma, probability);
590
591                 if (score1 > score2) {
592                         printf("%f %f %f\n", mu, sigma, probability);
593                 } else {
594                         printf("%f %f %f\n", mu, sigma, probability);
595                 }
596         } else {
597                 int k = atoi(argv[5]);
598
599                 // assess all possible scores
600                 for (int i = 0; i < k; ++i) {
601                         double newmu1, newmu2, newsigma1, newsigma2, probability;
602                         compute_new_rating(mu1, sigma1, mu2, sigma2, k, i, newmu1, newsigma1, probability);
603                         compute_new_rating(mu2, sigma2, mu1, sigma1, i, k, newmu2, newsigma2, probability);
604                         printf("%u-%u,%f,%+f,%+f\n",
605                                 k, i, probability, newmu1-mu1, newmu2-mu2);
606                 }
607                 for (int i = k; i --> 0; ) {
608                         double newmu1, newmu2, newsigma1, newsigma2, probability;
609                         compute_new_rating(mu1, sigma1, mu2, sigma2, i, k, newmu1, newsigma1, probability);
610                         compute_new_rating(mu2, sigma2, mu1, sigma1, k, i, newmu2, newsigma2, probability);
611                         printf("%u-%u,%f,%+f,%+f\n",
612                                 i, k, probability, newmu1-mu1, newmu2-mu2);
613                 }
614         }
615         
616         fp = fopen("fftw-wisdom", "wb");
617         if (fp != NULL) {
618                 fftw_export_wisdom_to_file(fp);
619                 fclose(fp);
620         }
621 }
622