viernes, 1 de julio de 2011

Generacion de numeros Aleatorios con Distribucion LogNormal

Hace unos dias en la facultad, en la clase de programacion y simulacion nos pidieron crear una funcion en algun lenguaje de nuestra eleccion que generara numeros aleatorios con una cierta distribucion. En nuestro caso, las mediciones que obtuvimos del proceso que queremos simular siguen una distribucion LogNormal.

Existen algunos metodos para generar numeros. Primeramente implementamos el metodo de la transformacion inversa que consiste en generar un numero aleatorio con distribucion uniforme entre 0 y 1. La mayoria de los lenguajes de programacion cuentan con alguna funcion tipo Random o Rnd que genere numeros aleatorios como estos. Despues, una vez obtenido el numero aleatorio, se calcula el valor de la variable aleatoria X para el cual la funcion de densidad acumulada sea mayor o igual al valor que generamos. Si se conoce la funcion F(X) es facil despejar la X, pero si la F(X) es desconocida o muy dificil de calcular se puede aproximar utilizando sumatorias. Se inicia en un valor pequeno mayor a 0 (para el caso de la distribucion lognormal), y se va incrementando usando un delta tambien pequeno (digamos, 0.001) y se van sumando las areas de los rectangulos hasta completar el valor generado entre 0 y 1. Los valores obtenidos con este metodo seguiran una distribucion logaritmica normal.

Y como este es un blog de programacion tambien, pongo este programa aqui por si a alguien le llega a servir algun dia. A decir verdad, estos algoritmos se necesitan en la programacion de video juegos, quiza en un juego tipo tetris estariamos usando una distribucion uniforme para enviar piezas al jugador. Y tal vez, para un juego de estrategia podriamos usar una distribucion normal. En fin, la distribucion sera elegida dependiendo de cada situacion.

Esto fue desarrollado en Visual Studio, usando el lenguaje C#. Muy similar al Java. Este es un codigo lento y necesita optimizacion. Pero se podrian generar, digamos unos 400 numeros aleatorios y almacenarlos en memoria. Utilizaria al rededor de 400 * 4 = 1600 bytes en memoria RAM si fueran enteros de 32 bits, pero no se necesitaria gastar tiempo de procesador en obtener el siguiente numero. Tengamos en cuenta que se necesitan obtener 400 numeros de una poblacion infinita para que esta muestra conserve las caracteristicas de la poblacion completa. Alguna vez vieron 100 mexicanos dijeron? Los estadisticos insisten en que con 4 de esos programas de television se conoceria la opinion de todo mexico. Siempre y cuando, por supuesto, todo Mexico tenga la misma probabilidad de ser entrevistado.


Los demas detalles acerca de la distribucion LogNormal se encuentran facilmente en wikipedia. Aqui esta el codigo:

        private double LogNormal(double R, double mu, double sigma)
        {
            double deltha = 0.001;
            double actual_R = 0;
            double X = 0;

            double eval1 = EvalLogNormal(X, mu, sigma);

            while (actual_R < R)
            {
                double eval2 = EvalLogNormal(X += deltha, mu, sigma);
                actual_R += deltha * (eval1 + eval2) / 2;

                eval1 = eval2;
            }

            return X;
        }

        private double EvalLogNormal(double X, double mu, double sigma)
        {
            if (X == 0) return 0;
           
            double c1 = 1 / (sigma * X * Math.Sqrt(2 * Math.PI));
            double c2 = -(0.5) * Math.Pow((Math.Log(X) - mu) / sigma, 2);
            return c1 * Math.Exp(c2);
        }
 
        private void btnGenerar_Click(object sender, EventArgs e)
        {
            Random rnd = new Random(System.Environment.
TickCount);

            double media = Convert.ToDouble(txtMedia.Text);
            double desviacionStandar = Convert.ToDouble(txtDesviacionStandar.Text);
            double varianza = desviacionStandar * desviacionStandar;

            double var = Math.Log(varianza / (media * media) + 1);
            double sigma = Math.Sqrt(var);
            double mu = Math.Log(media) - (0.5) * Math.Log(varianza / (media * media) + 1);

            int cantidadNumeros = Convert.ToInt16(txtCantidad.Text);

            progressBar1.Minimum = progressBar1.Value = 0;
            progressBar1.Maximum = cantidadNumeros;

            txtResultados.Text = "";

            for (int k = 0; k < cantidadNumeros; k++)
            {
                txtResultados.Text += LogNormal(rnd.NextDouble(), mu, sigma).ToString("##.###") + "\r\n";

                progressBar1.Value++;
                Application.DoEvents();
            }

            progressBar1.Value = 0;
        }