Programación Temporal de Eventos con Python

Este artículo estuvo en mi lista de borradores (draft posts) por mucho tiempo ya. Evidente mente no cumplí con la consigna release early del desarrollo tipo bazar. Enough!

Este artículo va a hablar un poco de simulación de sistemas discretos, va a presentar un módulo que escribí como prueba de que se podía hacer y me va a servir para contarles algunas cosas uq e aprendí en el camino.

Sigo Aprendiendo Python y esta es una clase más.

Programación temporal de eventos

En el primer cuatrimestre de este año cursé una materia llamada Simulación. Estudiamos principalmente como simular sistemas discretos (como un grupo de pasajeros subiendo a un avión. Suben de a uno, en un momento hay 7 pasajeros a bordo y en otro momento hay 8, nunca hay 7.5) en contraposición a los sistemas continuos (como el tanque de nafta del avión dónde su nivel baja instante a instante cuando el avión está volando).

Básicamente existen dos tipos de técnicas que se pueden utilizar para simular sistemas discretos, las de orientación al intervalo y las de orientación al evento. En las del primer tipo, cada cierto tiempo t se verifica de alguna forma si ha ocurrido un evento y se realiza el procesamiento asociado a este. Esto lleva a impresiciones y en ocasiones a un gasto innecesesario de cpu. En las técnicas de orientación al evento estos problemas no existen.

Una de las técnicas más primitivas de orientación al intervalo (no se si se usa en la actualidad) es la de Programación Temporal de Eventos. Básicamente se tiene un reloj que cuenta el tiempo de simulación y una lista de eventos ordenada en el tiempo. Uno debe escribir una función o procedimiento por cada uno de los eventos principales del sistema (por ejemplo la llegada de una pieza a una máquina) y una función principal que contenga el main loop de la simulación. Esta irá tomando eventos de la lista, avanzará el tiempo de simulación según corresponda y los ejecutará. Siempre se toma el primero y se lo ejecuta, si no hay más elementos en la lista termina la siumulación.

Los eventos son agregados a la lista con una primitiva llamada por ejemplo PPE (programar próximo evento), dónde además de indicar cual será el próximo evento, se debe indicar dentro de cuanto tiempo a partir del actual se debe ejecutar.

Mientras cursaba, estudiaba y rendía pensaba que sería muy fácil implementar en Python un módulo que provea las primitivas necesarias para realizar una simulción por programación temporal de eventos. Poner y sacar funciones de una lista seguro sería sencillo en un lenguaje muy dinámico y donde todo (incluidas las funciones) son objetos. La semana siguiente a rendir estuve escribiendo un poco de código Python. El resultado es un módulo llamado pte que cubre las funcionalidades mínimas que yo pretendía.

Resultado

No pretendía realizar un imponente desarrollo ni pienso que alguien use esto alguna vez en serío. Es más bien una prueba de concepto (Proof of concept). Lo había avandonado y ni siquiera funcionaba sin tirar errores. Objetivo: que funcione.

Este es el módulo: pte.py (versión orginal)
Una versión con el consejo de Daniel: pte-d.py (gracias Daniel!)
Y este un ejemplo en el que lo uso: pte_ej2.py

Ejemplo de ejecución:

juanjo@sarge:~/python$ ./pte_ej2.py
 
500 piezas fueron aceptadas en 1247.55 unidades de tiempo.
 
Máximo tamaño de la cola para procesamiento 5.

Qué aprendí

Módulos

Más de lo que pueda decir sobre módulos: http://docs.python.org/tut/node8.html

reload(modulo)

Reload a previously imported module. The argument must be a module object, so it must have been successfully imported before. This is useful if you have edited the module source file using an external editor and want to try out the new version without leaving the Python interpreter. The return value is the module object (the same as the module argument).

Entonces lo que hay que hacer cuando se está trabajando en la consola de Python[1] es, primer importar el módulo que se quiere probar:

>>> import modulo

Hacer algunas pruebas:

>>> modulo.var1
1
>>> modulo.cuad(3)
9

Si luego editamos el texto de modulo y queremos probar la función que agregarmos:

>>> modulo = reload(modulo)

Funciones como objetos

En Python todo es un objeto, incluso las funciones. Sabiendo esto y teniendo en mente lo que quería hacer (en la definición de una función, explicitar que esta se llame en el futuro, es decir almacenarla en una lista de eventos) hice una pequeña prueba en el intérprete:

>>> l = []
>>> def f():
	l.append(f)
 
 
>>> l
[]
>>> f()
>>> l
[<function f at 0x418bce9c>]
>>> l[0]
<function f at 0x418bce9c>
>>> l
[<function f at 0x418bce9c>]
>>> l[0]()
>>> l
[<function f at 0x418bce9c>, <function f at 0x418bce9c>]

Esto es posible justamente gracias a que en Python las funciones son objetos. En ciencias de la computación esto se conoce como Funciones de Primera Clase, es decir que las funciones sean Objetos de Primera Clase, es decir que puedan ser creadas durante la ejecución de un programa, almacenadas en estructuras de datos, recuperadas más tarde para usarlas, pasadas como argumentos y retornadas en otras funciones. Más al respecto en wikipedia.

Ordenar según un criterio propio

Una lista puede ser ordenada según un criterio propio pasándole al método sort de una lista ese criterio en forma de función:

def _comparar_tiempos(a,b):
    """ Función auxiliar usada para ordenar los pares eventos,tiempos
    en la lista de eventos.
    """
    t1 = a[1]
    t2 = b[1]
    if (t1 > t2): return 1
    elif (t1 < t2): return -1
    else: return 0

y luego..

eventos.sort(_comparar_tiempos)

Espero les haya resultado interesante!

[1] También conocida como REPL, algo muy piola cuando uno esta desarrollando.

Acerca de Juanjo

Mi nombre es Juanjo Conti, vivo en Santa Fe y soy Ingeniero en Sistemas de Información. Mi lenguaje de programación de cabecera es Python; lo uso para trabajar, estudiar y jugar.
Esta entrada fue publicada en Aprendiendo Python, Facultad y etiquetada . Guarda el enlace permanente.
  • Juanjo
    Me quedó la duda de si lo de -1, 0 y 1 me lo había imaginado o realmente había leido que debía ser así:


    >>> help([].sort)
    Help on built-in function sort:

    sort(...)
    L.sort(cmpfunc=None) -- stable sort *IN PLACE*; cmpfunc(x, y) -> -1, 0, 1


    Lo había leido, de todas formas la otra forma también funciona:


    >>> l = range(10)
    >>> l
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    >>> l.sort(lambda a, b: b - a)
    >>> l
    [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
    >>> l.reverse() # hace lo mismo que el sort tuneado de más arriba
    >>> l
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
  • Juanjo
    Gracias Daniel! mejoro el código con tu consejo!

    Juanjo

    PS: si, salió bien. Pero te cambié las marcas <code> por <pre> para que se respete el indentado en el código.
  • Daniel Moisset
    Se puede simplificar un poco la función de comparación. Los valores de resultado no necesariamente deben ser -1, 0 y +1, alcanza con que devuelvas negativo, 0, positivo. con lo que podés hacer:


    def _comparar_tiempos(a,b):
    """ Función auxiliar usada para ordenar los pares eventos,tiempos
    en la lista de eventos.
    """
    t1 = a[1]
    t2 = b[1]
    return t1 - t2


    y cuando se simplifica tanto, por ahí podrías no definirla y directamente usar:


    eventos.sort (lambda e1,e2:e1[1]-e2[1])


    que es una función que hace lo mismo pero con notación lambda la ponés inline...

    [espero que esto quede bien, no hay botón de preview]
  • Muy bueno el post. Que bueno estaría mas adelante poder utilizar estos conocimientos de simulación adquiridos para algún proyecto. Lo mismo digo con lo que estamos aprendiendo en Sistemas de Gestión I.

    También muy interesante (cada vez más), Python...
blog comments powered by Disqus