Статьи

Переход от цифр к картинкам — виден монте карло!

Одна из лучших вещей в математике — это то, что она … видна. Посмотрите на оценку Монте-Карло, которую мы провели на последнем уроке: ничто не создает «вау-фактор», такой как «3.134!»

Ну … по крайней мере, я почти сказал «вау». Вроде, как бы, что-то вроде. (Чтобы быть совершенно честным, несмотря на то, что я знал, что происходит, моя настоящая реакция была «вау … это все?») Даже если бы мы получили montecarlopi.py, чтобы дать нам действительно точное значение для пи (скажем, » 3.14159, «ради аргумента) было бы лучше, чем у нас было …

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

Итак, давайте использовать наши безумные навыки питона, чтобы стать черепахой и … делать то, что делают черепахи. В этом случае они рисуют. Да, мы собираемся начать делать графику. Более того, поскольку мой обычный процесс состоит в том, чтобы опираться на то, что у нас было раньше, мы собираемся использовать модуль черепахи python, чтобы показать нам, как все идет и работает.

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

Вот montecarlopi.py:

import random
import math

def hypotenuse(x, y):
return math.sqrt(math.pow(x,2)+math.pow(y, 2))

total=0
incircle=0

random.seed(5)
while total<10000000:
if (triangle.hypotenuse(random.random(), random.random()) <= 1) : incircle+=1
total=total+1

print (incircle*4.0)/total

С одним миллионом выборок наше значение для пи оказывается довольно точным 3.1413076, что неплохо, но недостаточно для того, чтобы высадить кого-либо на Луну или что-то еще. Что было бы здорово, тем не менее, это наблюдать за тем, как оно генерируется, чтобы видеть, как оно становится все ближе и ближе к реальному значению числа пи.

Подобные вещи ужасно популярны, потому что всем нравится видеть вещи по мере их возникновения. Но будь драконами: смотреть забавно, а также очень медленно. В реальном мире, если бы нам пришлось смотреть это, мы бы сказали обновлять наши значения каждые несколько тысяч образцов, потому что в противном случае мы бы состарились и умерли до того, как это было сделано. (Между прочим, это набрано парнем, у которого седая борода, который ходит с тростью и который, как правило, выглядит так, как будто он прослушивается на роль Мафусаила, старейшего человека в Библии. Почему я выгляжу так? Потому что у меня есть неприятная склонность смотреть подобные симуляции.)

Первое, о чем я подумал, приближаясь к этому уроку, это добавить что-то, называемое наблюдателем, в мой маленький алгоритм, который будет — потенциально — вызываться каждый раз, когда генерируется новая выборка. Этот наблюдатель будет общим и, таким образом, сможет делать все, что захочу. Вот наблюдатель, который сообщает нам сгенерированную точку и как далеко она находится от центра нашего круга (называемого «источником» математическими гиками) в файле simpleobserver.py:

def observe(x,y,distance):
print "x =", x,", y =",y,",distance =",distance

Мы собираемся перейти к другому по-настоящему интересному кусочку программирования: структурам данных.

Когда вы играете в «Go Fish», сначала у вас в руках несколько карт: в программировании это набор или список. Разница между ними … ну … искусственная. Теоретически, в наборе может быть только одно из заданного значения, в то время как в списке может быть больше одного, поэтому карты, которые у вас есть в руке, являются набором, а не списком (потому что, если у вас в руке более девяти червей рука, вы обманываете … или играете с очень прикольной колодой.)

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

Мы сделаем это, создав своего рода объект (существительное!) Под названием Observer. Для нас наблюдатель — это все, что знает, как наблюдать что-то еще — в этом случае наблюдаемые вещи — это три значения. Проще говоря, наш Observer является контейнером для чего-то, что имеет определение «наблюдаем (x, y, расстояние)» выше.

Затем мы создаем еще один объект (другое существительное) под названием MonteCarloObserver. Это тип Обозревателя, который будет очень, очень важен для нас: это то, что мы будем использовать для генерации числа Пи!

Наконец, мы создаем своего рода драйвер для всего этого, объект MonteCarlo, который создает для нас MonteCarloObserver и помещает его во внутренний список наблюдателей. (Это также позволяет нам добавлять других наблюдателей в список.)

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

import random

class Observer:
def __init__(self):
return

def observe(self,x,y,distance):
return

class MonteCarloCalculator(Observer):
def __init__(self):
self.total=0
self.incircle=0

def observe(self,x,y,distance):
self.total+=1
if distance < 1: self.incircle+=1

def pi(self):
return (self.incircle*4.0)/self.total

class MonteCarlo:
def __init__(self):
self.calc=MonteCarloCalculator()
self.observers=[self.calc]
def addlistener(self, ob):
self.observers.append(ob)

def generate(self, count=1000):
total=0
while(total<count):
x=random.random()
y=random.random()
d=math.sqrt(math.pow(x,2)+math.pow(y,2))
for f in self.observers: f.observe(x,y,d)
total=total+1
return self.calc.pi()

Определение __init __ (self) называется «конструктор» или «инициализатор». Всякий раз, когда мы создаем объект данного типа, вызывается «__init__» этого типа, чтобы мы могли быть уверены, что объект готов для использования.

Каждый раз, когда вы видите ссылку «self», это означает, что Python будет использовать значение объекта, поэтому «self.observers» относится к списку, который содержится в каждом экземпляре MonteCarlo, а self.calc ссылается на MonteCarloCalculator, который создается для каждого нового Монте-Карло.

Назначение «self.observers» — использует []. Это сообщить Python, что это список. (В противном случае он считает, что значение является простым значением, и это именно то, чего мы не хотим.)

Функция generate () тоже особенная — по многим причинам.

С одной стороны, «count = 1000» означает, что нам не нужно указывать, сколько итераций нужно выполнить — если мы не дадим ему одну, она будет использовать 1000 в качестве значения по умолчанию.

То, что генерирует (), является сердцем всего, что мы пытаемся сделать. Он генерирует точку (с точностью до количества точек, которые мы просили) и расстояние до этой точки от начала координат (0,0). Это основа метода Монте-Карло, конечно, никакой магии там нет. Но тогда это делает что-то странное:

for f in self.observers: f.observe(x,y,d)

Это означает, что для каждого Обозревателя в нашем списке наблюдателей вызывайте метод Обозревателя «Наблюдать». Это позволяет нам делать что-то особенное на каждом звонке. Мы можем, например, вывести текущий расчет числа пи или … нарисовать что-то на экране, показывая нам наши образцы по мере их генерирования. Последнее, что он делает — возвращает вычисление числа пи.

Использование этой вещи немного сложнее, чем раньше. Если мы поместим этот код в файл с именем «mcp2.py», мы используем его следующим образом:

$ python
Python 2.5.1 (r251:54863, May 18 2007, 16:56:43)
[GCC 3.4.4 (cygming special, gdc 0.12, using dmd 0.125)] on cygwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import mcp2
>>> cl=mcp2.MonteCarlo()
>>> cl.generate()
3.172
>>> [CTRL-D]

Мы импортируем модуль, затем создаем экземпляр класса MonteCarlo, а затем говорим ему сгенерировать.

Это чертовски много вещей, которые нужно пройти, прежде чем мы даже нарисовали одну классную линию на экране!

Однако теперь у нас есть все для всего, что нам нужно. По сути, все, что нам нужно сделать, — это создать наблюдателя, который рисует наши результаты в окне, и другого наблюдателя, который отображает значение pi как его сгенерированное, чтобы мы могли видеть, как мы приближаемся к тому, что мы думаем о pi.

Давайте сделаем наблюдателя, который показывает нам пи с течением времени, просто для простоты. Давайте сохраним это в «runningpi.py»:

import mcp2

class RunningTotal(mcp2.MonteCarloCalculator):
def __init__(self):
mcp2.MonteCarloCalculator.__init__(self)
self.counter=0

def observe(self, x, y, distance):
mcp2.MonteCarloCalculator.observe(self, x, y, distance)
self.counter=self.counter+1
if self.counter == 100:
print self.pi()
self.counter=0

Теперь мы можем использовать этот класс, как в следующем примере:

$ python
Python 2.5.1 (r251:54863, May 18 2007, 16:56:43)
[GCC 3.4.4 (cygming special, gdc 0.12, using dmd 0.125)] on cygwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import mcp2
>>> import runningpi
>>> cl=mcp2.MonteCarlo()
>>> cl.addlistener(runningpi.RunningTotal())
>>> cl.generate()
3.04
3.18
3.16
3.15
3.144
3.13333333333
3.10285714286
3.13
3.12444444444
3.128
3.1280000000000001
>>> [CTRL-D]

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

Вы знаете, у нас есть много текста здесь … и до сих пор нет графики! (Черт эти несколько концепций!)

Наконец — давайте сгенерируем один последний файл, который называется «graphicpi.py». Мы будем использовать встроенную в Python графику черепахи:

 

import mcp2
import turtle

class PiGraph(mcp2.MonteCarloCalculator):
def __init__(self):
mcp2.MonteCarloCalculator.__init__(self)
self.scale=200

# create a graphics area, make the turtle non-slow
turtle.setup(width=2*self.scale+10, height=2*self.scale+10)
turtle.speed('fastest')
turtle.tracer(0)

# draw a square!
turtle.down()
turtle.forward(self.scale)
turtle.left(90)
turtle.forward(self.scale)
turtle.left(90)
turtle.forward(self.scale)
turtle.left(90)
turtle.forward(self.scale)
turtle.left(90)
# draw a circle
turtle.forward(self.scale)
turtle.left(90)
turtle.circle(self.scale, 90)
turtle.up()
turtle.goto(0,0)

def observe(self, x, y, distance):
turtle.up()
turtle.goto(x*self.scale, y*self.scale)
turtle.color(0,0,1)
if distance>1 : turtle.color(1,0,0)
turtle.down()
turtle.forward(1)
turtle.up()

Теперь давайте запустим нашу симуляцию, используя следующие команды, и посмотрим, что вы получите — и посмотрим, сможете ли вы выяснить, как и почему.

$ python
Python 2.5.1 (r251:54863, May 18 2007, 16:56:43)
[GCC 3.4.4 (cygming special, gdc 0.12, using dmd 0.125)] on cygwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import mcp2
>>> import runningpi
>>> import graphicpi
>>> cl=mcp2.MonteCarlo()
>>> cl.addlistener(runningpi.RunningTotal())
>>> cl.addlistener(graphicpi.PiGraph())
>>> cl.generate(10000)

Это займет некоторое время, но это интересно смотреть! Используйте большие значения для большей точности и более длительного — в некоторых случаях, гораздо более длительного — времени выполнения.