.. -*- coding: utf-8 -*- .. index:: Monte Carlo Experimentos con numeros aleatorios =================================== En la primera sesión de este bloque aprendimos a recorrer exhaustivamente un conjunto de posibilidades para contar las que satisfacían una propiedad, y así calcular probabilidades como el número de casos favorables dividido por el número de casos posibles. En algunos casos el número de posibilidades es enorme, y el resultado, realmente mucho más preciso de lo que necesitábamos. Una alternativa consiste en calcular sólo unos cuantos casos extraídos aleatoriamente, y hacer una estadística de los casos favorables entre los posibles. A esta estrategia se le llama en general el **método de Monte Carlo**, por su uso del azar. Aunque el resultado sólo es aproximado, es fácil controlar el tiempo de cómputo que destinamos a aproximar la solución. Por supuesto, en muchos casos es posible hacer el cálculo exacto mediante razonamiento puro y, aún en los casos en que no es así, el razonamiento puede servir para reducir el número de posibilidades a explorar, o hacer más eficiente un método de Monte Carlo. Ejemplo: galletas con pasas ::::::::::::::::::::::::::: Como ejemplo, nos planteamos este problema: partiendo de una masa para hacer galletas que contiene un total de P pasas, hacemos G galletas. Asumiendo que cada pasa terminará en una de las G galletas con la misma probabilidad y de forma independiente a la galleta de destino de las otras pasas, calcula la probabilidad de que cada galleta tenga al menos *k* pasas. Para distribuir las pasas de todas las formas posibles, contamos cada posible lista de P valores .. MATH:: [G_1,G_2,\dots,G_P] donde el primer valor indica la galleta en que termina la primera pasa, y así sucesivamente. Como cada pasa puede terminar en una de las G galletas posibles, el número total de posibilidades es :math:`G^P`. :: sage: def pasas(G,P,k): ... '''Calcula cuantas formas de repartir P pasas entre G galletas ... dejan al menos k pasas en cada galleta ... ''' ... favorables = 0 ... #Cada pasa termina en una galleta distinta, total G^P posibilidades ... for j in srange(G^P): ... #lista que indica en que galleta esta cada pasa ... donde_esta_la_pasa = j.digits(base=G,padto=P) ... #G galletas, que comienzan sin pasas ... pasas_en_cada_galleta = [0]*G ... #Contamos el numero de pasas en cada galleta ... for g in donde_esta_la_pasa: ... pasas_en_cada_galleta[g] += 1 ... if min(pasas_en_cada_galleta)>=k: ... favorables += 1 ... return favorables/G^P :: sage: %time sage: pasas(4,6,1) 195/512 CPU time: 0.17 s, Wall time: 0.19 s :: sage: %time sage: pasas(3,7,1) 602/729 CPU time: 0.09 s, Wall time: 0.10 s :: sage: %time sage: pasas(4,7,1) 525/1024 CPU time: 0.66 s, Wall time: 0.67 s Como vemos, este enfoque requiere mucho tiempo incluso para cantidades pequeñas de pasas y de galletas. No en vano hay que iterar el bucle principal ¡¡G^P veces!!. Como el problema no tiene mucha gracia si el número de pasas no es al menos igual al número de galletas, estamos hablando de crecimiento exponencial. Aunque se pueden usar varios trucos para reducir el tiempo de cómputo, sigue siendo un crecimiento muy rápido. Usando el método de Monte Carlo, decidimos exactamente el número de veces que iteramos el bucle. *Nota:* este problema se puede resolver de forma exacta, pero no es trivial. *Nota:* la lista de galletas de cada pasa contiene información redundante para este problema, en el que sólo nos interesa el total de pasas en cada galleta. Por ejemplo, podemos usar ``IntegerVectors`` para recorrer las listas de enteros de longitud G que suman P (el número de pasas en cada galleta), pero distintas formas de recorrer el conjunto pueden responder a distintos modelos de probabilidad. :: sage: def pasas_mc(G,P,k,T): ... '''Calcula cuantas formas de repartir P pasas entre G galletas ... dejan al menos k pasas en cada galleta, usando un método de ... Monte Carlo con muestra de tamaño T ... ''' ... favorables = 0 ... for h in xrange(T): ... #lista que indica en que galleta esta cada pasa ... #Usamos randint que devuelve un entero escogido ... #aleatoriamente en un rango ... donde_esta_la_pasa = [randint(0,G-1) for j in range(P)] ... #G galletas, que comienzan sin pasas ... pasas_en_cada_galleta = [0]*G ... #Contamos el numero de pasas en cada galleta ... for g in donde_esta_la_pasa: ... pasas_en_cada_galleta[g] += 1 ... if min(pasas_en_cada_galleta)>=k: ... favorables += 1 ... return favorables/T :: sage: #Distintas llamadas a esta funcion con los mismos argumentos sage: #no devuelven el mismo resultado, debido al uso de numeros sage: #aleatorios sage: #Cuanto mayor la muestra, menor la oscilacion sage: p = pasas_mc(4,7,1,1000) sage: print p, p.n() 63/125 0.504000000000000 .. index:: binomial negativa, randint Simulaciones :::::::::::: El método de Monte Carlo permite abordar problemas que no se pueden resolver por fuerza bruta contando todas las posibilidades. En el siguiente ejemplo sencillo, lanzamos monedas al aire (monedas equilibradas, lanzamientos independientes) hasta que acumulamos un cierto número de caras, ¿cuántos lanzamientos necesitamos en promedio para obtener al menos k caras? Podemos simular este experimento usando llamadas a ``randint(0,1)`` para simular lanzamientos de monedas. Si el número devuelto es 0, lo tomamos como una cruz, si es un 1, como una cara. Repetimos el experimento hasta que acumulemos k caras, y tomamos nota del número de lanzamientos que hemos necesitado. :: sage: def geometrica_mc(k,T): ... '''Número medio de lanzamientos hasta obtener k caras, por ... el método de Monte Carlo ... ''' ... lanzamientos = 0 ... for j in range(T): ... caras = 0 ... while caras