Статьи

Запуск симуляции Монте-Карло в PHP

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

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

Проблема реального мира

Позавчера у меня была встреча в 9:00, примерно в 100 милях от моего дома. В 6:30 я проснулся и оделся, а во время завтрака я начал тренироваться следующие несколько часов на блокноте. Я, очевидно, хотел прибыть на встречу безопасно и вовремя, поэтому я начал делать набросок разбивки на восемь этапов: городской выезд, проселочная дорога, государственная дорога на север, государственная дорога на восток, государственная трасса на восток, городской переход, государственная трасса на север и городское прибытие. Эскиз более или менее выглядел так:

montecarlo1

Накануне вечером моя жена наполнила бензобак, и в идеале я мог выехать прямо на проселочную дорогу. Когда я смотрел на них, шины казались нормальными, но сомневаюсь, что мне нужно было сделать 10-минутный обход, чтобы должным образом проверить их давление. Если бы я остановился и проверил шины, я был бы уверен в их давлении. Иначе мне пришлось бы путешествовать с неопределенностью. Плохое давление в шинах может повлиять на мою устойчивость и скорость вождения.

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

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

montecarlo2

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

montecarlo3

Ожидаемое время в пути составило 115 минут (1 час 55 минут), которые я мог покрыть без остановок. Мой ETA был 8:35, если я уехал прямо на дорогу, и 8:55, если я решил взять мою дочь в школу и проверить шины.

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

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

Городское движение в городе, через который я должен был перейти, было намного легче, чем обычно, и мне понадобилось не более 10 минут, чтобы пересечь его. И в нескольких милях от шоссе на юг туман поднялся, и я смог ехать на полной легальной скорости. Но когда я приблизился к месту назначения, я понял, что некоторые дорожные работы, которые велись, заблокировали выезд, который я планировал сделать. Это добавило еще 10 минут к моему времени в пути и, разумеется, я опоздал.

Моделирование поездки в PHP

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

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

<?php 
class MyTrip 
{ 
    protected $departureTime; 
    protected $meetingTime; 
    protected $travelTimes; 

    public function __construct() { 
        $this->setDepartureTime('0640'); 
        $this->setMeetingTime('0900'); 

        // travel times in minutes between milestones 
        $this->setTravelTimes(array( 
            'AB' => 17, 
            'BC' => 17, 
            'CD' => 36, 
            'DE' => 9, 
            'EF' => 15, 
            'FG' => 15, 
            'GH' => 6 
        )); 
    } 

    // for convenience convert time string to minutes past
    // midnight 
    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) {
    // calculate the sum of travel times between milestones
    $travelTime = array_sum($this->travelTimes);

    // add delay if dropping kid off at school
    $schoolDelay = ($stopAtSchool) ? 10 : 0;

    // add delay if checking tire pressure
    $tiresDelay = ($checkTires) ? 10 : 0;

    // find the total schedule baseline
    $meetingArriveTime = $this->departureTime + $travelTime +
        $schoolDelay + $tiresDelay;

    // does the traveller make the meeting on time?
    $arriveOnTime = $meetingArriveTime <= $this->meetingTime;

    return array($meetingArriveTime, $this->meetingTime,
        $arriveOnTime);
}

Теперь все, что нам нужно сделать, это создать экземпляр класса и попросить его проверить мой план:

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

Учитывая значения по умолчанию, выше будет выводить, что мой первоначальный план был в порядке:

  массив
 (
     [0] => 535
     [1] => 540
     [2] => 1
 ) 

Я должен быть там через 535 минут после полуночи, а встреча происходит через 540 минут после полуночи. Согласно исходным данным, я прибуду на собрание в 8:45, всего за 5 минут до назначенного времени!

Но как насчет неизбежных изменений? Как мы можем объяснить неопределенные элементы?

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

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

Мы также можем назначить 50% вероятности, что оба решения бросят мою дочь в школу и проверят шины. Снова нам помогает функция rand()

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

Собрав все это вместе, мы можем определить метод checkPlanRisk()

 <?php
public function checkPlanRisk() {
    // adjust and sum travel times between milestones
    $travelTime = 0;
    foreach ($this->travelTimes as $t) {
        $travelTime += $t * rand(90, 125) / 100;
    }

    // decide whether to drop kid off at school and randomly set
    // the delay time
    $schoolDelay = 0;
    if (rand(1, 100) > 50) {
        $schoolDelay = 10 * rand(90, 125) / 100;
    }
    
    // ditto for checking tires
    $tiresDelay = 0;
    if (rand(1, 100) > 50) {
        $tiresDelay = 10 * rand(90, 125) / 100;
    }

    // find the total schedule baseline
    $meetingArriveTime = $this->departureTime + $travelTime +
        $schoolDelay + $tiresDelay;

    // does the traveller make the meeting on time?
    $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 "Trial: " . $i;
        echo " School delay: " . $result[0];
        echo " Tire delay: " . $result[1];
        echo " Enroute time: " . $result[2];

        if ($result[4]) {
            echo " -- Arrive ON TIME";
        }
        else {
            echo " -- Arrive late";
        }

        $confidence = $arriveOnTime / $i;
        echo "nConfidence level: $confidencenn";
    }
}

Создать новый экземпляр MyTrip

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

Результатом будет распечатка с 1000 записей, например:

  Пробная версия: 1000 Школьная задержка: 0 Задержка шин: 11,3 Время в пути: 530,44 - Прибытие по времени
 Уровень доверия: 0.716 

С указанными выше параметрами уровень достоверности, по-видимому, приближается к 0,72, что примерно указывает на то, что у путешественника есть 72% шансов попасть на встречу вовремя.

Grosso modo , Monte Carlo полагается на сходимость среднего результата последовательности идентичных экспериментов. Средний результат также известен как ожидаемое значение и может рассматриваться как вероятность достижения желаемого результата. Конечно, это довольно старая концепция, и такие симуляции были сделаны задолго до появления цифрового компьютера.

Вывод

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

Код для этой статьи доступен на GitHub.

Изображение через Fotolia