Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <?php
- /**
- * Моделирование N участков с случайным числом избирателей и случайно
- * распределенной явкой, с целью проверить, как будет выглядеть
- * график распределения числа участков с определенной явкой.
- *
- * Число избирателей на участке распределено равномерно.
- *
- * Явка распределена нормально.
- *
- * На выходе программа генерирует CSV-файл с двумя колонками:
- *
- * {процент в бине}, {число участков в бине}
- *
- * Например:
- *
- * 0,5
- * 1,13
- * 2,6
- * ....
- * 100,12
- *
- */
- if (!empty($argv[1]) && $argv[1] == '--test-gen') {
- // тестирование генератора нормального распределения
- $bins = array_fill(0, 200, 0);
- $num = 0;
- $gen = generateMtRand();
- foreach (generateNormallyDistributedNumber($gen, 100, 20) as $number) {
- $round = round($number);
- $bins[$round]++;
- // echo "$num,$number\n";
- $num++;
- if ($num >= 5000) {
- break;
- }
- }
- foreach ($bins as $key => $bin) {
- echo "$key,$bin\n";
- }
- exit();
- }
- // Число участков
- $uikCount = 50000;
- // Мин. число число изб. на участке
- $minVoters = 1000;
- // Макс. число изб. на участке
- $maxVoters = 2000;
- // Средняя явка в процентах
- $meanTurnout = 55;
- // Станд. отклонение явки (сигма) в процентах
- // 99% значений будут в диапазоне (среднее +- 3 сигмы)
- // При этом, значения явки выше 100% приводятся к 100%
- $turnoutStdDev = 10;
- // Размер бина в процентах
- $binSize = 1;
- if (in_array('--small-bin', $argv)) {
- $binSize = 0.2;
- }
- // Генератор числа людей на участке, равномерно распределенный
- $voterGen = generateMtRand();
- if (in_array('--voters-norm', $argv)) {
- // Опция для генерации числа изб-й на участке нормальным распределением
- $voterGen = generateNormallyDistributedNumber($voterGen, 0.5, 0.2);
- }
- // Генератор явки, нормально распределенный
- $randomGen = generateMtRand();
- if (in_array('--random-int', $argv)) {
- // опция для использования более случайного генератора явки
- $randomGen = generateRandomInt();
- }
- $turnoutGen = generateNormallyDistributedNumber($randomGen, $meanTurnout, $turnoutStdDev);
- // Генерируем участки и явку на них
- // Сохраняем результат в массив
- $uiks = [];
- for ($i=0; $i < $uikCount; $i++) {
- // Всего избирателей
- $voterRandom = $voterGen->current();
- $voterGen->next();
- $votersTotal = round($minVoters + $voterRandom * ($maxVoters - $minVoters));
- $turnoutPercent = $turnoutGen->current();
- $turnoutGen->next();
- // При этом явка не может быть выше 100% или ниже 0%
- $turnoutPercent = min(100, max(0, $turnoutPercent));
- // Явка, генерируем цифру в процентах и приводим к целому числу людей
- // Чтобы учесть эффекты от деления целых чисел
- $turnoutPeople = round($turnoutPercent * $votersTotal / 100);
- $uiks[] = [
- 'voters' => $votersTotal,
- 'turnout' => $turnoutPeople
- ];
- }
- // Мы имеем данные по каждому УИКу, теперь раскладываем их по бинам
- // Число бинов
- $binCount = ceil(100 / $binSize);
- $bins = [];
- for ($i=0; $i < $binCount; $i++) {
- $min = ($i * 100 / $binCount);
- $max = (($i + 1) * 100 / $binCount);
- $bins[$i] = [
- // В этом бине явка от столько процентов
- 'min' => $min,
- // До стольки
- 'max' => $max,
- // Число участков с такой явкой
- 'count' => 0
- ];
- }
- foreach ($uiks as $uik) {
- // Явка в процентах
- $uikPercentTurnout = $uik['turnout'] / $uik['voters'] * 100;
- // при определении номера бина округляем вниз
- $binNumber = floor($uikPercentTurnout / $binSize);
- // Для 100% данные идут в последний бин
- if ($binNumber >= count($bins)) {
- $binNumber = count($bins) - 1;
- }
- $bins[$binNumber]['count']++;
- }
- // Выводим результаты по бинам
- foreach ($bins as $bin) {
- printf("%3f,%d\n", $bin['min'], $bin['count']);
- }
- /* echo "\n\nUiks\n";
- var_dump($uiks);
- echo "\n\nBins\n";
- var_dump($bins);
- */
- /**
- * Возвращает генератор, который на каждой итерации возвращает
- * случайное число с нормальным распределением, средним mu и
- * стандартным отклонением sigma.
- *
- * На вход также передается функция-генератор случайных чисел. Это
- * позволяет использовать разные алгоритмы генерации случайных
- * чисел (mt_rand, random_bytes итд.). $random должен возвращать
- * случайное число от 0 до 1.
- *
- * Использует алгоритм с преобразованием Box-Muller
- * https://en.wikipedia.org/wiki/Box%E2%80%93Muller_transform
- */
- function generateNormallyDistributedNumber(Generator $random, $mu, $sigma)
- {
- $two_pi = M_PI * 2;
- $epsilon = 1e-20;
- while (true) {
- do {
- $u1 = $random->current();
- $random->next();
- $u2 = $random->current();
- $random->next();
- } while ($u1 <= $epsilon);
- $z0 = sqrt(-2.0 * log($u1)) * cos($two_pi * $u2);
- $z1 = sqrt(-2.0 * log($u1)) * sin($two_pi * $u2);
- yield $z0 * $sigma + $mu;
- yield $z1 * $sigma + $mu;
- }
- }
- /**
- * Генератор случайных чисел в диапазоне 0..1 на основе
- * mt_rand (Вихрь Мерсенна):
- * https://ru.wikipedia.org/wiki/%D0%92%D0%B8%D1%85%D1%80%D1%8C_%D0%9C%D0%B5%D1%80%D1%81%D0%B5%D0%BD%D0%BD%D0%B0
- */
- function generateMtRand()
- {
- $max = mt_getrandmax();
- while (true) {
- $random = mt_rand() / $max;
- yield $random;
- }
- }
- /**
- * Генератор случайных числел в диапазоне 0..1 на основе
- * random_int() (доступно с PHP7, работает медленнее)
- */
- function generateRandomInt()
- {
- $max = PHP_INT_MAX;
- while (true) {
- $random = random_int(0, $max) / $max;
- yield $random;
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement