Имитационное моделирование и метод Монте-Карло

Когда формулу вывести трудно, на помощь приходит случайность: тысячи «бросков» дают ответ, который не получить аналитически.

Метод Монте-Карло — способ приближённого решения задач путём многократного случайного эксперимента и усреднения результатов.

Зачем это нужно

Некоторые системы слишком сложны для точных формул: очередь в банке, движение частиц, поведение рынка. Их моделируют имитационно — воспроизводят шаг за шагом, включая случайные события. А метод Монте-Карло превращает случайность в инструмент: бросая «виртуальные кости» много раз, можно вычислить площадь, вероятность, даже число π. Это рабочий метод физиков, финансистов и инженеров — и красивое применение того, что вы уже знаете о случайных числах.

В этом методе есть что-то парадоксальное: случайность, которую обычно считают источником ошибок и непредсказуемости, здесь становится точным вычислительным инструментом. Как такое возможно? Ответ — в законе больших чисел: отдельный случайный эксперимент непредсказуем, но среднее по огромному их числу сходится к вполне определённой величине. Один бросок монеты ничего не говорит, но миллион бросков почти наверняка дадут долю орлов, очень близкую к половине. Метод Монте-Карло эксплуатирует это: задачу, у которой есть точный, но трудновычислимый ответ, он заменяет случайным экспериментом, среднее которого равно этому ответу. Своё название метод получил в 1940-х от физиков Манхэттенского проекта — в честь знаменитого казино в Монако, символа азарта и случая. С тех пор он стал одним из самых универсальных инструментов вычислительной науки именно потому, что не требует выводить формулу: достаточно уметь смоделировать один случайный исход и повторить это много раз.

Случайность как инструмент

Модуль random даёт псевдослучайные числа. Чтобы результаты были воспроизводимыми (одинаковыми при каждом запуске для проверки), задают «зерно» генератора через seed. Смоделируем тысячу бросков кубика и проверим, что частоты близки к ожидаемым:

import random
random.seed(42)        # фиксируем для воспроизводимости

counts = {i: 0 for i in range(1, 7)}
N = 6000
for _ in range(N):
    roll = random.randint(1, 6)
    counts[roll] += 1

print("грань : выпало раз (ожидалось ~1000)")
for face in range(1, 7):
    print(f"  {face}   : {counts[face]}")

Вывод:

грань : выпало раз (ожидалось ~1000)
  1   : 970
  2   : 974
  3   : 989
  4   : 1001
  5   : 999
  6   : 1067

Частоты колеблются около 1000 — закон больших чисел в действии: чем больше испытаний, тем ближе к теории.

Оцениваем число π броском точек

Вот жемчужина метода Монте-Карло. Впишем четверть круга в единичный квадрат. Будем бросать случайные точки в квадрат: доля попавших внутрь четверти круга равна отношению их площадей — π/4. Значит, π ≈ 4 × (доля попаданий). Никакой формулы круга — только случайные точки и арифметика:

import random
random.seed(1)

N = 100000
inside = 0
for _ in range(N):
    x = random.random()     # точка в квадрате [0,1)×[0,1)
    y = random.random()
    if x * x + y * y <= 1:   # попала в четверть круга?
        inside += 1

pi_estimate = 4 * inside / N
print(f"бросков: {N}")
print(f"попало внутрь: {inside}")
print(f"оценка пи: {pi_estimate}")
print(f"точное пи: 3.14159...")

Вывод:

бросков: 100000
попало внутрь: 78446
оценка пи: 3.13784
точное пи: 3.14159...

Два верных знака без единой формулы площади круга! Точность растёт с числом бросков (чтобы добавить знак, бросков нужно примерно в 100 раз больше) — это и есть мощь и одновременно цена метода Монте-Карло.

Имитация процесса: очередь к кассе

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

import random
random.seed(7)

queue = 0
total_queue = 0
served = 0
minutes = 60
for minute in range(minutes):
    if random.random() < 0.6:      # с вероятностью 0.6 пришёл клиент
        queue += 1
    if queue > 0 and random.random() < 0.5:   # касса обслужила одного
        queue -= 1
        served += 1
    total_queue += queue

print("обслужено клиентов:", served)
print("средняя длина очереди:", round(total_queue / minutes, 2))
print("осталось в очереди:", queue)

Вывод:

обслужено клиентов: 31
средняя длина очереди: 5.1
осталось в очереди: 8

Меняя вероятности прихода и обслуживания, можно подобрать, сколько касс нужно, чтобы очередь не росла — реальная задача для магазина.

Попробуй сам

Классический вероятностный сюжет: «парадокс дней рождения». В группе из 23 человек вероятность совпадения дней рождения у кого-то превышает 50% — это противоречит интуиции. Проверим имитацией, повторив «формирование группы» много раз:

import random
random.seed(3)

def has_match(group_size):
    birthdays = [random.randint(1, 365) for _ in range(group_size)]
    return len(set(birthdays)) < group_size   # есть совпадение?

trials = 10000
group = 23
matches = sum(has_match(group) for _ in range(trials))
print(f"группа {group} человек, испытаний {trials}")
print(f"совпадение было в {matches} случаях")
print(f"оценка вероятности: {matches / trials:.2%}")

Вывод:

группа 23 человек, испытаний 10000
совпадение было в 5071 случаях
оценка вероятности: 50.71%

Частые ошибки

  • Слишком мало испытаний. Монте-Карло точен лишь при большом числе экспериментов; на сотне бросков оценка π будет грубой.
  • Забывают про seed при отладке. Без фиксации зерна результат меняется каждый запуск, и трудно сравнивать.
  • Считают оценку точным ответом. Результат Монте-Карло — приближение со случайной погрешностью, а не точное значение.
  • Неверно задают условие попадания. Для четверти круга условие именно x² + y² ≤ 1, а не x + y ≤ 1.

Итоги

  • Имитационная модель воспроизводит процесс по шагам, включая случайные события.
  • Метод Монте-Карло решает задачи многократным случайным экспериментом и усреднением.
  • Оценка π броском точек: π ≈ 4 × доля точек, попавших в четверть круга.
  • Точность растёт с числом испытаний (закон больших чисел); seed делает результат воспроизводимым.
Проверьте себя
1. На чём основан метод Монте-Карло?
AНа точном решении уравнений
BНа многократном случайном эксперименте и усреднении результатов
CНа сортировке данных
DНа построении графа
2. Как из доли точек, попавших в четверть круга, получить оценку π?
AУмножить долю на 2
BУмножить долю на 4
CРазделить долю на 4
DВозвести долю в квадрат
3. Зачем при моделировании со случайностью задают random.seed()?
AЧтобы ускорить генерацию чисел
BЧтобы результаты были воспроизводимыми при повторных запусках
CЧтобы числа стали по-настоящему случайными
DЭто обязательное требование Python
Поддержать проект