Как я считал время прибытия методом Монте-Карло

от автора

Одной из самых захватывающих вещей в восьмидесятые было программное моделирование для решения каких-нибудь сложных аналитических проблем и одной из самых используемых техник был метод Монте-Карло. Заключается он в том, что запускает моделирование большое количество раз для получения все более и более достоверного результата.

Несмотря на то, что PHP не является научным языком и редко используется в исследовательских целях, метод Монте-Карло легко может быть реализован и на нём. И в данной статье я покажу как это сделать.

Задача из реальной жизни

Пару дней назад у меня должна быть встреча в 9 часов утра, за 100 миль от моего дома. В 6.30 утра я проснулся, оделся и пока я завтракал, я начал прикидывать в блокноте ближайшие пару часов. Я, как обычно, хотел приехать вовремя, поэтому я начал набрасывать маршрут: выезд из города, проселочная дорога, затем по штату на север, на восток, местная дорога на восток, проехать город, затем на снова на север и прибытие в город. Все это выглядело как-то так:


image

Моя жена заполнила бак прошлым вечером и я спокойно мог ехать по проселочной дороге. Шины, казалось, были в порядке, когда я смотрел на них, но мысль о том, делать или нет остановку на 10 минут, чтобы проверить их давление, не давала мне покоя. Ведь если я остановлюсь и проверю их, то я буду уверен в их давлении, так как сейчас в нем не был уверен, да и давление в шинах может повлиять на мое движение и скорость…

Я могу выезжать раньше, например, в 6.40, но тогда моей жене придется самой отводить дочь в школу, вместо того чтобы прямиком идти на работу. Если я подожду еще 10 минут, то я могу быть у школы как раз в тот момент, когда они только открывают свои двери, тем самым избавив мою жену от неудобств, тем более что школа по пути к выезду из города и это не сильно меня задерживало.

Я вернулся к тому, что я рисовал и добавил следующее:

image

Выпивая вторую чашку кофе, я встал у окна. Чистое утреннее небо, легкий утренний ветерок подтверждали хороший прогноз на моем смартфоне и я подумал, что поездка в этот раз будет быстрой. Заканчивая свое планирование, я нарисовал примерное время для каждого этапа, которые я нарисовал ранее:

image

Ожидаемое время поездки было 115 минут (1 час и 55 минут), если я смогу двигаться без лишних остановок. Я ожидал приехать в 8.35, если поеду сразу, или же в 8.55, если придется захватить с собой дочь, чтобы отправить ее в школу и заодно проверить шины.

Но любое планирование перестает быть идеальным после того, как столкнется с реальностью! По каким-то непонятным причинам, многие родители решают оставить своих детей в школе раньше, чем обычно, поэтому я потерял больше 5 минут, по сравнению с тем, что я планировал для своей быстрой поездки. Поняв, что я немного опаздываю, я решил не проверять давление в шинах и ехать сразу.

Я добрался до выезда из города на 5 минут раньше, чем я планировал в самом плохом раскладе дел и все шло хорошо ровно до тех пор, пока где-то между точками B и C в моем плане я не наткнулся на туман, сильно повлиявший на видимость на дороге. Туман снизил мою среднюю скорость? да еще и мешал обгонять медленные, но длинные грузовики. Городской поток в городе я преодолел намного легче, чем обычно и это не заняло больше 10 минут. Через несколько миль на южной дороге туман снизился и я мог спокойно ехать на разрешенной скорости. Но когда я приближался к моей цели, я понял, что впереди идут дорожные работы и это опять отнимет у меня запланированное время. В общем и целом я потратил еще 10 лишних минут на мое путешествие и в конце-концов я опоздал.

Моделируем путешествие

Я понимаю, что большая часть работы с PHP направлена на всякие там интернет-магазины и другие вебсайты. Но PHP может быть прекрасным инструментом для научных исследований, поскольку ему легко обучить непрофессиональных программистов, таких как инженеры и ученые, в отличии от моего любимого Python.

Напишем небольшой код, который поможет мне понять насколько раньше или позже я смогу прибыть на встречу, если пара этапов моего путешествия немного отойдут от намеченного пути. Для начала, можно описать наш путь как он есть:

<?php  class MyTrip  {      protected $departureTime;      protected $meetingTime;      protected $travelTimes;        public function __construct() {          $this->setDepartureTime('0640');          $this->setMeetingTime('0900');            // время потраченное на проезд между этапами         $this->setTravelTimes(array(              'AB' => 17,              'BC' => 17,              'CD' => 36,              'DE' => 9,              'EF' => 15,              'FG' => 15,              'GH' => 6          ));      }        // для простоты переведем время в минуты     protected static function convertToMinutes($timeStr) {          return substr($timeStr, 0, 2) * 60 +             substr($timeStr, 2, 2);      }        public function setDepartureTime($timeStr) {          $this->departureTime = self::convertToMinutes($timeStr);      }        public function setMeetingTime($timeStr) {          $this->meetingTime = self::convertToMinutes($timeStr);      }        public function setTravelTimes(array $travelTimes) {          $this->travelTimes = $travelTimes;      }        public checkPlan($stopAtSchool = true, $checkTires = true) {         // ...     } } 

План должен быть осуществимым и подходящим критерием для определения этого будет таким, что сумма всех времен плюс раннее время выезда должна быть меньше или равна времени, на которое назначена моя встреча. Собственно это и определяет метод checkPlan():

<?php public checkPlan($stopAtSchool = true, $checkTires = true) {     // посчитаем сумму проезда между всеми этапами     $travelTime = array_sum($this->travelTimes);       // добавим задержку, если мне придется отвозить дочь в школу     $schoolDelay = ($stopAtSchool) ? 10 : 0;       // задержка на проверку давления в шинах     $tiresDelay = ($checkTires) ? 10 : 0;       // находим ожидаемое время приезда     $meetingArriveTime = $this->departureTime + $travelTime +         $schoolDelay + $tiresDelay;       // доеду ли я вовремя?     $arriveOnTime = $meetingArriveTime <= $this->meetingTime;       return array($meetingArriveTime, $this->meetingTime,         $arriveOnTime); } 

Все, нам осталось только сделать экземпляр этого класса и проверить:

<?php $trip = new MyTrip(); print_r($trip->checkPlan()); 

С учетом настроек по умолчанию, вывод будет таким:

Array (     [0] => 535     [1] => 540     [2] => 1 ) 

Я должен прибыть в 535 минут, а встреча назначена на 540 минут. Если перевести на часы, то я прибуду в 8.45, раньше запланированного времени на 15 минут.

Но что на счет возможных задержек? Как их предусмотреть и просчитать?

Метод Монте-Карло или добавляем случайности

В самом простом виде мы можем определить некий безопасный зазор для каждого события и сказать что он может случиться на 10% раньше и на 25% позднее заданного времени. Эти зазоры могут быть случайно добавлены к изначальным задержкам (дочь, давление) и времени между этапами умножением каждого фактора на rand(90,125)/100.
Также можно присвоить 50% вероятность к двум решениям — отвезти дочь в школу и проверить шины. Тут снова поможет rand():

$this->checkTires = rand(1, 100) > 50; 

Объединив все это вместе можно определить метод checkPlanRisk() для вычисления решения приеду ли я вовремя или нет, с учетом разных ситуация во время пути:

<?php public function checkPlanRisk() {     // скорректируем время проезда     $travelTime = 0;     foreach ($this->travelTimes as $t) {         $travelTime += $t * rand(90, 125) / 100;     }       // Решение прихватить с собой дочь     $schoolDelay = 0;     if (rand(1, 100) > 50) {         $schoolDelay = 10 * rand(90, 125) / 100;     }           // и для шин     $tiresDelay = 0;     if (rand(1, 100) > 50) {         $tiresDelay = 10 * rand(90, 125) / 100;     }       // вычисление времени прибытия     $meetingArriveTime = $this->departureTime + $travelTime +         $schoolDelay + $tiresDelay;       // вовремя или же нет?     $arriveOnTime = $meetingArriveTime <= $this->meetingTime;       return array($schoolDelay, $tiresDelay, $meetingArriveTime,         $this->meetingTime, $arriveOnTime); } 

Сейчас встает вопрос прибуду ли я вовремя, принимая во внимание все возможные задержи? Метод Монте-Карло позволяет решить этот вопрос запуском большого количества раз данной задачи и вычисляя «уровень доверия» как отношение времени прибытия вовремя к общему количеству запусков задачи.

Если запустить данный достаточное количество раз и записать как часто я прибуду вовремя, то я смогу определить некоторую вероятность того, что я прибуду как планировал.

<?php public function runCheckPlanRisk($numTrials) {     $arriveOnTime = 0;     for ($i = 1; $i <= $numTrials; $i++) {         $result = $this->checkPlanRisk();         if ($result[4]) {             $arriveOnTime++;         }           echo "Попыток: " . $i;         echo " Школа: " . $result[0];         echo " Шины: " . $result[1];         echo " Время в пути: " . $result[2];           if ($result[4]) {             echo " -- Вовремя";         }         else {             echo " -- Опоздание";         }           $confidence = $arriveOnTime / $i;         echo "Уровень доверия: $confidencen";     } } 

Создадим новый экземпляр и сделаем 1000 вычислений друг за другом:

<?php $trip = new MyTrip(); $trip->runCheckPlanRisk(1000); 

После завершения получим прогноз:

Попыток: 1000 Школа: 0 Шины: 11.3 Время в пути: 530.44 -- Вовремя Уровень доверия: 0.716 

При текущих параметрах, как видно, у меня есть шанс в 72% прибыть на встречу вовремя. Конечно, это просто среднее значение, но и оно имеет право на жизнь.

От себя: скорее всего статья не несет абсолютно ничего нового, заголовок немного желтоват, но мне эта статья показалась забавной. Прочитал давно, а вспомнил с появлением статьи на хабре про Монте-Карло. Может кому-нибудь и пригодится. Всех с праздником 🙂

ссылка на оригинал статьи http://habrahabr.ru/post/192448/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *