Несмотря на то, что PHP не является научным языком и редко используется в исследовательских целях, метод Монте-Карло легко может быть реализован и на нём. И в данной статье я покажу как это сделать.
Задача из реальной жизни
Пару дней назад у меня должна быть встреча в 9 часов утра, за 100 миль от моего дома. В 6.30 утра я проснулся, оделся и пока я завтракал, я начал прикидывать в блокноте ближайшие пару часов. Я, как обычно, хотел приехать вовремя, поэтому я начал набрасывать маршрут: выезд из города, проселочная дорога, затем по штату на север, на восток, местная дорога на восток, проехать город, затем на снова на север и прибытие в город. Все это выглядело как-то так:
Моя жена заполнила бак прошлым вечером и я спокойно мог ехать по проселочной дороге. Шины, казалось, были в порядке, когда я смотрел на них, но мысль о том, делать или нет остановку на 10 минут, чтобы проверить их давление, не давала мне покоя. Ведь если я остановлюсь и проверю их, то я буду уверен в их давлении, так как сейчас в нем не был уверен, да и давление в шинах может повлиять на мое движение и скорость…
Я могу выезжать раньше, например, в 6.40, но тогда моей жене придется самой отводить дочь в школу, вместо того чтобы прямиком идти на работу. Если я подожду еще 10 минут, то я могу быть у школы как раз в тот момент, когда они только открывают свои двери, тем самым избавив мою жену от неудобств, тем более что школа по пути к выезду из города и это не сильно меня задерживало.
Я вернулся к тому, что я рисовал и добавил следующее:
Выпивая вторую чашку кофе, я встал у окна. Чистое утреннее небо, легкий утренний ветерок подтверждали хороший прогноз на моем смартфоне и я подумал, что поездка в этот раз будет быстрой. Заканчивая свое планирование, я нарисовал примерное время для каждого этапа, которые я нарисовал ранее:
Ожидаемое время поездки было 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/
Добавить комментарий