simu

Ice hockey final standings simulator
git clone https://git.inz.fi/simu
Log | Files | Refs

simu.c (12865B)


      1 /*
      2  * Input:
      3  *  <number of teams>
      4  *  Team1
      5  *  Team2
      6  *
      7  *  <number of played games>
      8  *  <index of home team> <index of away team> <home score> <away score> <OT/SO>
      9  *
     10  *  <number of remaining games>
     11  *  <index of home team> <index of away team>
     12  *
     13  * Output:
     14  *  Team1 <current games> <current points> <elo rating> <total games> <expected points> <probab finish 1st> <probab finish 2nd> ...
     15  *  Team2 <current games> <current points> <elo rating> <total games> <expected points> <probab finish 1st> <probab finish 2nd> ...
     16  */
     17 #define _POSIX_C_SOURCE 200811L
     18 #include <limits.h>
     19 #include <math.h>
     20 #include <pthread.h>
     21 #include <stdbool.h>
     22 #include <stdint.h>
     23 #include <stdio.h>
     24 #include <stdlib.h>
     25 #include <string.h>
     26 #include <unistd.h>
     27 
     28 #if defined(USE_LEHMER)
     29 #define R_MAX UINT32_MAX
     30 struct rand_t {
     31 	__uint128_t state;
     32 };
     33 
     34 static void getseed(struct rand_t *r)
     35 {
     36 	struct timespec s;
     37 	clock_gettime(CLOCK_MONOTONIC, &s);
     38 	r->state = s.tv_nsec;
     39 	r->state <<= 64;
     40 	r->state |= s.tv_sec | 1;
     41 }
     42 
     43 static inline uint32_t getrand(struct rand_t *r)
     44 {
     45 	r->state *= 0xda942042e4dd58b5;
     46 	return r->state >> 64;
     47 }
     48 
     49 #elif defined(USE_URANDOM)
     50 #define R_MAX UINT32_MAX
     51 struct rand_t {
     52 	int fd;
     53 	uint32_t rbuf[4096];
     54 	uint32_t *r;
     55 };
     56 
     57 static void getseed(struct rand_t *r)
     58 {
     59 	r->fd = open("/dev/urandom", O_RDONLY);
     60 	r->r = 1[&r->rbuf];
     61 }
     62 
     63 static inline uint32_t getrand(struct rand_t *r)
     64 {
     65 	if (r->r == 1[&r->rbuf]) {
     66 		read(r->fd, r->rbuf, sizeof(r->rbuf));
     67 		r->r = r->rbuf;
     68 	}
     69 	return *r->r++;
     70 }
     71 #else
     72 struct rand_t {
     73 	unsigned int seed;
     74 };
     75 #define R_MAX RAND_MAX
     76 static void getseed(struct rand_t *r)
     77 {
     78 	struct timespec s;
     79 	clock_gettime(CLOCK_MONOTONIC, &s);
     80 	r->seed = s.tv_sec ^ s.tv_nsec;
     81 }
     82 
     83 static inline uint32_t getrand(struct rand_t *r)
     84 {
     85 	return rand_r(&r->seed);
     86 }
     87 #endif
     88 
     89 static inline bool urand(uint32_t limit, bool *anti, struct rand_t *r)
     90 {
     91 	uint32_t v = getrand(r);
     92 	if (anti)
     93 		*anti = (uint32_t)R_MAX - v < limit;
     94 	return (uint32_t)v < limit;
     95 }
     96 
     97 struct team {
     98 	size_t id;
     99 	char name[128];
    100 	unsigned points;
    101 	unsigned games;
    102 	unsigned wins;
    103 	double elo;
    104 	long long unsigned pointsum;
    105 	long long unsigned gamessum;
    106 	size_t *poscounts;
    107 };
    108 
    109 struct game {
    110 	size_t t[2];
    111 	uint32_t expected;
    112 };
    113 
    114 struct standing {
    115 	size_t ti;
    116 	unsigned points;
    117 	unsigned wins;
    118 	unsigned games;
    119 	unsigned random;
    120 };
    121 
    122 int pointscmp(const void *a, const void *b)
    123 {
    124 	int r;
    125 	const struct standing *at = a;
    126 	const struct standing *bt = b;
    127 
    128 	if ((r = bt->points * at->games - at->points * bt->games))
    129 		return r;
    130 	if ((r = bt->wins - at->wins))
    131 		return r;
    132 	return bt->random - at->random;
    133 }
    134 
    135 int teampointscmp(const void *a, const void *b)
    136 {
    137 	int r;
    138 	const struct team *at = a;
    139 	const struct team *bt = b;
    140 
    141 	if ((r = bt->points * at->games - at->points * bt->games))
    142 		return r;
    143 	if ((r = bt->wins - at->wins))
    144 		return r;
    145 	return 0;
    146 }
    147 
    148 int teamcmp(const void *a, const void *b)
    149 {
    150 	const struct team *at = a;
    151 	const struct team *bt = b;
    152 
    153 	if (at->pointsum * bt->gamessum > bt->pointsum * at->gamessum)
    154 		return -1;
    155 	if (bt->pointsum * at->gamessum > at->pointsum * bt->gamessum)
    156 		return 1;
    157 	return 0;
    158 }
    159 
    160 void isort(void *data, size_t n, size_t sz, int (*cmp)(const void *a, const void *b))
    161 {
    162 	char (*d)[sz][1] = data;
    163 	char tmp[sz];
    164 	size_t i, j;
    165 
    166 	for (i = 1; i < n; i++) {
    167 		if (cmp(d[i], d[i - 1]) >= 0)
    168 			continue;
    169 
    170 		memcpy(tmp, d[i], sizeof(tmp));
    171 
    172 		for (j = i - 1; j > 0 && cmp(tmp, d[j - 1]) < 0; j--);
    173 
    174 		memmove(d[j + 1], d[j], *d[i] - *d[j]);
    175 		memcpy(d[j], tmp, sizeof(tmp));
    176 	}
    177 }
    178 
    179 void simulate(struct team *teams,
    180 	      size_t n_teams,
    181 	      const struct game *games,
    182 	      size_t n_games,
    183 	      uint32_t otprob,
    184 	      size_t iterations,
    185 	      struct rand_t *seedp)
    186 {
    187 	struct standing s0[n_teams];
    188 	struct standing standings[n_teams];
    189 	struct standing antistandings[n_teams];
    190 	size_t i;
    191 
    192 	for (i = 0; i < n_teams; i++) {
    193 		s0[i].ti = i;
    194 		s0[i].points = teams[i].points;
    195 		s0[i].wins = teams[i].wins;
    196 		s0[i].games = teams[i].games;
    197 	}
    198 
    199 	for (i = 0; i < iterations / 2; i++) {
    200 		size_t j;
    201 
    202 		memcpy(standings, s0, sizeof(standings));
    203 		memcpy(antistandings, s0, sizeof(antistandings));
    204 
    205 		for (j = 0; j < n_games; j++) {
    206 			size_t wi;
    207 			size_t li;
    208 			size_t awi;
    209 			size_t ali;
    210 
    211 			bool antihw;
    212 			bool antiot;
    213 			bool hw = urand(games[j].expected, &antihw, seedp);
    214 			bool ot = urand(otprob, &antiot, seedp);
    215 
    216 			wi = games[j].t[!hw];
    217 			li = games[j].t[hw];
    218 
    219 			awi = games[j].t[!antihw];
    220 			ali = games[j].t[antihw];
    221 
    222 			standings[wi].points += 3 - ot;
    223 			standings[li].points += ot;
    224 			standings[wi].wins += !ot;
    225 			standings[wi].games++;
    226 			standings[li].games++;
    227 
    228 			antistandings[awi].points += 3 - ot;
    229 			antistandings[ali].points += ot;
    230 			antistandings[awi].wins += !ot;
    231 			antistandings[awi].games++;
    232 			antistandings[ali].games++;
    233 		}
    234 
    235 		for (j = 0; j < n_teams; j++) {
    236 			standings[j].random = getrand(seedp) % (INT_MAX / 2);
    237 			antistandings[j].random = getrand(seedp) % (INT_MAX / 2);
    238 		}
    239 		isort(standings, n_teams, sizeof(*standings), pointscmp);
    240 
    241 		for (j = 0; j < n_teams; j++) {
    242 			teams[standings[j].ti].pointsum += standings[j].points;
    243 			teams[standings[j].ti].gamessum += standings[j].games;
    244 			teams[standings[j].ti].poscounts[j]++;
    245 		}
    246 
    247 		isort(antistandings, n_teams, sizeof(*antistandings), pointscmp);
    248 
    249 		for (j = 0; j < n_teams; j++) {
    250 			teams[antistandings[j].ti].pointsum += antistandings[j].points;
    251 			teams[antistandings[j].ti].gamessum += antistandings[j].games;
    252 			teams[antistandings[j].ti].poscounts[j]++;
    253 		}
    254 	}
    255 
    256 }
    257 
    258 void simulate_winall(struct team *teams,
    259 		     size_t n_teams,
    260 		     const struct game *games,
    261 		     size_t n_games,
    262 		     uint32_t otprob,
    263 		     size_t iterations,
    264 		     size_t team_id,
    265 		     bool win,
    266 		     struct rand_t *seedp)
    267 {
    268 	struct team teams_cpy[n_teams];
    269 	struct game games_cpy[n_games];
    270 
    271 	size_t r;
    272 	size_t w;
    273 
    274 	memcpy(teams_cpy, teams, sizeof(teams_cpy));
    275 
    276 	for (w = r = 0; r < n_games; r++) {
    277 		if (games[r].t[0] == team_id || games[r].t[1] == team_id) {
    278 			size_t opp = games[r].t[0] + games[r].t[1] - team_id;
    279 			teams_cpy[team_id].points += win * 3;
    280 			teams_cpy[team_id].games++;
    281 			teams_cpy[team_id].wins += win;
    282 
    283 			teams_cpy[opp].points += !win * 3;
    284 			teams_cpy[opp].games++;
    285 			teams_cpy[opp].wins += !win;
    286 		} else {
    287 			games_cpy[w++] = games[r];
    288 		}
    289 	}
    290 
    291 	simulate(teams_cpy, n_teams, games_cpy, w, otprob, iterations, seedp);
    292 }
    293 
    294 void simulate_winloseall(struct team *teams,
    295 			 size_t n_teams,
    296 			 size_t team,
    297 			 const struct game *games,
    298 			 size_t n_games,
    299 			 uint32_t otprob,
    300 			 size_t iterations,
    301 			 struct rand_t *seedp)
    302 {
    303 	struct team teams_cpy[n_teams];
    304 	size_t poscounts[n_teams][n_teams];
    305 	size_t e;
    306 	size_t i;
    307 
    308 	memcpy(teams_cpy, teams, sizeof(teams_cpy));
    309 	memset(poscounts, 0, sizeof(poscounts));
    310 
    311 	for (i = 0; i < n_teams; i++)
    312 		teams_cpy[i].poscounts = poscounts[i];
    313 
    314 	simulate_winall(teams_cpy, n_teams,
    315 			games, n_games,
    316 			otprob,
    317 			iterations,
    318 			team, true,
    319 			seedp);
    320 	simulate_winall(teams_cpy, n_teams,
    321 			games, n_games,
    322 			otprob,
    323 			iterations,
    324 			team, false,
    325 			seedp);
    326 
    327 	for (i = 0; i < n_teams; i++)
    328 		if (teams_cpy[team].poscounts[i])
    329 			break;
    330 	for (e = n_teams; e > 0; e--)
    331 		if (teams_cpy[team].poscounts[e - 1])
    332 			break;
    333 	for (; i < e; i++)
    334 		teams[team].poscounts[i] = 1;
    335 }
    336 
    337 struct thread_data {
    338 	struct team *teams;
    339 	bool *winloseall;
    340 	size_t n_teams;
    341 
    342 	struct game *games;
    343 	size_t n_games;
    344 
    345 	uint32_t otprob;
    346 
    347 	size_t iterations;
    348 	size_t iterated;
    349 	pthread_mutex_t mutex;
    350 };
    351 
    352 void *simulate_thread(void *data)
    353 {
    354 	struct thread_data *td = data;
    355 	struct team teams_cpy[td->n_teams];
    356 	size_t i;
    357 	size_t iters;
    358 	size_t poscounts[td->n_teams][td->n_teams];
    359 	size_t n = 0;
    360 	struct rand_t seedp;
    361 
    362 	getseed(&seedp);
    363 
    364 	pthread_mutex_lock(&td->mutex);
    365 	memcpy(teams_cpy, td->teams, sizeof(teams_cpy));
    366 	pthread_mutex_unlock(&td->mutex);
    367 
    368 	memset(poscounts, 0, sizeof(poscounts));
    369 
    370 	for (i = 0; i < td->n_teams; i++) {
    371 		teams_cpy[i].poscounts = poscounts[i];
    372 		pthread_mutex_lock(&td->mutex);
    373 		if (!td->winloseall[i]) {
    374 			td->winloseall[i] = true;
    375 			pthread_mutex_unlock(&td->mutex);
    376 
    377 			simulate_winloseall(teams_cpy, td->n_teams, i,
    378 					    td->games, td->n_games,
    379 					    td->otprob,
    380 					    td->iterations / td->n_teams / 2,
    381 					    &seedp);
    382 		} else {
    383 			pthread_mutex_unlock(&td->mutex);
    384 		}
    385 	}
    386 
    387 	for (;;) {
    388 		pthread_mutex_lock(&td->mutex);
    389 
    390 		if (td->iterations - td->iterated > 100000)
    391 			iters = 100000;
    392 		else
    393 			iters = td->iterations - td->iterated;
    394 
    395 		td->iterated += iters;
    396 		pthread_mutex_unlock(&td->mutex);
    397 
    398 		if (!iters)
    399 			break;
    400 		n += iters;
    401 
    402 		simulate(teams_cpy, td->n_teams,
    403 			 td->games, td->n_games,
    404 			 td->otprob,
    405 			 iters, &seedp);
    406 	}
    407 
    408 	pthread_mutex_lock(&td->mutex);
    409 	for (i = 0; i < td->n_teams; i++) {
    410 		size_t j;
    411 		td->teams[i].pointsum += teams_cpy[i].pointsum;
    412 		td->teams[i].gamessum += teams_cpy[i].gamessum;
    413 
    414 		for (j = 0; j < td->n_teams; j++)
    415 			td->teams[i].poscounts[j] += teams_cpy[i].poscounts[j];
    416 	}
    417 	pthread_mutex_unlock(&td->mutex);
    418 
    419 	return (void *)n;
    420 }
    421 
    422 const char *ltrim(const char *s)
    423 {
    424 	while (*s++ == ' ');
    425 	return s - 1;
    426 }
    427 
    428 int main(int argc, char **argv)
    429 {
    430 	int opt;
    431 	double homeadv = 58;
    432 	double elok = 32;
    433 	uint32_t otprob = 0.23 * R_MAX;
    434 	size_t n;
    435 	size_t m;
    436 	size_t i;
    437 	size_t j;
    438 	size_t iterations = 1000000;
    439 	size_t threads = 1;
    440 
    441 	while ((opt = getopt(argc, argv, "i:t:a:o:")) != -1) {
    442 		switch (opt) {
    443 		case 'i':
    444 			iterations = strtoull(optarg, NULL, 10);
    445 			break;
    446 
    447 		case 't':
    448 			threads = strtoull(optarg, NULL, 10);
    449 			break;
    450 
    451 		case 'a':
    452 			homeadv = strtod(optarg, NULL);
    453 			break;
    454 
    455 		case 'o':
    456 			otprob = strtod(optarg, NULL) * R_MAX;
    457 			break;
    458 
    459 		case 'k':
    460 			elok = strtod(optarg, NULL);
    461 			break;
    462 
    463 		default:
    464 			exit(1);
    465 			break;
    466 		}
    467 	}
    468 
    469 	iterations += iterations & 1;
    470 
    471 	if (scanf(" %zu", &n) < 1)
    472 		return 1;
    473 
    474 	struct team teams[n];
    475 	size_t teammap[n];
    476 	size_t poscounts[n][n];
    477 	memset(poscounts, 0, sizeof(poscounts));
    478 
    479 	for (i = 0; i < n; i++) {
    480 		if (scanf(" %127s", teams[i].name) < 1)
    481 			return 1;
    482 		teams[i].id = i;
    483 		teams[i].points = 0;
    484 		teams[i].games = 0;
    485 		teams[i].wins = 0;
    486 		teams[i].pointsum = 0;
    487 		teams[i].gamessum = 0;
    488 		teams[i].poscounts = poscounts[i];
    489 		teams[i].elo = 2000;
    490 	}
    491 
    492 	if (scanf(" %zu", &m) < 1)
    493 		return 1;
    494 
    495 	for (i = 0; i < m; i++) {
    496 		size_t hi, ai;
    497 		unsigned hs, as;
    498 		bool ot = false;
    499 		char det[16] = "";
    500 
    501 		switch (scanf(" %zu %zu %u %u%15[^\n]", &hi, &ai, &hs, &as, det)) {
    502 		case 5:
    503 			ot = !strcmp(ltrim(det), "OT") ||
    504 			     !strcmp(ltrim(det), "SO");
    505 			break;
    506 
    507 		case 4:
    508 			break;
    509 
    510 		default:
    511 			exit(1);
    512 		}
    513 
    514 		double expected = 1. / (1 + pow(10, (teams[ai].elo - teams[hi].elo - homeadv) / 400.0));
    515 		double actual;
    516 
    517 		teams[hi].games++;
    518 		teams[ai].games++;
    519 
    520 		if (hs > as) {
    521 			if (!ot) {
    522 				actual = 1;
    523 				teams[hi].wins++;
    524 				teams[hi].points += 3;
    525 			} else {
    526 				actual = 2./3;
    527 				teams[hi].points += 2;
    528 				teams[ai].points += 1;
    529 			}
    530 		} else {
    531 			if (ot) {
    532 				actual = 1./3;
    533 				teams[hi].points += 1;
    534 				teams[ai].points += 2;
    535 			} else {
    536 				teams[ai].points += 3;
    537 				teams[ai].wins++;
    538 				actual = 0;
    539 			}
    540 		}
    541 
    542 		teams[hi].elo += elok * (actual - expected);
    543 		teams[ai].elo -= elok * (actual - expected);
    544 	}
    545 
    546 	isort(teams, 1[&teams] - teams, sizeof(*teams), teampointscmp);
    547 
    548 	for (i = 0; i < n; i++)
    549 		teammap[teams[i].id] = i;
    550 
    551 	if (scanf(" %zu", &m) < 1)
    552 		return 1;
    553 
    554 	struct game games[m];
    555 
    556 	for (i = 0; i < m; i++) {
    557 		size_t h, a;
    558 		if (scanf(" %zu %zu", &h, &a) < 2)
    559 			return 1;
    560 		games[i].t[0] = teammap[h];
    561 		games[i].t[1] = teammap[a];
    562 		games[i].expected = R_MAX / (1 + pow(10.0, (teams[games[i].t[1]].elo  - (teams[games[i].t[0]].elo + homeadv)) / 400.0));
    563 	}
    564 
    565 	if (threads > 1) {
    566 		pthread_t pthrds[threads];
    567 		bool winloseall[n];
    568 		memset(winloseall, 0, sizeof(winloseall));
    569 		struct thread_data td = {
    570 			teams, winloseall, n,
    571 			games, m,
    572 			otprob,
    573 			iterations, 0,
    574 			PTHREAD_MUTEX_INITIALIZER
    575 		};
    576 		for (i = 0; i < threads; i++)
    577 			pthread_create(&pthrds[i], NULL, simulate_thread, &td);
    578 		iterations = 0;
    579 		for (i = 0; i < threads; i++) {
    580 			void *a;
    581 			pthread_join(pthrds[i], &a);
    582 			iterations += (size_t)a;
    583 		}
    584 	} else {
    585 		struct rand_t seedp;
    586 		getseed(&seedp);
    587 
    588 		for (i = 0; i < n; i++) {
    589 			simulate_winloseall(teams, n, i,
    590 					    games, m,
    591 					    otprob,
    592 					    iterations / n / 2,
    593 					    &seedp);
    594 		}
    595 
    596 		simulate(teams, n,
    597 			 games, m,
    598 			 otprob,
    599 			 iterations,
    600 			 &seedp);
    601 	}
    602 
    603 	isort(teams, n, sizeof(*teams), teamcmp);
    604 	for (i = 0; i < n; i++) {
    605 		printf("%s %u %u %llu %.1f %d", teams[i].name,
    606 		       teams[i].games, teams[i].points,
    607 		       teams[i].gamessum / iterations,
    608 		       (double)teams[i].pointsum / iterations,
    609 		       (int)round(teams[i].elo));
    610 		for (j = 0; j < n; j++) {
    611 			if (teams[i].poscounts[j] == iterations + 1)
    612 				printf(" +");
    613 			else if (teams[i].poscounts[j])
    614 				printf(" %e", (teams[i].poscounts[j] - 1) * 100.0 / iterations);
    615 			else
    616 				printf(" -");
    617 		}
    618 		printf("\n");
    619 	}
    620 
    621 	return 1;
    622 }