Вы находитесь на странице: 1из 98

MOTOR DE EJECUCIÓN DE REDES DE PETRI

Br. Demián Gutierrez


Tutor: Prof. Edgar Chacón

COMO REQUISITO PARA OBTENER


EL GRADO DE
INGENIERO DE SISTEMAS
DE LA
UNIVERSIDAD DE LOS ANDES
MÉRIDA, VENEZUELA
ABRIL 2004

c Copyright de Universidad de Los Andes, 2004


°
ii
UNIVERSIDAD DE LOS ANDES
FACULTAD DE INGENIERı́A

El jurado aprueba el proyecto de grado titulado


“Motor de Ejecución de Redes de Petri” realizado por
Br. Demián Gutierrez como requisito parcial para la obtención
del grado de Ingeniero de Sistemas.

Fecha: Abril 2004

Tutor:
Prof. Edgar Chacón

Jurado:
Prof. Eladio Dapena

Prof. Juan Cardillo


CYRANO: Pero, perdón; tengo que irme; no puedo
hacer esperar a ese rayo de luna que viene a llevarme.
(Edmundo Rostand, 1897, Cyrano de Bergerac)
Índice general

Índice de Tablas IX

Índice de Figuras X

Agradecimientos XIII

Resumen XV

1. Introducción 1
1.1. Sistemas de Eventos Discretos . . . . . . . . . . . . . . . . . . . . 1
1.2. Definición del Problema . . . . . . . . . . . . . . . . . . . . . . . 2
1.3. Motor de Ejecución de Redes de Petri . . . . . . . . . . . . . . . . 3
1.4. Objetivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.4.1. Objetivo General . . . . . . . . . . . . . . . . . . . . . . . 3
1.4.2. Objetivos Especı́ficos . . . . . . . . . . . . . . . . . . . . . 4
1.5. Descripción de los Siguientes Capı́tulos . . . . . . . . . . . . . . . 4

2. Redes de Petri 5
2.1. Introducción a las Redes de Petri . . . . . . . . . . . . . . . . . . 5
2.1.1. Definición Informal de una Redes de Petri . . . . . . . . . 5
2.1.2. Definición Formal de una Red de Petri . . . . . . . . . . . 7
2.1.3. Notación Matricial de una Red de Petri . . . . . . . . . . . 8
2.1.4. Red de Petri Pura . . . . . . . . . . . . . . . . . . . . . . 9
2.1.5. Habilitación de una Transición . . . . . . . . . . . . . . . . 10

v
2.1.6. Disparo de una Transición . . . . . . . . . . . . . . . . . . 11
2.1.7. Conflicto Estructural y Efectivo . . . . . . . . . . . . . . . 12
2.1.8. Secuencia de Disparo . . . . . . . . . . . . . . . . . . . . . 13
2.1.9. Conjunto de Marcaciones Accesibles . . . . . . . . . . . . . 14
2.1.10. Redes de Petri Acotadas . . . . . . . . . . . . . . . . . . . 14
2.2. Redes de Petri con Arcos Inhibidores . . . . . . . . . . . . . . . . 15
2.3. Máquinas de Estado y Redes de Petri . . . . . . . . . . . . . . . . 16
2.4. Redes de Petri de Alto Nivel . . . . . . . . . . . . . . . . . . . . . 17

3. Modelo Ampliado de Redes de Petri 21


3.1. Eventos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
3.2. Transiciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
3.2.1. Problemas de Conflicto . . . . . . . . . . . . . . . . . . . . 24
3.2.2. Transición Simple . . . . . . . . . . . . . . . . . . . . . . . 27
3.2.3. Transición Generadora de Eventos . . . . . . . . . . . . . . 27
3.2.4. Transición Asociada a Código Java . . . . . . . . . . . . . 28
3.3. Lugares . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
3.3.1. Marcación de un Lugar . . . . . . . . . . . . . . . . . . . . 29
3.3.2. Lugar de Fichas . . . . . . . . . . . . . . . . . . . . . . . . 30
3.3.3. Lugar de Fichas Coloreadas . . . . . . . . . . . . . . . . . 31
3.3.4. Lugar Asociada a Código Java . . . . . . . . . . . . . . . . 31
3.4. Aristas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
3.4.1. Arista de Fichas . . . . . . . . . . . . . . . . . . . . . . . . 32
3.4.2. Arista de Fichas Coloreadas . . . . . . . . . . . . . . . . . 33
3.4.3. Arista Simple . . . . . . . . . . . . . . . . . . . . . . . . . 33

4. Arquitectura del Sistema 35


4.1. Requerimientos . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
4.2. Arquitectura General del Sistema . . . . . . . . . . . . . . . . . . 37
4.2.1. El Motor de Ejecución . . . . . . . . . . . . . . . . . . . . 37
4.2.2. Ciclo de Vida de una Red de Petri . . . . . . . . . . . . . 38
4.2.3. Interfaz Remota y Local . . . . . . . . . . . . . . . . . . . 40
4.2.4. El Editor de Redes de Petri . . . . . . . . . . . . . . . . . 40
4.2.5. Los Clientes . . . . . . . . . . . . . . . . . . . . . . . . . . 40
4.3. Arquitectura del Motor . . . . . . . . . . . . . . . . . . . . . . . . 41
4.3.1. Capturador de Eventos . . . . . . . . . . . . . . . . . . . . 41
4.3.2. Repositorio de Redes de Petri . . . . . . . . . . . . . . . . 42
4.3.3. Manejador de Eventos . . . . . . . . . . . . . . . . . . . . 43
4.3.4. Manejador de Sucesos . . . . . . . . . . . . . . . . . . . . 43
4.3.5. Componentes . . . . . . . . . . . . . . . . . . . . . . . . . 43
4.4. Arquitectura del Editor . . . . . . . . . . . . . . . . . . . . . . . . 44
4.4.1. Interfaz Principal . . . . . . . . . . . . . . . . . . . . . . . 44
4.4.2. Interfaz de Administración . . . . . . . . . . . . . . . . . . 45
4.4.3. Vista . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
4.4.4. Documento . . . . . . . . . . . . . . . . . . . . . . . . . . 46

5. Implementación 47
5.1. El API de Componentes . . . . . . . . . . . . . . . . . . . . . . . 47
5.1.1. Data del Componente ComponentData . . . . . . . . . . . 48
5.1.2. Objeto Acuarela ComponentObject . . . . . . . . . . . . . 49
5.1.3. Objeto de Ejecución ComponentRuntime . . . . . . . . . . 51
5.1.4. Fábrica de Componentes ComponentFactory . . . . . . . . 52
5.1.5. Archivo Descriptor de Componentes . . . . . . . . . . . . . 53
5.2. El Documento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
5.2.1. Representación del Documento en Memoria . . . . . . . . 53
5.2.2. Representación del Documento en XML . . . . . . . . . . 54
5.3. El Motor de Ejecución . . . . . . . . . . . . . . . . . . . . . . . . 56
5.3.1. Interfaz de Administración, Capturador de Eventos . . . . 56
5.3.2. Manejador de Sucesos . . . . . . . . . . . . . . . . . . . . 58
5.3.3. Repositorio de Redes de Petri . . . . . . . . . . . . . . . . 58
5.3.4. Manejador de Eventos . . . . . . . . . . . . . . . . . . . . 60
5.4. El Editor de Redes de Petri . . . . . . . . . . . . . . . . . . . . . 62
5.4.1. Clases del Editor de Redes de Petri . . . . . . . . . . . . . 63

6. Conclusiones y Recomendaciones 65
6.1. Conclusiones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
6.2. Recomendaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . 66

A. Definición de Redes de Petri en XML 71

B. Tecnologı́as y Términos más Comunes en Java 77

C. Acrónimos 81

Bibliografı́a 82
Referencias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
Índice de cuadros

B.1. Tecnologı́as más Comunes en Java . . . . . . . . . . . . . . . . . . 80

ix
Índice de figuras

1.1. Sistemas: a) Continuos, b) Eventos Discretos . . . . . . . . . . . . 2

2.1. Componentes de una Red de Petri . . . . . . . . . . . . . . . . . . 6


2.2. Ejemplo de una Red de Petri . . . . . . . . . . . . . . . . . . . . 6
2.3. Una Red de Petri que Representa Recursos y Actividades . . . . . 7
2.4. Una Red de Petri no Pura . . . . . . . . . . . . . . . . . . . . . . 10
2.5. Habilitación de una Transición . . . . . . . . . . . . . . . . . . . . 11
2.6. Disparo de una Transición . . . . . . . . . . . . . . . . . . . . . . 12
2.7. Conflicto: a) Estructural, b) Efectivo . . . . . . . . . . . . . . . . 13
2.8. Marcaciones Accesibles de una Red de Petri . . . . . . . . . . . . 14
2.9. Efecto de un Arco Inhibidor: a) t1 Deshabilitada, b) t1 Habilitada 16
2.10. Red de Petri con un Número Infinito de Estados . . . . . . . . . . 17
2.11. Marcaciones Accesibles (infinitas) de una red de Petri . . . . . . . 18
2.12. Red de Petri de Alto Nivel . . . . . . . . . . . . . . . . . . . . . . 19
2.13. Comparación Entre Redes de Petri y Lenguajes de Programación 20

3.1. Disparo de una Transición . . . . . . . . . . . . . . . . . . . . . . 23


3.2. Conflicto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
3.3. Transición Simple . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
3.4. Transición Generadora de Eventos . . . . . . . . . . . . . . . . . . 27
3.5. Situación de Lazo Infinito . . . . . . . . . . . . . . . . . . . . . . 28
3.6. Transición Asociada a Código Java . . . . . . . . . . . . . . . . . 29
3.7. Lugar de Fichas . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31

xi
3.8. Lugar de Fichas Coloreadas . . . . . . . . . . . . . . . . . . . . . 31
3.9. Lugar Asociado a Código Java . . . . . . . . . . . . . . . . . . . . 32

4.1. Arquitectura General del Sistema . . . . . . . . . . . . . . . . . . 37


4.2. Ciclo de Vida de una Red en el Motor . . . . . . . . . . . . . . . 39
4.3. Arquitectura del Motor . . . . . . . . . . . . . . . . . . . . . . . . 42
4.4. Arquitectura del Editor . . . . . . . . . . . . . . . . . . . . . . . . 45

5.1. Diagrama de Clases del API de Componentes . . . . . . . . . . . 48


5.2. Diagrama de Clases de los Objetos Acuarela Predeterminados . . 50
5.3. Diagrama de Clases de los Renderers Acuarela Predeterminados . 51
5.4. Diagrama de Clases del Documento . . . . . . . . . . . . . . . . . 54
5.5. Diagrama de Clases de la Interfaz del Motor . . . . . . . . . . . . 57
5.6. Diagrama de Clases del Motor . . . . . . . . . . . . . . . . . . . . 59
5.7. Ejecución de un Evento (Primera Etapa) . . . . . . . . . . . . . . 61
5.8. Ejecución de un Evento (Segunda Etapa) . . . . . . . . . . . . . . 62
5.9. Diagrama de Clases del Editor . . . . . . . . . . . . . . . . . . . . 63
Agradecimientos

A mis dos grandes amores: Glorianna e IDE. A ti Glori, gracias por tu


ternura y tu amor. A ti Iride, gracias por todo lo que me has dado, por tu amor
y apoyo incondicional, sin ti no hubiera llegado a la meta. Las amo. Son la luz de
mi vida.
A mis padres, quienes también recorrieron este camino conmigo, se plantea-
ron la meta de traerme hasta aquı́ y lo lograron.
A Minotauro, un sueño que se convirtió en mi segundo hogar.
A mi compadre Esteban Añez, gracias por tu apoyo y solidaridad, pero
sobre todo, gracias por el cariño que le das a mi familia.
A mis compañeros de trabajo: Cucho, Orlando y Angel, gracias por su
amistad y compañerismo. Son como mis hermanos. Es un honor trabajar con
ustedes.
A todos aquellos profesores que ayudaron en mi formación: Pero
especialmente a Leandro, Rafael, Eladio, Edgar. A todos ustedes; Gracias!
A la ULA, gracias por haberme aceptado y acogido en su recinto. Gracias
a esta oportunidad, pude aprender muchas cosas sobre mi carrera, crecer como
persona y convertirme en un futuro profesional.

xiii
Resumen

Este documento propone y desarrolla un motor de ejecución de redes de Petri.


El objetivo fundamental del motor es su utilización para modelar sistemas de
eventos discretos (utilizando dichas redes) para un sistema de gestion de holones
y para sistemas de manufactura holónicos. Sin embargo, el motor se concibe de
la forma más amplia posible, como un software de propósito general que permita
modelar y ejecutar cualquier tipo de redes de Petri. Tales resultados se logran
por medio de una arquitectura fácilmente expandible y escalable basada en el
concepto de componentes o plug-ins, que pueden ser desarrollados e insertados
en el motor de ejecución según van surgiendo nuevas necesidades.

Descriptores Cota
Sistemas de control de supervisión *
Redes de Petri TJ222
G88

xv
Capı́tulo 1

Introducción

1.1. Sistemas de Eventos Discretos

En general, todo sistema tiene asociada una dinámica que representa los cam-
bios que pueden ocurrir en las variables del mismo. De forma que los diferentes
elementos de un sistema evolucionan de acuerdo a un conjunto de leyes propias
que describen su evolución natural, bien sea fı́sica o quı́mica, y que puede ser del
tipo x = f (x, u), en el caso de que el sistema sea continuo, o xk+1 = ft (xk , σk )
si el sistema es de dinámica discreta. Un caso especial se presenta en los siste-
mas hı́bridos, que se componen de elementos continuos y elementos discretos.
En principio, existen muchas herramientas y técnicas para modelar y predecir la
dinámica continua y discreta de un sistema. Sin embargo, desde el punto de vista
de los sistemas de eventos discretos, una que ha resultado especialmente exitosa
ha sido la utilización de redes de Petri.

En la Figura 1.1-b se muestra un ejemplo de la dinámica de un sistema de


eventos discretos. En este tipo de sistemas, el estados del mismo (lineas horizon-
tales) cambian bruscamente en instantes especificos de tiempo. Estos cambios,
generalmente, están asociados a eventos o sucesos dentro del sistema, y no siem-
pre puede predecirse el momento en que ocurren.
2 Introducción

t
a)

t
b)

Figura 1.1: Sistemas: a) Continuos, b) Eventos Discretos

1.2. Definición del Problema

En los sistemas de eventos discretos es especialmente útil poder seguirle la


pista a la evolución del sistema para poder obtener información respecto al estado
del mismo. Si un sistema de producción es pequeño (por ejemplo, una fábrica de
pequeña envergadura) es relativamente sencillo saber cual es el estado del mismo,
es decir, que máquinas están disponibles, que se está produciendo, en que etapa
de producción se encuentra la fábrica, etc. Sin embargo, cuando el sistema de
producción crece un poco, ya no es fácil determinar a simple vista la situación
exacta en la que se encuentra. Es por esto que resulta útil seguirle la pista al
sistema a medida que éste va cambiando, para que en un momento dado, sea
posible tener una idea clara del estado del mismo.
La visión “panorámica” de los sistemas de eventos discretos, brindada por
las redes de Petri, permite entre otras cosas agilizar el flujo de información de
las capas más bajas a las más altas de los sistema de producción. Esto hace que
el sistema se vuelva más eficiente y pueda responder con mayor rapidez a las
1.3 Motor de Ejecución de Redes de Petri 3

eventualidades que ocurran durante el proceso de producción. Por otra parte, al


tener una visión más amplia del sistema, es posible tomar decisiones de forma
más inteligente, incluyendo rápidamente factores que de otro modo no hubieran
sido posible considerar. Por esta razón, serı́a útil contar con una herramienta que
permita modelar y supervisar este tipo de sistemas, haciendo que la misma se
ejecute y evolucione en paralelo con el sistema real.

1.3. Motor de Ejecución de Redes de Petri


El motor de ejecución de redes de Petri es una herramienta capaz de simular
y ejecutar redes de Petri, de modo que éstas puedan acoplarse a un sistema
de eventos discretos. De esta forma, un sistema de eventos discretos modelado
utilizando este tipo de redes, puede acoplarse al motor, siendo posible hacer que el
modelo evolucione al recibir estı́mulos del sistema real, en paralelo con el mismo.
En general, esto resuelve el problema planteado en la sección 1.2. Además, el
motor permite modelar y ejecutar sistemas discretos que no son “reales”, es decir,
que no existen en el mundo fı́sico como tales, pero si en el mundo del computador
digital. Básicamente, esto último hace que el motor de redes de Petri se convierta
en una herramienta de diseño poderosa en el mundo del desarrollo de software,
ya que permite modelar e implantar fácilmente procesos (que en el fondo son
sistemas de eventos discretos) que de otra forma serı́an mucho más complejos de
codificar.

1.4. Objetivos

1.4.1. Objetivo General

Esta tesis plantea la construcción de un motor de ejecución de redes de Petri de


propósito general, que pueda ser utilizado a largo plazo para el análisis, modelado,
verificación y supervisión de sistemas de eventos discretos.
4 Introducción

1.4.2. Objetivos Especı́ficos


Definición de un formato de archivo XML que permita describir redes de
Petri.

Definición de una estructura de datos que permita manipular redes de Petri.

Diseño e implementación de una arquitectura de componentes y plug-ins


para el motor de ejecución y el editor de redes de Petri.

Diseño e implementación del motor de ejecución de redes de Petri.

Diseño e implementación del editor de redes de Petri.

1.5. Descripción de los Siguientes Capı́tulos


El capı́tulo 2 sirve de base en lo referente a la teorı́a básica de redes de Petri y el
mismo tiene su origen, en buena parte, en las publicaciones realizadas al respecto
por Janette Cardoso y Robert Valette (ver referencias bibliográficas). Por otra
parte, el capı́tulo 3 propone un modelo “ampliado” de redes de Petri, por medio
del cual se puedan expandir este tipo de redes a gusto, según sean las necesidades
del problema que se desea solucionar. Este capı́tulo, además, plantea algunos de
los posibles inconvenientes y dificultades, tanto generales como particulares que
es posible encontrar al enfrentarse a los distintos componentes de dicho modelo.
Desde este punto en adelante, comienzan abiertamente las tareas de diseño y
desarrollo del sistema, definiendo y proponiendo en el capı́tulo 4 la arquitectura
del motor de ejecución. Por otra parte, los detalles particulares a la implantación
del software son definidos en el capı́tulo 5, donde se aprecian detalles importan-
tes sobre el modelo de componentes, que resulta ser un punto clave a lo largo del
desarrollo del proyecto. Finalmente, en el capı́tulo 6 se expresan algunas conclu-
siones finales y se hacen recomendaciones que pueden permitir mejorar futuras
versiones del sistema.
Capı́tulo 2

Redes de Petri

Según (Janette Cardoso, Robert Valette, 1997), las redes de Petri son una
herramienta gráfica y matemática que se adapta bien a un gran número de apli-
caciones en que las nociones de eventos, estados y evoluciones simultáneas son
importantes.

Fueron inventadas por Carl Adam Petri en 1962, en su tesis de doctorado


titulada Comunicación con Autómatas (C. A. Petri. Kommunikation mit Au-
tomaten. PhD thesis, Institut für instrumentelle Mathematik, Bonn, 1962). En
general, fueron objeto de uso teórico hasta los 1980s, momento a partir del cual
se incrementó su uso práctico, debido principalmente a la introducción de las
redes de Petri de alto nivel y la disponibilidad de herramientas de software que
manejaban este tipo de redes.

Algunas de las aplicaciones de las redes de Petri son: Análisis y verificación


formal en los sistemas discretos, protocolos de comunicación, sistemas de trans-
porte, control de sistemas de producción y sistemas de información, entre otros.
6 Redes de Petri

2.1. Introducción a las Redes de Petri

2.1.1. Definición Informal de una Redes de Petri


Una red de Petri es un grafo dirigido bipartito que está formado por dos tipos
de nodos llamados lugares y transiciones. Los nodos están conectados por medio
de arcos, y no está permitido que dos nodos del mismo tipo estén conectados
entre sı́. Normalmente, un lugar se representa con un cı́rculo, una transición con
un cuadrado (algunas notaciones utilizan un rectángulo o lı́nea horizontal) y los
arcos con flechas dirigidas (ver Figura 2.1).

Lugar Transiciones Arco

Figura 2.1: Componentes de una Red de Petri

La Figura 2.2 muestra un ejemplo simple de una red de Petri, formada por las
transiciones t1 , t2 y t3 y los lugares p1 , p2 , p3 y p4 . La transición t3 , por ejemplo,
tiene dos lugares de entrada (p2 y p3 ) y dos lugares de salida (p3 y p4 ). Los arcos
dirigidos se encargan de definir con que lugares (entrada y salida) está asociada
una transición. En este caso, en particular, p3 es tanto un lugar de entrada de t3
como un lugar de salida.

t2

t1 [2]
p2
t3
p1 p4

p3

Figura 2.2: Ejemplo de una Red de Petri

Los lugares de una red de Petri pueden tener fichas, las cuales se denotan
2.1 Introducción a las Redes de Petri 7

utilizando puntos dentro del cı́rculo que representa el lugar que las contiene. En
el ejemplo de la Figura 2.2, los lugares p1 y p4 tienen una ficha cada uno, p2 tiene
dos fichas y p3 ninguna.

2.1.2. Definición Formal de una Red de Petri


Existen, a lo largo de la literatura, muchos tipos de redes y muchas maneras
de definirlas formalmente. En general, todas son bastante similares, y difieren
sólo en pequeños detalles. Sin embargo, la más adecuada para los propósitos de
esta tesis define una red de Petri R como una quı́ntupla:

R = (P, T, P re, P ost, M ) (2.1)

En donde:

P es un conjunto finito de lugares de dimensión n.

T es un conjunto finito de transiciones de dimensión m.

P re : P × T → N es una aplicación de entrada (pre-condiciones).

P ost : P × T → N es una aplicación de salida (post-condiciones).

M : P → N es una aplicación de marcación.

La quı́ntupla R = (P, T, P re, P ost, M ) con los lugares P = {p1 , p2 , p3 }, las


transiciones T = {t1 , t2 , t3 , t4 }, los valores de entrada P re(p2 , t3 ) = 3, P re(p1 , t2 ) =
P re(p2 , t1 ) = P re(p3 , t4 ) = 1 y de salida P ost(p1 , t1 ) = P ost(p2 , t2 ) = P ost(p3 , t3 ) =
1, P ost(p2 , t3 ) = 3, junto con M (p1 ) = M (p3 ) = 1 y M (p2 ) = 3 representa a la
red de Petri mostrada en la Figura 2.3.
Además, se dice que M (p) es el número de fichas que contiene el lugar p. La
marcación M de una red de Petri es la distribución de fichas alrededor de los
lugares de la red. La marcación de la red en un momento determinado define el
estado de la red en dicho instante.
8 Redes de Petri

t1 t3
[3]

p1 p2 p3

[3]
t2 t4

Figura 2.3: Una Red de Petri que Representa Recursos y Actividades

2.1.3. Notación Matricial de una Red de Petri


Es posible escribir las aplicaciones P re y P ost de la equación 2.1 en forma
matricial. Esto es consistente con la definición de una red de Petri como un grafo
dirigido bipartito. Desde ese punto de vista, se pueden definir las matrices P re y
P ost de una red, y éstas se pueden ver como las matrices de adyacencia del grafo
dirigido bipartito. Por ejemplo, para la red de la Figura 2.3, las matrices P re y
P ost son:

t t2 t3 t4
1 
0 1 0 0 p1
  (2.2)
P re = 
 1 0 3 0  p2
0 0 0 1 p3

t t2 t3 t4
1 
1 0 0 0 p1
  (2.3)
P ost = 
 0 1 0 3  p2
0 0 1 0 p3
Además, se define la matriz caracterı́stica, o de incidencia, C de la red como:

C = P ost − P re (2.4)

Dicha matriz proporciona el balance (movimiento neto) de las fichas en la red


al momento de dispararse alguna transición. Para la Figura 2.3, tenemos que la
2.1 Introducción a las Redes de Petri 9

matriz C de la red es:

t1 t2 t3 t4
 
1 −1 0 0 p1
  (2.5)
C= 
 −1 1 −3 3  p2
0 0 1 −1 p3
Para esta red, de la matriz C, se infiere entre otras cosas, que al dispararse
la transición t3 se removeran tres fichas del lugar p2 y se añadirá una a p3 . En
general, se utiliza la notación P re(., t), P ost(., t) y C(., t) para referirse a la
columna asociada a la transición t una de alguna de estás matrices.
Desde el punto de vista matricial, la marcación de la red se representa con un
vector columna M , cuya dimensión es el tamaño n del conjunto de lugares P y
que contiene los valores de M (p) para todos los lugares de la red.

2.1.4. Red de Petri Pura


Una red de Petri es pura si se cumple que:

∀p ∈ P, ∀t ∈ T ⇒ P re(p, t)P ost(p, t) = 0 (2.6)

Es decir, una red de Petri es pura si no existe un lugar que sea a la vez entrada
y salida de una misma transición. En otras palabras, también se puede decir que
una red es pura si la intersección de los lugares de entrada con los de salida
es vacı́a. Por ejemplo, las redes de Petri de las Figuras 2.2 y 2.4 no son puras,
mientras que la de la Figura 2.3 es pura.
Matemáticamente, una red impura genera matrices P re y P ost que sobrepo-
nen en algún lugar valores distintos de cero (consecuencia directa de la ecuación
2.6), cosa que no ocurre en una red pura. Esto puede apreciarse en la red pura
de la Figura 2.3 y en sus matrices P re y P ost mostradas en las Ecuaciones 2.2 y
2.3, donde no existe ninguna combinación (p, t) que produzca entradas en P re y
en P ost distintas de cero simultáneamente. Por ejemplo, las matrices P re y P ost
de la red impura de la Figura 2.4 son:
10 Redes de Petri

t1 t3
[3]

p1 p2 p3 t5

[3]
t2 t4

Figura 2.4: Una Red de Petri no Pura

t t2 t3 t4 t5
1 
0 1 0 0 0 p1
  (2.7)
P re = 
 1 0 3 0 0  p2
0 0 0 1 1 p3

t t2 t3 t4 t5
1 
1 0 0 0 0 p1
  (2.8)
P ost = 
 0 1 0 3 0  p2
0 0 1 0 1 p3
Donde se hace evidente que P re(p3 , t5 )P ost(p3 , t5 ) = 1, es decir, que existe al
menos una entrada que es distinta de cero en P re y que al mismo tiempo lo es
en P ost. Por otra parte, en la Figura 2.4 se aprecia que la red no es pura porque
la transición t5 tiene a p3 como lugar de entrada y salida al mismo tiempo.

2.1.5. Habilitación de una Transición


Se dice que una transición t de una red de Petri está habilitada si existen
suficientes fichas en las entradas como para satisfacer P re(., t).
Por ejemplo, en la Figura 2.5, la transición t1 no está habilitada. Esto se debe
a que, si bien P re(p1 , t1 ) = 1 se satisface con M (p1 ) = 1, P re(p2 , t1 ) = 1 no lo
hace con M (p2 ) = 0. Por otro lado, t2 está habilitada porque P re(p5 , t2 ) = 1 y
P re(p6 , t2 ) = 1 se satisfacen ambas con M (p5 ) = 1 y M (p6 ) = 1.
2.1 Introducción a las Redes de Petri 11

t1 t2
p1 p3 p5 p7

p2 p4 p6 p8

Figura 2.5: Habilitación de una Transición

Formalmente, una transición t está habilitada si y solamente si:

∀p ∈ P, M (p) ≥ P re(p, t) (2.9)

En este punto, en particular, existen muchas variantes. Es posible, por ejem-


plo, definir los lugares de la red de modo que tengan una capacidad máxima
de fichas. Con esta restricción adicional, se hace necesario revisar los lugares de
salida, además de los de entrada, al momento de determinar si una transición
está habilitada o no, todo esto, con el fin de no sobrepasar el máximo de fichas
permitido en un lugar al disparar la transición. Esta restricción podrı́a escribirse
de la siguiente forma:

∀p ∈ P, M (p) + P ost(p, t) ≤ M ax(p) (2.10)

Donde M ax : P → N, y M ax(p) representa la máxima cantidad de fichas


permitidas en el lugar p.

2.1.6. Disparo de una Transición

Si una transición t está habilitada para una determinada marcación M , es


posible obtener una nueva marcación M 0 por medio del disparo de t, utilizando
la ecuación:
∀p ∈ P, M 0 (p) = M (p) − P re(p, t) + P ost(p, t) (2.11)
12 Redes de Petri

o bien de forma vectorial:

M 0 = M − P re(., t) + P ost(., t) = M + C(., t) (2.12)

Cuando una transición es disparada, se remueve una determinada cantidad de


fichas de cada lugar de entrada según los pesos de los arcos que conectan dichos
lugares con la transición. De igual forma, según los pesos de los arcos que salen
de la transición, se añaden fichas a todos los lugares de salida.

t1 t1
p1 p3 p1 p3

p2 p4 p2 p4

Figura 2.6: Disparo de una Transición

La Figura 2.6, muestra la transición t1 y sus lugares asociados antes y después


del disparo de la misma. En este caso, ningún arco tiene un peso mayor que uno,
por lo tanto, sólo se elimina una ficha de cada lugar de entrada y se agrega una
a cada lugar de salida.

2.1.7. Conflicto Estructural y Efectivo

Cuando dos transiciones comparten un recurso (ver Figura 2.7), se produce un


conflicto. Existen dos tipos de conflictos, el primero se llama conflicto estructural,
y el segundo, conflicto efectivo. Un conflicto estructural se presenta cuando un
lugar es compartido (recurso compartido) por dos o más transiciones. Un conflicto
efectivo se presenta en el caso de que un lugar esté compartido por dos transiciones
y además éstas estén habilitadas.
Dos transiciones t1 y t2 están en conflicto estructural (ver Figura 2.7-a) si
tienen un lugar de entrada en común:
2.1 Introducción a las Redes de Petri 13

p1 p1

t1 p4 t1 p4

p2 p2

t2 p5 t2 p5

p3 p3

a) b)

Figura 2.7: Conflicto: a) Estructural, b) Efectivo

∃p ∈ P, P re(p, t1 )P re(p, t2 ) 6= 0 (2.13)

Por otro lado, se dice que dos transiciones t1 y t2 están en conflicto efectivo
para una marcación M si están en conflicto estructural, es decir, se cumple la
ecuación 2.13, y además están habilitadas (ver Figura 2.7-b).

2.1.8. Secuencia de Disparo

Dada una red de Petri, es posible llevarla de una marcación M0 a una marca-
ción M2 por medio de una serie de disparos de transiciones. Esto puede anotarse,
por ejemplo, de la siguiente forma:

t1 ,t2 (2.14)
M0 −→ M2

La secuencia de transiciones que es necesario disparar para llevar una red de


Petri de una marcación inicial Mo a una marcación final Mf , es llamada secuencia
de disparo y se denota s = t1 t2 ...tn .
14 Redes de Petri

2.1.9. Conjunto de Marcaciones Accesibles

El conjunto de marcaciones accesibles de una red de Petri, es el conjunto de


marcaciones que pueden ser alcanzadas por la red a partir de una marcación
inicial, por medio de una secuencia de disparos.

0
3
t1 t3
0
t2 t4

1 0
2 0
0 1

t1 t2

2
1
0

t1 t2

3
0
0

Figura 2.8: Marcaciones Accesibles de una Red de Petri

Este conjunto (si es finito) se puede representar por medio de un grafo. La


Figura 2.8 representa el conjunto de marcaciones accesibles para la red de la
Figura 2.3. Es importante decir, que el conjunto de marcaciones accesibles (y su
grafo asociado) es además la máquina de estado equivalente a la red de Petri que
lo origina. Respecto a este punto, en la sección 2.3 se hablará de la relación que
existe entre las máquinas de estado y las redes de Petri, mostrando las ventajas
y desventajas de ambas estructuras.

2.1.10. Redes de Petri Acotadas

Dado un k ∈ N, entonces una red de Petri R se dice que es k-acotada si y sólo


si:
2.2 Redes de Petri con Arcos Inhibidores 15

∀M ∈ R y ∀P ∈ P entonces M (p) ≤ k (2.15)

Es decir, para todas las marcaciones alcanzables por la red, ningún lugar
tendrá más de k fichas.
Si se analiza el grafo de marcaciones accesibles de la Figura 2.8, es evidente que
la máxima cantidad de fichas contenidas por un lugar para todas las marcaciones
es tres. Por esta razón, se dice que la red de la Figura 2.3 es 3-acotada. Por otra
parte, si una red de Petri no es k-acotada, entonces existe al menos un lugar que
puede llegar a tener un número infinito de fichas, y debido a esto, entonces la red
tendrá un número infinito de estados.
Un ejemplo de una red de Petri no acotada se muestra en la Figura 2.10, y en
su grafo de marcaciones accesibles en la Figura 2.11, donde se aprecia que la red
evoluciona desde su estado inicial por un número infinito de marcaciones.

2.2. Redes de Petri con Arcos Inhibidores


En general, se han propuesto muchos elementos agregados a las redes de Petri,
con el fin de ampliar su funcionalidad (ver sección 2.4) y permitir que las mismas
sean útiles y brinden soporte para una gran cantidad de aplicaciones. Uno de
estos elementos añadidos son los arcos inhibidores. En la Figura 2.9 se muestra
un arco de este tipo que parte del lugar p3 y se conecta por el otro extremo con
la transición t1 .
El objetivo básico de un arco inhibidor es habilitar una transición si la pre-
condición asociada al mismo (o post-condición) no está habilitada. En otras pa-
labras, una transición asociada a un arco inhibidor estará habilitada si el lugar al
que está asociado el arco está deshabilitado y viceversa. Es decir, desde el punto
de vista de la habilitación de una transición, el arco inhibidor cumple un papel
opuesto al de un arco convencional.
Sin embargo, desde el punto de vista funcional, cuando una transición se dis-
para, los arcos inhibidores no restan ni suman fichas de los lugares de origen
16 Redes de Petri

p1 p3 p2 p1 p3 p2

t1 t2 t1 t2

p4 p5 p4 p5

a) b)

Figura 2.9: Efecto de un Arco Inhibidor: a) t1 Deshabilitada, b) t1 Habilitada

o destino (operación que no tendrı́a sentido dada la naturaleza de este tipo de


arcos). Es decir, los arcos inhibidores cumplen una función al momento de deter-
minar si una transición está o no habilitada, pero no cumplen ningún papel al
momento de ejecutar la transición.
Como ejemplo, en la Figura 2.9 a), la transición t1 está deshabilitada, porque
si bien p1 contiene fichas, p3 también las contiene, y el arco inhibidor que va
desde p3 a t1 no permite que t1 esté habilitada. Luego, en la Figura 2.9 b), t1
está habilitada porque p1 contiene fichas y p3 no contiene.

2.3. Máquinas de Estado y Redes de Petri


La relación que existe entre las redes de Petri y las máquinas de estado es
bastante estrecha, sobre todo, porque ambas son herramientas utilizadas para
modelar sistemas de eventos discretos.
En general, si la red de Petri es k-acotada, entonces es posible encontrar una
máquina de estados finita equivalente a la red. Luego, si el número de estados
representados por una red es finito, entonces el grafo de marcaciones accesibles
será una máquina de estado funcionalmente similar a la red de Petri.
Por ejemplo, el grafo de marcaciones accesibles de la Figura 2.8 es la máquina
de estados equivalente a la red de Petri de la Figura 2.3. En dicho caso, el número
2.4 Redes de Petri de Alto Nivel 17

p1

t2 t1 t3

p2 p3

Figura 2.10: Red de Petri con un Número Infinito de Estados

de nodos de la máquina de estados es ligeramente menor que los de su red de


Petri equivalente, pero esto no siempre es ası́. De hecho, para la mayorı́a de las
redes de Petri, la máquina de estado es mucho más compleja que la red misma.
Esta “explosión de estados” se debe a que una máquina de estados utiliza un
nodo para cada estado en el que se puede encontrar el sistema, mientras que en
una red de Petri, el estado se representa mediante una combinación de fichas en
los distintos lugares.
Por otro lado, si la red de Petri no es k-acotada (ver Figura 2.10), no existe
una máquina de estados finita equivalente a la red, ya que el número de esta-
dos representados en la red es infinito (Figura 2.11). No es posible utilizar una
máquina de estados finita para representar un sistema con un número infinito
de estados (apenas hay algunas técnicas para aproximarse), pero si es posible
representar un sistema de este tipo utilizando redes de Petri.

2.4. Redes de Petri de Alto Nivel


En general, en algún momento, a las redes de Petri clásicas les ocurrió lo mismo
que a las máquinas de estado; se quedaron pequeñas al momento de solucionar
ciertos problemas de gran complejidad. En algunos casos, ciertos sistemas no
18 Redes de Petri

1
0
0
t1

0 t1 1 t2 0 t3 1 t1 0
1 0 1 1 2
2 1 1 0 1
t2 t3
t3 t2
1 1 1
0 1 2
2 1 0
t1 t1 t1

0 0 0
1 2 3
3 2 1
t2 t3 t2 t3 t2 t3
... ... ... ... ... ...

Figura 2.11: Marcaciones Accesibles (infinitas) de una red de Petri

podı́an ser modelados mediante los componentes tradicionales de las redes de


Petri (transiciones, lugares y aristas simples), mientras que en otros casos, si bien
los sistemas si podı́an ser modelados, la complejidad de la red de Petri resultante
la volvı́a en algún momento inmanejable.
A causa de los problemas que se presentaban con las redes de Petri tradiciona-
les, comenzaron a surgir algunas modificaciones, variantes y componentes nuevos
que permitı́an atacar más fácilmente algunos sistemas que resultaban difı́ciles y
hasta imposibles de manejar con las redes convencionales. Debido a esto, nacie-
ron toda una nueva serie de redes de Petri que tenı́an en cuenta factores como
el tiempo, probabilidades y estadı́stica, realizaban operaciones complejas con las
fichas (que también representaban datos complejos) y que, en general, añadı́an
toda una serie de elementos, componentes y comportamientos nuevos a las redes
de Petri tradicionales. Fue entonces cuando entraron en escena conceptos como
los de Redes de Petri Coloreadas, Redes de Petri Estocásticas y Redes de Pe-
tri Temporizadas, entre otras. Con el tiempo, todos estos nuevos tipos de redes
fueron agrupados y vistos bajo el nombre de Redes de Petri de Alto Nivel.
2.4 Redes de Petri de Alto Nivel 19

4 1
p1
v
1
t1
v3
p3

(v < v ),
2 3 v 1 2
2
p2
v 3 = v 2 − v1

Figura 2.12: Red de Petri de Alto Nivel

Las redes de Petri de alto nivel fueron propuestas en 1980, debido a que cuando
se trata de modelar sistemas complejos con redes de Petri tradicionales, éstos
sufren una explosión en tamaño que hace que el modelo sea difı́cil de manejar
(fenómeno similar al que ocurre con las máquinas de estado). En las redes de
Petri tradicionales, las fichas representan objetos simples del mismo tipo y con un
mismo valor. En las redes de alto nivel, las fichas representan una gran variedad de
tipos de datos como booleanos, enteros, reales, arreglos, registros, etc. Además de
los tipos de fichas complejas, las redes de alto nivel incluyen reglas para manipular
la información contenida en las fichas (Robert Esser, 1996).
La red de la Figura 2.12 muestra una red de alto nivel que utiliza fichas de
tipo entero y tiene una transición con una función guarda G(t) = v1 ≤ v2 y una
función de salida F (t) = v2 − v1 . La función guarda es una restricción adicional
que debe cumplirse para que la transición esté habilitada, mientras que, la función
de salida se encarga de generar las fichas adecuadas en los lugares de salida de la
transición.
Si bien las redes de alto nivel son conceptualmente mucho mas complejas que
las redes de Petri tradicionales, esta complejidad adicional se ve compensada por
el hecho de que es posible modelar sistemas más complejos con mayor facilidad.
Algunos autores (Gile & DiCesare, *) comparan la evolución de los distintos tipos
de redes de Petri con la de los lenguajes de programación (ver Figura 2.13). Sobre
este punto, afirman que las máquinas de estados son comparables con los lenguajes
de ensamblador, es decir, si bien son eficientes, las posibilidades de desarrollo
20 Redes de Petri

Lenguajes de Programacion Herramientas de Modelado

Lenguajes Orientados a Objetos


Redes de Petri de Alto Nivel
C++, Java, Smalltalk, etc

Menos Errores
Abstraccion

Eficiencia
Lenguajes de Alto Nivel
Redes de Petri
C, Pascal, Fortran, etc

Lenguajes de Ensamblador Maquinas de Estado

Figura 2.13: Comparación Entre Redes de Petri y Lenguajes de Programación

son muy limitadas. Por otro lado, las redes de alto nivel son comparables con
los lenguajes de alto nivel, en donde la eficiencia es un poco menor que en los
lenguajes de bajo nivel, pero las posibilidades de desarrollo son mucho mayores.
Capı́tulo 3

Modelo Ampliado de Redes de


Petri

En el capı́tulo anterior se mostraron las ventajas de las redes de Petri, dis-


cutiendo inicialmente sobre redes tradicionales y haciendo luego referencia a la
evolución de las mismas en otras más complejas y flexibles. En general, uno de los
requisitos del software desarrollado en éste proyecto consiste en brindar un mode-
lo fácilmente expandible que permita con poco esfuerzo implantar redes de Petri
clásicas y de alto nivel (ver sección 4.1), es decir, la idea es crear una plataforma
que permita en un futuro manejar una amplia variedad de redes de Petri. De
forma que, el presente capı́tulo pretende hacer un recorrido por los componentes
ya implantados en el motor, y además de eso, exponer algunas de las dificulta-
des encontradas desde el punto de vista del diseño del software al momento de
desarrollar los distintos componentes de una red de Petri (transiciones, lugares y
aristas).

3.1. Eventos
Los eventos son los estı́mulos originados por el mundo exterior que producen
cambios en una red de Petri. Una red cambia de estado y evoluciona en función
22 Modelo Ampliado de Redes de Petri

de los eventos que reciba y del orden en que sean procesados. Para que un evento
produzca un cambio de estado en una red de Petri, debe existir una transición
habilitada asociada al mismo. En la siguiente sección se detallan las condiciones
que deben cumplirse bajo el modelo ampliado de redes de Petri para que una
transición esté habilitada.

Los eventos representan sucesos del mundo exterior. Un evento puede repre-
sentar sucesos tan distintos como el inicio de un proceso de producción, un usuario
pulsando un enlace o un botón de una página Web, la apertura de una válvula o
la averı́a de una máquina, entre otros. Algunos eventos tienen asociadas porciones
de información que son necesarias para describirlos.

Por ejemplo, la petición de inicio de un proceso de producción tendrá asociada


la cantidad de producto que se desea obtener. El evento generado por el usuario
de la página Web puede tener asociados los parámetros de la petición HTTP, de
modo que una red de Petri pueda decidir a que página enviarlo como resultado
de la petición. La averı́a de una máquina puede tener asociada la dirección fı́sica
de la misma, para que ésta pueda ser incluida en un reporte de fallas y que el
departamento técnico sepa que máquina debe reparar.

El motor de ejecución acepta eventos que vengan acompañados por paráme-


tros, siendo el comportamiento por defecto transferir y hacer que los componentes
involucrados con un evento determinado (transiciones, aristas y lugares) tengan
conocimiento de dichos parámetros.

Por otra parte, un evento debe poder ejecutarse de forma atómica, es decir,
sin interrupción desde que comienza hasta que termina. Esto es imprescindible
para mantener la integridad de la red de Petri y de la información asociada a
la misma. Una red de Petri nunca puede estar ejecutando dos eventos de forma
simultánea.
3.2 Transiciones 23

3.2. Transiciones
Las transiciones son los componentes asociados a la dinámica de la red de
Petri. Representan la reacción de la red frente a los eventos del mundo exterior.
Las transiciones están asociadas a eventos. Cada transición puede estar aso-
ciada sólo a un único evento, pero un evento puede estar asociado a muchas
transiciones dentro de una misma red. Una transición depende de dos factores
para que pueda dispararse, el primero, que llegue el evento al cual está asociada,
y el segundo que esté habilitada. Una transición está habilitada si puede ejecutar
con éxito todas sus aristas asociadas. Las aristas entrantes están asociadas con las
pre-condiciones de la transición, mientras que las aristas salientes están asociadas
con las post-condiciones.

p3
0 0 0
p1 1 1 1 p2
0 0 0

2.1.− Cada arista consulta 2.− La transicion consulta


con su lugar asociado a las aristas entrantes

4.− Si todas las aristas / lugares entrantes


e1 y salientes estan habilitados, la
transicion se dispara
1.− Llega un evento t1

3.− La transicion consulta


a las aristas salientes
3.1.− Cada arista consulta
con su lugar asociado

0 0
p4 1 1 p5
0 0

Figura 3.1: Disparo de una Transición

En general, un evento asociado a una transición es procesado por ésta en


cuatro pasos:

1. Llega el evento a la transición.


24 Modelo Ampliado de Redes de Petri

2. La transición ejecuta todas sus aristas entrantes y verifica que estén habi-
litadas.

3. La transición ejecuta todas sus aristas salientes y verifica que estén habili-
tadas.

4. Finalmente, si todas las aristas entrantes y salientes están habilitadas, la


transición se dispara.

Una transición determina si está o no habilitada, consultando a todas las


aristas entrantes y salientes. Si todas y cada una de estas aristas se encuentran
habilitadas, entonces la transición está habilitada y se disparará al momento de
llegar el evento asociado a la misma. Por otra parte, las aristas determinan si
están o no habilitadas consultando a su lugar asociado.

3.2.1. Problemas de Conflicto


En el modelo clásico de redes de Petri se asume que las transiciones responden
a los eventos de forma inmediata. De esta manera, la ejecución de una transición
(verificar pre-condiciones, post-condiciones y disparar la transición) es en teorı́a,
instantánea. Como consecuencia de la suposición anterior, entre otras cosas, se
asume que dos eventos no se ejecutarán nunca de forma simultánea. Al no existir
simultaneidad en la ejecución de dos eventos, no es posible que se produzca un
problema de conflicto (o concurrencia), ya que dos transiciones nunca competirán
por el acceso a un mismo lugar. Sin embargo, a nivel práctico, no es posible
ejecutar una transición instantáneamente, ası́ como tampoco se puede evitar que
un evento llegue a la red de Petri mientras que otro se está ejecutando. Tales
limitaciones prácticas, tarde o temprano, generan problemas de concurrencia que
deben ser manejados por el motor de ejecución.
En el caso de la Figura 3.2, dos transiciones (asociadas a eventos distintos)
poseen al menos un lugar en común. Cuando llega un evento e1 a la red, la
transición t1 se encuentra habilitada y por lo tanto dispara. Pero en ese instante,
3.2 Transiciones 25

p2
1 1 1
p1 1 1 1 p3
0 0 0

Lugares Compartidos

e1 e2
t1 t2

0 0 0
p4 1 1 1 p6
0 0 0

p5

Figura 3.2: Conflicto

entra a la red un evento e2 , antes de que la ejecución de t1 logre descontar la


ficha del lugar p2 . Desde la óptica del segundo evento, su transición asociada t2
está habilitada y debe dispararse, pero eso es un error, ya que la ficha de p2
está comprometida con la ejecución de la primera transición y no deberı́a estar
disponible para la segunda.

Es por esto que el motor debe garantizar que la ejecución de un evento trans-
curra de forma atómica, sin interrupciones desde que comienza hasta que termina.
En otras palabras, un evento está asociado a una serie de transiciones, y éstas a
una serie de lugares y aristas. Pero muchos lugares son compartidos por varias
transiciones, asociadas a su vez con otros tipos de eventos. De esta forma, es ne-
cesario que la ejecución de un evento y sus transiciones asociadas ocurra sin que
otro evento asociado a otras transiciones interfiera, siempre y cuando comparta
lugares con el primer evento.

Otro posible problema de concurrencia se puede presentar cuando dos o más


transiciones asociadas a un mismo evento comparten lugares comunes. Si bien el
caso anterior se puede resolver (de una forma fácil pero ineficiente) ejecutando
26 Modelo Ampliado de Redes de Petri

un evento a la vez, en este caso, tal solución no es posible, ya que el problema de


concurrencia se produce dentro del contexto de ejecución de un mismo evento.
Existen dos posibles soluciones (en lo absoluto mutuamente excluyentes) que
permiten resolver el problema de concurrencia dentro del contexto de ejecución
de un mismo evento. La primera, contempla que las transiciones asociadas a un
mismo evento se marquen al momento del diseño de la red, de forma que el
motor conozca explı́citamente el orden en que deben ejecutarse. De esta forma,
la transición con más precedencia se ejecutará primero y tendrá prioridad sobre
sus recursos asociados.
Otra solución consiste en determinar, antes de ejecutar un evento, cuales tran-
siciones están habilitadas y cuales no lo están. Esta operación de comprobación
debe poder determinar el estado de una transición (habilitada o no) sin tener que
ejecutarla y sin modificar sus aristas y lugares asociados. De esta forma, es posible
obtener una lista de transiciones habilitadas y transiciones no habilitadas para un
determinado evento en un determinado momento. Como paso siguiente, se deben
ejecutar todas las transiciones habilitadas en bloque, como en una transacción.
Si alguna de las transiciones falla en su ejecución es porque se presentó un pro-
blema de concurrencia. En este caso, el error debe reportarse y la ejecución de
las transiciones debe abortar, restaurando la red al estado anterior a la llegada
del evento.
Tal situación se podrı́a ver en la Figura 3.2 si se cambia el evento asociado
a t2 de e2 a e1 . En ese caso, el motor no puede determinar que transición debe
ejecutar primero, si t1 o t2 . Ambas transiciones no pueden ejecutarse, si se ejecuta
t1 primero, ésta consumirá la ficha de p2 y dejará a t2 deshabilitada. Este proceso
hace que la red se vuelva difı́cil de predecir (por no decir incoherente) en un
momento dado, ya que una transición que inicialmente podı́a dispararse, falló al
momento de ejecutarse.
En este caso, una solución podrı́a ser especificar explı́citamente una preceden-
cia para resolver el orden de ejecución de las transiciones. Es decir, se le puede
asignar a t1 una precedencia mayor que a t2 , de modo que el motor sabe que debe
3.2 Transiciones 27

ejecutar t1 primero y luego, si quedan recursos, a t2 .


Otra forma, utilizando el modelo de transacciones (donde todos los compo-
nentes se ejecutan adecuadamente o no se ejecuta ninguno), serı́a no tomando
en cuenta el orden de ejecución de las transiciones. De esta manera, la primera
transición se ejecutarı́a correctamente, y al fallar la segunda, toda la ejecución
del evento se abortarı́a y la red se devolverı́a al estado anterior a la llegada del
mismo.

3.2.2. Transición Simple


Esta es la transición más simple que existe y es en general la base para todas
las demás. Cuando llega un evento asociado a una transición, ésta sigue los pasos
de la Figura 3.1. Su comportamiento al momento de dispararse es no hacer nada.

t1

e1

Figura 3.3: Transición Simple

3.2.3. Transición Generadora de Eventos


La Transición Generadora de Eventos sigue el comportamiento básico de la
Figura 3.1, pero al dispararse genera un evento dirigido a la red de Petri que la
contiene, a alguna otra arbitraria especificada al diseñar la red, o a alguna especi-
ficada en los parámetros del evento inicial que disparó la transición. Los eventos
generados por este tipo de transición son colocados en una cola y procesados des-
pués de que se termina con el evento en curso, pero antes de ejecutar cualquier
otro evento proveniente del mundo exterior.

t1

e1

Figura 3.4: Transición Generadora de Eventos


28 Modelo Ampliado de Redes de Petri

Es necesario utilizar con extremo cuidado este tipo de transiciones, ya que no


es difı́cil imaginarse un caso en el que se pueda caer en un lazo infinito.

1
p1 1
0

e1 t1 t2 e2

0
1 p2
0

Figura 3.5: Situación de Lazo Infinito

La Figura 3.5 muestra una situación en la que si t1 genera un e2 , y t2 genera


un e1 , entonces al llegar e1 , la red cae en un estado de lazo infinito. En general, no
existe una forma sencilla de detectar tales lazos en la implementación del motor,
ası́ que, es necesario tener estos casos en cuenta al diseñar las redes.

3.2.4. Transición Asociada a Código Java

Una transición de este tipo ejecutará al dispararse un método de una clase


JavaTM definida en el momento de diseñar la red de Petri. Es responsabilidad del
diseñador que la clase y el método sean válidos y estén presentes en el servidor
al momento de dispararse la transición. La clase y el método son instanciados e
invocados dinámicamente por el motor utilizando reflexión de JavaTM . El motor
de ejecución debe reportar cualquier error en tiempo de ejecución que pueda
generar una transición de este tipo.
3.3 Lugares 29

t1

e1

Figura 3.6: Transición Asociada a Código Java

3.3. Lugares
Los lugares son los componentes de una red de Petri asociados al estado de la
red.
El estado de una red de Petri, en un instante determinado, viene dado por la
marcación de la red. En el caso de una red de Petri clásica, que sólo tiene lugares
con fichas, la marcación de la red viene dada por el número de fichas que están
presentes en cada uno de los lugares de la red. En el caso del modelo ampliado de
redes de Petri, la situación se complica porque la red no sólo está compuesta por
lugares con fichas. La red puede tener lugares complejos en los que no siempre
es fácil y claro ver cual es la marcación del lugar. Por ejemplo, un lugar puede
representar una consulta a una base de datos, siendo la marcación 1 si la consulta
no es vacı́a y 0 si la consulta no genera resultados.
Además, las redes de Petri clásicas son grafos bipartitos no ponderados, es
decir, que lo único que importa de una arista es de donde viene y a donde va.
Sin embargo, el modelo ampliado aquı́ propuesto, sugiere que las aristas tengan
una función más protagónica en la ejecución de la red, cumpliendo un papel en
la evaluación de la habilitación de un lugar. Por ejemplo, en el caso de un lugar
con fichas coloreadas, la arista que lo conecta con su transición asociada puede
especificar el número de fichas de cada color que se deben desplazar para que el
lugar esté habilitado.

3.3.1. Marcación de un Lugar

El estado de un lugar viene representado por su marcación. En el caso de las


redes de Petri clásicas, la marcación de un lugar es el número de fichas presentes
en dicho lugar. Sin embargo, el modelo ampliado complica un poco las cosas, ya
30 Modelo Ampliado de Redes de Petri

que ahora es posible tener un sin fin de lugares de distintos tipos interactuando
juntos en una misma red, de modo que ya no existe una sola forma de representar
la marcación de un lugar. En general, la marcación de un lugar, sirve en el modelo
clásico de redes de Petri para determinar si el lugar está o no habilitado, pero en
el modelo extendido se puede dar el caso de que, sin la presencia de un evento
(con sus respectivos parámetros), no sea posible determinar si un lugar está o no
habilitado. Ese es el caso de los Lugares Asociados a Código JavaTM , donde la
habilitación o no del lugar depende del código que se ejecute y de los parámetros
asociados al evento.
Por las razones expuestas anteriormente, el modelo ampliado define la mar-
cación de un lugar como un elemento particular al tipo de lugar. Es decir, para
un lugar de fichas, la marcación será el número de fichas presentes (como en el
modelo clásico). Para un lugar con fichas coloreadas, la marcación será una tabla
cuya clave es el color y que tiene por valor en cada entrada el número de fichas
presentes en el lugar para el color especificado. Un ejemplo aún más dramático es
el de la marcación de un lugar JavaTM , en el que sólo existen tres posibilidades a
saber: no habilitado, habilitado e indefinido. En general, un lugar JavaTM deberı́a
hacer el mejor esfuerzo para determinar su estado, pero en casos en los que esto
no sea posible, debido a que no están presentes los parámetros de un evento,
entonces la marcación del lugar es indefinida, es decir, no es posible determinar
si está habilitado o no.

3.3.2. Lugar de Fichas


Los Lugares de Fichas cumplen la función clásica de los lugares en las redes de
Petri. Son nodos que contienen fichas que son eliminadas o agregadas a medida
que se van disparando transiciones en la red. En teorı́a, un lugar de este tipo
está habilitado si contiene al menos una ficha. Sin embargo, el modelo ampliado
permite que las aristas jueguen un papel al momento de determinar si un lugar
está o no habilitado. Más adelante, se verá que existe una combinación transición-
arista-lugar que permite emular el comportamiento clásico de un lugar con fichas.
3.3 Lugares 31

0
1
0

p1

Figura 3.7: Lugar de Fichas

Adicionalmente, esta clase de lugares permite acotar la mı́nima y máxima


cantidad de fichas que son válidas para que el lugar esté habilitado. Cualquier
intento de sustraer fichas por debajo del mı́nimo o agregar por arriba del máximo,
generará como resultado que el lugar fallará en su ejecución y, por lo tanto, no
estará habilitado.

3.3.3. Lugar de Fichas Coloreadas

Los Lugares de Fichas Coloreadas representan una forma cómoda de agrupar


varios lugares de fichas en un solo sitio. Un lugar de este tipo posee un conjunto
de colores, y por cada color existe una determinada cantidad de fichas.

p1

Figura 3.8: Lugar de Fichas Coloreadas

3.3.4. Lugar Asociada a Código Java

Un lugar JavaTM determina si está o no habilitado ejecutando un programa


JavaTM especificado al momento del diseño de la red. Cuando al lugar se le pre-
gunta si está o no habilitado se ejecuta un método que debe retornar verdadero
si el lugar está habilitado o falso si no lo está. Además, es necesario especificar
un método que ejecutará en sı́ el lugar.
32 Modelo Ampliado de Redes de Petri

p1

Figura 3.9: Lugar Asociado a Código Java

Los lugares JavaTM pueden ser utilizados en casos en los que, por ejemplo, la
habilitación de un lugar depende de una consulta a una base de datos o a algún
otro recurso similar. Por otra parte, la ejecución de un lugar de este tipo puede
representar un cambio en algún registro en una base de datos llevado a cabo por
el método invocado a la hora de ejecutar el componente.

3.4. Aristas
Las aristas en las redes de Petri clásicas cumplen la función de enlazar las
transiciones con los lugares. Sin embargo, en el modelo ampliado de redes de Petri,
las aristas juegan un papel más activo. En general, una transición está habilitada
si todas sus aristas entrantes o salientes están habilitadas. Esto es coherente con
la definición de redes de Petri del capı́tulo anterior, donde se especifica que una
arista determina si una transición está o no habilitada dependiendo del número de
fichas que se desean eliminar (o agregar) de un lugar. Las aristas además pueden
funcionar como arcos inhibitorios. En tal situación, una arista estará habilitada
si su lugar asociado no lo está (según los criterios de la arista y el lugar), pero
sin embargo, al momento de su ejecución, la arista debe abstenerse de realizar
cambios en la red de Petri (ver sección 2.2).

3.4.1. Arista de Fichas


Una arista de fichas puede unirse por uno de sus extremos con una transición
de cualquier tipo, y por el otro, con un Lugar de Fichas. Esta arista tiene asociado
un peso, que representa el número de fichas que se añadirán o removerán del lugar
3.4 Aristas 33

con el que conecta según sea el caso. Si la arista va de un lugar a una transición,
entonces las fichas se removerán del lugar, si va de una transición a un lugar,
entonces se añadirán al lugar.

3.4.2. Arista de Fichas Coloreadas


Una Arista de Fichas Coloreadas cumple la misma función que la Arista de
Fichas, pero en lugar de manejar una única ficha, maneja múltiples fichas de
distintos colores. Esta arista tiene asociados n pesos, donde n es el número de
colores presentes en el lugar con el que está asociada. Los pesos definen el número
de fichas de un color determinado que serán restadas o sumadas al lugar asociado
en caso de que la arista sea ejecutada.

3.4.3. Arista Simple


La Arista Simple tiene como función unir transiciones con lugares que sólo
tienen dos posibles estados: habilitados o no habilitados. En general, sirven para
unir transiciones a lugares JavaTM y cualquier otro lugar que tenga sólo estos
dos estados. Sin embargo, desde el punto de vista práctico, una arista simple
debe también poder manejar lugares cuyo estado sea indefinido, esto permite
determinar el estado de una transición (habilitado, no habilitado o indefinido),
en caso de que al motor le sean consultados dichos estados sobre una red en
particular.
34 Modelo Ampliado de Redes de Petri
Capı́tulo 4

Arquitectura del Sistema

4.1. Requerimientos

A lo largo de la literatura consultada se pudo verificar que a partir del con-


cepto simple de redes de Petri clásicas evolucionan una gran cantidad de redes
de distintos tipos. En general, el software que se encontró en el mercado, algunos
productos de distribución gratuita y algunas conclusiones extraidas de (Harald
Störrle, 1998), se enfocan en la simulación de redes de Petri clásicas, y no aprove-
chan la posibilidad de conectar las redes con otro software que pueda utilizarlas.
Sin embargo, no se encontró ninguna implementación de redes de Petri de alto ni-
vel o redes coloreadas. Tampoco se pudo hallar una implementación de un motor
de ejecución que pueda ser utilizado como repositorio de redes de Petri, donde las
mismas corran y evolucionen (en un ambiente de producción) en función de los
eventos que reciben del exterior, de modo que sea posible en un momento dado
obtener información útil sobre el estado de una red.
Este fue el panorama que incentivo el desarrollo de esta tesis. No es la intención
de este proyecto aportar otro software que simule redes de Petri clásicas, o cubrir
la carencia de simuladores de redes de Petri de alto nivel o coloreadas, aun cuando
al final este proyecto también pueda ser utilizado para atacar cualquiera de los
puntos mencionados anteriormente. De hecho, la idea fundamental de esta tesis
36 Arquitectura del Sistema

no es simular redes de Petri, es ejecutarlas. La diferencia radica en que al simular


una red de Petri sólo se está validando el modelo, es decir, se determinan sus
propiedades, comportamiento, se detectan abrazos mortales, etc. Al ejecutar la
red, esta se pone a correr en paralelo con el sistema modelado (algunas veces ella
misma es el sistema) de modo que es posible tener una idea del estado de dicho
sistema. Además, hay aplicaciones en las que se desean coordinar procesos, y en
estos casos la red debe correr a la par del sistema.
En general, la intención de este proyecto es definir una arquitectura versátil
(basada en el concepto de componentes) que permita implementar y ejecutar
cualquier tipo de redes de Petri. Si alguna aplicación en particular necesita un
lugar en particular, este debe poder programarse e integrarse al motor de una
forma sencilla y práctica.
Por otra parte, muchos procesos y sistemas que generalmente corren de forma
distribuida, se pueden modelar y coordinar utilizando redes de Petri. Esto significa
que es necesario tener el motor de ejecución corriendo como un servicio en alguna
máquina, de modo que los clientes puedan registrar y ejecutar redes de Petri de
forma remota. Además, muchos proyectos de software pueden aprovechar para
modelar y seguirle la pista a ciertos procesos utilizando redes de Petri pero no
como servicio, sino como librerı́a.
De modo, que las premisas de diseño y los requerimientos del software resultan
ser:

1. Debe ser posible agregar componentes para poder crear luego redes com-
plejas.

2. Es necesario que corra como un servicio por si mismo (standalone).

3. Debe poder correr como librerı́a para ser utilizado localmente desde una
aplicación.

4. Alta portabilidad.
4.2 Arquitectura General del Sistema 37

4.2. Arquitectura General del Sistema


Fundamentalmente, el sistema está compuesto por cinco actores: El motor de
ejecución, la interfaz remota, la interfaz local, el editor y los clientes. Estos ele-
mentos y las relaciones entre los mismos se muestran de forma general en la Figura
4.1. Las lı́neas punteadas representan comunicación entre los módulos de forma
remota (una red de área local, por ejemplo), mientras que las lı́neas continuas
representan comunicación local dentro de un mismo espacio de direcciones.
Interfaz Remota
(servicio)

Cliente Motor BD

Redes de Petri
Serializadas

Editor Motor BD

Interfaz Local
(libreria)

Figura 4.1: Arquitectura General del Sistema

4.2.1. El Motor de Ejecución


El motor de ejecución se encarga de manejar todo lo relacionado con las redes
de Petri. Brinda la interfaz y la implementación necesaria para administrar las
redes, estas operaciones se pueden resumir de la siguiente forma:

Registrar nuevas redes de Petri en el motor y ponerlas en ejecución.

Guardar en la base de datos una red registrada cuando no es necesario


tenerla en memoria.

Recuperar una red de la base de datos cuando sea necesario realizar una
operación sobre la misma.
38 Arquitectura del Sistema

Suspender temporalmente la ejecución de una red de Petri.

Resumir la ejecución de una red previamente suspendida.

Eliminar redes de Petri del motor.

Consultar el estado de una red de Petri (marcación y transiciones habilita-


das).

Listar las redes que están corriendo en el motor y su estado de ejecución


(corriendo o suspendidas).

Manejar los eventos que llegan al sistema y ejecutarlos en la red adecuada.

4.2.2. Ciclo de Vida de una Red de Petri


Utilizando como base las operaciones de la lista anterior, es posible definir el
ciclo de vida para una red de Petri en el motor (ver Figura 4.2). Una red no existe
en el motor hasta que es registrada, momento en el cual pasa automáticamente al
estado en ejecución. Inmediatamente la red es almacenada en la base de datos (de
modo que si ocurre una falla en el servidor la información no se pierde) pero se
mantiene una copia en memoria para atender futuras peticiones. Una red responde
a eventos sólo si se encuentra en ejecución, de modo que si la red está en cualquier
otro estado los eventos son ignorados (suspendida) o es necesario hacer que la red
cambie de estado primero antes de que pueda procesar un evento (pasiva).
Sin embargo, es posible que la red no sea utilizada con frecuencia y mante-
nerla en memoria represente un desperdicio de recursos, ası́ que después de cierto
tiempo de inactividad la red es eliminada de la memoria y pasa al estado pasiva.
Por otro lado, si en algún momento llega un evento o se hace una petición de
administración dirigida a una red que esta en la base de datos pero no en memo-
ria, el motor debe recuperar la red de la base de datos y subirla a memoria para
poder cumplir con la petición (menos si la petición es de eliminación, caso en el
cual la red es simplemente eliminada de la base de datos).
4.2 Arquitectura General del Sistema 39

No Existe

registro eliminacion

ejecucion de
evento
no en uso
En Ejecucion Pasiva (BD)
requerida

suspender resumir

no en uso
Suspendida
requerida

Figura 4.2: Ciclo de Vida de una Red en el Motor

Además, si una red está en ejecución es posible suspenderla, pasándola al


estado suspendida. En este estado, la red responde a peticiones de administración
pero no responde frente a los eventos del mundo exterior. Es útil suspender una
red en caso de que se deseen realizar labores de administración sobre el sistema
que modela la red o sobre la red misma sin que la coherencia de los datos se
vea alterada por algún evento inesperado. Por otra parte, existe una operación
de administración (resumir) que hace que una red pase del estado suspendida al
estado en ejecución. Si una red suspendida pasa más de cierto tiempo en este
estado sin ser resumida, el motor también la pasa al estado pasiva y la elimina
de la memoria. Cualquier petición que se realice sobre una red suspendida que
haya pasado al estado pasiva (con excepción de una petición de eliminación que
será procesada directamente) causará la recuperación de la red de la base de
datos, pasándola nuevamente al estado suspendida.

Finalmente, el estado pasiva es opcional cuando el motor se utiliza como una


librerı́a (siempre está presente si el motor corre como servicio). Esto generalmente
es útil si una aplicación desea hacer uso de redes de Petri de corta vida sin que
sea necesario almacenarlas en una base de datos.
40 Arquitectura del Sistema

4.2.3. Interfaz Remota y Local


Las interfaces brindan una API adecuada, transparente y estándar para ma-
nejar y acceder a las operaciones brindadas por el motor y enumeradas en la lista
anterior. La interfaz remota permite utilizar el motor como un servicio, es decir,
el motor corre en alguna máquina esperando a que clientes corriendo en otras
máquinas se conecten y realicen alguna clase de petición. Por otra parte, la inter-
faz local hace que el motor se comporte como una librerı́a de modo que pueda ser
incrustado directamente en una aplicación. En general, desde el punto de vista de
la interfaz y salvo algunos cambios menores, el cliente no debe poder diferenciar
entre una u otra interfaz, esto con el fin de que exista la mayor uniformidad y
portabilidad posible en la utilización del motor.

4.2.4. El Editor de Redes de Petri


El editor es un módulo que permite dos funciones básicas, la primera, facilitar
la creación de redes de Petri brindándole al usuario las comodidades de una
herramienta de edición gráfica. La segunda, servir como interfaz de administración
para cualquier motor que esté corriendo como servicio, permitiendo importar,
exportar, suspender, resumir y eliminar redes de Petri. Además, el editor debe
permitir la simulación, el monitoreo de redes de Petri que corran en un motor
configurado como servicio, asi como de igual forma debe permitir estimular (enviar
eventos) a alguna red en ejecución.

4.2.5. Los Clientes


Los clientes pueden acceder al motor de ejecución de dos formas: localmente o
remotamente. Si un cliente necesita utilizar redes de Petri de modo que sea posible
compartirlas con otros clientes o procesos entonces deberı́a utilizar el motor como
un servicio, que corre independiente sobre un servidor de aplicaciones, de modo
que pueda ser accedido por todas las partes interesadas. Además, esto garantizarı́a
la persistencia de las redes de Petri a lo largo del tiempo, de forma tal que la
4.3 Arquitectura del Motor 41

red se transforma en un ente independiente que no necesita al cliente que la


creó corriendo para para poder funcionar.
Por otra parte, es posible que un cliente esté interesado en utilizar ciertas
redes de Petri por periodos de tiempo muy cortos, sin compartirlas con otros
procesos o clientes y opcionalmente sin que la red sea almacenada y persista entre
dos ejecuciones distintas de un mismo cliente. En este caso, el motor deberı́a ser
utilizado como librerı́a, estando disponible sólo para el cliente que lo haya creado,
y opcionalmente sin que exista persistencia a largo plazo de las redes de Petri entre
dos sesiones diferentes.

4.3. Arquitectura del Motor


Conceptualmente, la interfaz del motor está dividida en tres partes distintas y
bien diferenciadas: Interfaz de administración, capturador de eventos y notifica-
ción de sucesos. En la Figura 4.3 se muestran los distintos módulos que componen
el motor. En general, a la izquierda se aprecian las interfaces del motor con el
mundo exterior, mientras que a la derecha se puede ver la división de módulos
interna del mismo.
La interfaz de administración brinda soporte a todas las operaciones de ad-
ministración del motor (registrar, eliminar, suspender, resumir y consultar redes
de Petri). Por otro lado, la interfaz del capturador de eventos se encarga de re-
cibir eventos del mundo exterior para introducirlos en la cola de eventos. Estas
interfaces reaccionan y se activan frente a ordenes de clientes del mundo exterior,
cosa que no ocurre con la interfaz de notificación de sucesos que se encarga (pre-
via sub) de notificarle a clientes del mundo exterior que algún cambio ocurrio en
alguna red.

4.3.1. Capturador de Eventos


El capturador de eventos se encarga de recibir los eventos del mundo exterior
y colocarlos en una cola de donde serán tomados por el manejador de eventos. Por
42 Arquitectura del Sistema

Interfaz de Repositorio de
administracion Redes de Petri BD

Capturador Manejador de
Cola de Eventos
de eventos Eventos

Notificacion Manejador de Componentes


de sucesos Sucesos

Figura 4.3: Arquitectura del Motor

razones de eficiencia, tanto el capturador como el manejador de eventos deben


correr en hilos diferentes, asi que la cola que sirve de intermediaria debe estar
debidamente sincronizada. En general, estos módulos siguen un esquema produc-
tor / consumidor, donde el capturador se activa sólo para cuando llega un evento
para introducirlo en la cola, mientras que el manejador se activa para consumir
eventos de la cola.

4.3.2. Repositorio de Redes de Petri

El repositorio de redes de Petri se encarga de suministrarle al manejador de


eventos las redes de Petri necesarias para procesar los eventos almacenados en
la cola de eventos. Además, se encarga de manejar la persistencia transparente
(desde el punto de vista de los demás módulos) de las redes de Petri en la base de
datos, ası́ como las polı́ticas necesarias para determinar que redes se mantienen
en memoria con el objetivo de mejorar el rendimiento (Cache de redes). Por
otra parte, brinda los métodos necesarios para que los métodos de la interfaz de
4.3 Arquitectura del Motor 43

administración pueda realizar todas las operaciones que le sean requeridas sobre
las redes de Petri del repositorio.
En general, el repositorio de redes es el único módulo que está al tanto de
que las redes de Petri son almacenadas en una base de datos, los demás módulos
sólo utilizan las redes suministradas por el repositorio, independientemente de
que haya que buscarlas en una base de datos o se encuentren ya en memoria.

4.3.3. Manejador de Eventos


El manejador de eventos tiene la responsabilidad de tomar eventos de la cola
de eventos, obtener la red de Petri correspondiente del repositorio de redes y
ejecutarlo sobre la red de Petri. En general, este es el modulo principal del motor,
ya que es el encargado del procesamiento de los eventos, disparo de las transiciones
y ejecución de las aristas y lugares. Por otra parte, debe manejar las situaciones
de conflicto efectivo en las transiciones, ası́ como la concurrencia de eventos sobre
una misma red o sobre recursos compartidos (lugares) de dos redes.

4.3.4. Manejador de Sucesos


El manejador de sucesos se encarga de notificar a los clientes sobre los distintos
sucesos (eventos al fin, pero no desde el punto de vista de una red de Petri) que
ocurren en el motor. Los clientes se registran con este módulo para ser notificados
cuando una red sea eliminada, suspendida, resumida o registrada. Además, dicho
módulo es capaz de enviar notificaciones a los clientes cuando una red de Petri
cambia de estado por causa del disparo de una transición.

4.3.5. Componentes
Los componentes (en colaboración con el documento que es explicado más ade-
lante) son la piedra angular del mecanismo de expansión del motor de ejecución.
En este caso, en particular, un componente es un pequeño módulo de software
que interactúa con el motor, una o más redes de Petri, otros componentes, y que
44 Arquitectura del Sistema

representa un “tipo” de transición, arista o lugar. En general, es posible pensar en


un “tipo” de componente como se piensa en una “clase” u objeto en un lenguaje
de programación orientado a objetos como JavaTM o C++. En otras palabras,
un componente define el comportamiento general que debe tener un lugar, aris-
ta o transición, pero una red de Petri puede contener muchas instancias de un
mismo tipo de componente, cada una con parámetros distintos, y que reaccionan
independientemente según el evento recibido y la configuración de la instancia.
El motor debe brindar una serie de componentes básicos, pero los usuarios
del mismo pueden agregar y programar componentes a medida que van siendo
necesarios. De esta forma, si existe un problema o una red que no es posible atacar
utilizando el conjunto de componentes ya existentes en el motor, entonces siempre
es posible programar una transición, arista o lugar que pueda ser utilizado en una
red siempre que se desee.

4.4. Arquitectura del Editor

El editor basa su arquitectura en el modelo documento - vista, que define un


documento (información a mostrar y editar) y una serie de vistas distintas sobre
el documento. La Figura 4.4 muestra la arquitectura general del editor y sus
distintos módulos. Además, en lı́neas punteadas, se aprecia la relación del editor
con las distintas interfaces del motor de ejecución (o el uso que hace el editor del
motor).

4.4.1. Interfaz Principal

La interfaz principal es la raı́z de toda la aplicación. Su función principal es


interactuar con el usuario y permitirle acceder al resto de la aplicación. Sirve
de padre y contenedor para las vistas y de punto de entrada para la interfaz de
administración y otras funcionalidades de la aplicación.
4.4 Arquitectura del Editor 45

Interfaz Documento
Vista
Principal (Red de Petri)

Motor
Motor

Interfaz de Capturador Notificacion


Administracion de eventos de sucesos

Motor

Interfaz de
administracion

Figura 4.4: Arquitectura del Editor

4.4.2. Interfaz de Administración

La interfaz de administración es la encargada de presentarle al usuario la posi-


bilidad de administrar un motor de ejecución. En general, debe permitir registrar,
eliminar, suspender y resumir redes en el motor seleccionado, pero además, debe
permitir listar e importar las redes que estén en ejecución. Este módulo es el
encargado de hablar con la interfaz de administración de un motor en particular.

4.4.3. Vista

La vista, tiene como función tomar una instancia en particular de un docu-


mento para mostrarlo al usuario y permitirle, según sea el caso, interactuar con el
mismo. Existe una asociación uno a uno entre la vista y su documento, es decir,
en un momento determinado una vista puede estar mostrando sólo a un docu-
mento. En general, existen dos modos para la vista, el primero, que permite al
usuario editar el documento, y el segundo que sirve únicamente para supervisar
y enviarle eventos a una red de Petri corriendo en un motor de ejecución (local o
remoto). La vista interactúa con el capturador de eventos del motor (para enviar-
le manualmente un evento a una red) y con la interfaz de notificación de sucesos
46 Arquitectura del Sistema

(para detectar cambios en una red que se esté supervisando)

4.4.4. Documento
Como objetivo principal, el documento debe manejar toda la lógica de la
información que contiene, ası́ como los detalles sobre el almacenamiento del mismo
en un medio persistente. Además, el documento es la forma estándar que tienen
el motor el editor y cualquier otro módulo de almacenar y representar redes de
Petri en memoria.
Capı́tulo 5

Implementación

En general, todos los módulos se implementaran utilizando JavaTM . Esta


decisión se toma básicamente en función de las bondades de dicho lenguaje y
de su fortaleza a la hora de desarrollar rápida y eficientemente aplicaciones de
gran envergadura, portabilidad y flexibilidad. Además, para todos los documentos
utilizados en el sistema, archivos de configuración y documentos en general, el
común denominador es la utilización de XML como formato.

5.1. El API de Componentes


El API de componentes permite que se puedan añadir elementos nuevos (lu-
gares, transiciones, aristas) al motor. Eso es posible gracias a la definición de un
API estándar que permita insertar dinámicamente nuevos componentes a medida
que estos se van programando. La Figura 5.1 muestra el diagrama de clases del
API de componentes del motor de ejecución y el editor de redes de Petri. En ge-
neral, el sistema brinda una serie de interfaces (desde el punto de vista JavaTM )
que deben ser implantadas para poder definir un nuevo componente.
Un componente representa la definición de tipo, es decir, es equivalente a la
definición de tipo de dato en un lenguaje de programación. De esta forma, es
posible que existan muchas instancias distintas de un mismo tipo en una red de
48 Implementación

Petri, diferentes todas entre si, pero que tienen en común ciertas caracterı́sticas
funcionales.

Figura 5.1: Diagrama de Clases del API de Componentes

Para crear un nuevo tipo de componente (transición, lugar o arista) basta con
implementar adecuadamente las interfaces de la Figura 5.1 y registrar el nuevo
tipo en el archivo descriptor de componentes (ver más adelante). En general, el
tipo debe ser registrado en cada instancia de motor o editor en el que se utilice,
asi como de igual forma, es necesario que las clases que implantan el tipo estén
disponibles en dichas instancias.

5.1.1. Data del Componente ComponentData


Esta interfaz concentra toda la información que un componente necesita para
funcionar. Las propiedades de un componente deben definirse utilizando objetos
de tipo XDataObject, los cuales deben tener su respectiva descripción utilizando
un objeto de tipo XClass (ver apéndice B). La utilización de XClass es bastante
5.1 El API de Componentes 49

intensiva a lo largo de todo el proyecto, ası́ que se recomienda tener nociones


sobre su funcionamiento y filosofı́a.
La interfaz define una serie de métodos que permiten manipular y modificar
los XDataObject de una instancia de componente en particular. Tales métodos son
addDataObject, getDataObject, delDataObject, dataObjectIterator y toDataObjec-
tArray. El funcionamiento de cada uno de estos métodos se describe por completo
en la API del proyecto (ver JavaDocs anexos en digital).
Además, se definen métodos para manipular la información común a todos los
tipos de componentes y que esta asociada a las tres clases básicas: transiciones,
aristas y lugares. Esta información común consiste en el nombre de la instancia
(metodos get / setName), el nombre del evento asociado si el componente es una
transición (metodos get / setEvent), la clase o tipo básico de componente (tran-
sición, arista, lugar, accesible por el método getType) y la fábrica que generó el
componente (método get / setFactory, ver sección 5.1.4).
Por otro lado, no siempre es estrictamente necesario implementar esta interfaz
directamente, ya que existen una serie de clases predefinidas para cada tipo básico
que ya lo hacen, ası́ que en general basta con heredar de alguna de dichas clases.
En la Figura 5.4 se muestran las clases que conforman el documento y su relación
con las clases del API de componentes. Como se aprecia, las clases TransData,
EdgeData y PlaceData heredan de ComponentDataImpl que a su vez implantan
la interfaz ComponentData.
En otras palabras, si se desea implantar algún tipo de componente en par-
ticular, lo normal es heredar de TransData, EdgeData o PlaceData o utilizar
instancias de dichas clases (que es lo más común). De modo que basta con relle-
nar cada instancia de las clases mencionadas anteriormente con los XDataObject
adecuados según sean las necesidades del componente.

5.1.2. Objeto Acuarela ComponentObject


El objeto acuarela es el encargado de la visualización y edición gráfica del
componente y es utilizado sólo en el editor e ignorado por completo en el motor
50 Implementación

de ejecución.

Figura 5.2: Diagrama de Clases de los Objetos Acuarela Predeterminados

En general, Acuarela hace la separación entre el comportamiento lógico del


objeto al momento de mostrarse (representado por un AObject) y de la forma
de dibujar o pintar al mismo (ARenderer ). Es decir, un AObject define como
se comporta el objeto en el lienzo, mientras que un ARenderer determina como
debe pintarse. Si bien dos transiciones de distinto tipo se comportaran de la
misma forma en el editor, estas no deberan verse igual, de modo que puedan ser
diferenciadas entre si. Esto se logra haciendo que todos los tipos de transiciones
tengan un mismo tipo de AObject, pero diferentes objetos ARenderer.
El editor define tres clases AObject predeterminadas (ver Figura 5.2) que
deben ser suficiente para cualquier tipo de componente nuevo que se desee im-
plantar. ATrans representa el comportamiento de una transición, AEdge el de
una arista y APlace el de un lugar.
5.1 El API de Componentes 51

Figura 5.3: Diagrama de Clases de los Renderers Acuarela Predeterminados

Como se mencionó anteriormente, la diferencia visual entre dos componentes


consiste en el objeto ARenderer de cada uno de ellos. En general, el editor trata
de mantener cierta coherencia visual entre los distintos componentes, razón por la
cual, define una serie de objetos ARenderer por defecto para cada uno de los tipos
básicos de componentes (Figura 5.3). Cualquier componente nuevo debe tener un
ARenderer que herede de alguna de estas clases básicas, de modo que cualquier
detalle visual adicional, particular al componente, se dibuje en el método paint
sobrecargado de la clase padre.

5.1.3. Objeto de Ejecución ComponentRuntime


El objeto de ejecución es el encargado de ejecutar y verificar si un componente
está habilitado. Todos los componentes deben tener un objeto de ejecución, que
se obtiene implantando la interfaz ComponentRuntime. Esta interfaz define los
métodos básicos que el motor necesita para poder ejecutar el componente.
El método initComponentRuntime es invocado por el motor para inicializar
el componente, recibiendo como parámetros la red de Petri a la que pertenece el
componente, la instancia de ComponentData correspondiente al componente y el
contexto de ejecución, que es un mapa que contiene todos los ComponentRuntime
que se han utilizado hasta el momento durante la ejecución del evento en curso.
Este mapa permite reutilizar los objetos ComponentRuntime que ya han sido ini-
cializados, de modo que sea posible establecer un ambiente transaccional óptimo
52 Implementación

entre los componentes. En otras palabras, si se necesita un ComponentRuntime,


éste debe buscarse primero en el contexto de ejecución, y si no es encontrado,
entonces se puede proceder a crearlo e inicializarlo.

Los métodos testComponentRuntime y fireComponentRuntime se encargan


de verificar el estado y ejecutar el componente respectivamente. El primero, sim-
plemente determina si el componente está habilitado y no debe realizar ningún
cambio persistente en el mismo (por ejemplo, modificar un registro de una ba-
se de datos). El segundo, es invocado al momento de ejecutar un componente.
Este último método puede modificar los datos del componente o de algún otro
repositorio de datos, pero no puede hacer los cambios persistentes hasta que no
sea invocado el método commitComponentRuntime, o debe de poder deshacer
cualquier cambio en caso de que el motor invoque a rollbackComponentRuntime.
Estos métodos retornan verdadero si el componente está habilitado o si la ejecu-
ción del mismo terminó con éxito. En la sección 5.3, más adelante, se muestra
el ciclo de vida de un ComponentRuntime y el orden en que el motor invoca los
métodos del mismo.

5.1.4. Fábrica de Componentes ComponentFactory

La fábrica de componentes es la interfaz que aglutina toda la API de com-


ponentes. Todo componente debe brindar una clase que implemente la interfaz
ComponentFactory. Dicha clase debe ser fuente de información básica del com-
ponente, como por ejemplo, el ı́cono que el editor debe utilizar en la barra de
herramientas para representar el componente (getIcon), el nombre base del tipo
de componente (getName) y la clase básica de componente (getType). De igual
forma, la fábrica de componentes debe permitir obtener instancias ya inicializa-
das de las interfaces ComponentData (getData), ComponentObject (getObject) y
ComponentRuntime (getRuntime).
5.2 El Documento 53

5.1.5. Archivo Descriptor de Componentes

El archivo descriptor de componentes contiene la lista de fábricas de compo-


nentes disponibles para un editor o motor de ejecución. Consiste en una lista de
nombres de clases completamente calificados que implantan ComponentFactory.
Todo componente debe estar registrado en este archivo para que pueda funcionar
apropiadamente. El formato del archivo XML se muestra a continuación:

<plugins>
<plugin factory="..."/>
...
<plugin factory="..."/>
</plugins>

5.2. El Documento

El documento es la representación en memoria o en disco de una red de Pe-


tri. Todos los módulos que interactúan con redes de Petri (editor y motor de
ejecución) comparten la estructura del documento.

5.2.1. Representación del Documento en Memoria

Una red de Petri se puede manipular y mantener en memoria mediante una


serie de clases. Estas clases son TransData (representa transiciones), EdgeData
(aristas), PlaceData (lugares) y PNetData (ver Figura 5.4). Esta última represen-
ta una red de Petri completa, y no es más que un conjunto de clases TransData,
EdgeData y PlaceData dispuestas en forma de grafo dirigido.
Las clases TransData, EdgeData y PlaceData implantan ComponentData,
ası́ que dada la necesidad de programar un nuevo componente, pueden utilizarse
estas clases en lugar de hacer una que implante ComponentData.
54 Implementación

Figura 5.4: Diagrama de Clases del Documento

5.2.2. Representación del Documento en XML


Por otra parte, también es necesario poder almacenar redes de Petri en un
medio persistente como un disco o en una base de datos. Por esta razón, se define
un documento en XML que representa una red de Petri y que puede obtenerse
a partir de la representación en memoria de la red. De igual forma, a partir del
documento, se puede obtener la representación en memoria de una red de Petri.
El formato del documento se muestra a continuación:

<petrinet>
<place name = "..." type = "...">
5.2 El Documento 55

<data-object name = "..." type = "...">


<property key = "..." value = "..."/> ...
</data-object>
</place>
...
<transition name = "..." type = "..." event = "...">
<data-object name = "..." type = "...">
<property key = "..." value = "..."/> ...
</data-object>
</transition>
...
<edge name = "..." type = "..." src = "..." dst = "...">
<data-object name = "..." type = "...">
<property key = "..." value = "..."/> ...
</data-object>
</edge>
<event name = "...">
<class name="..."></class>
</event>
...
</petrinet>

El archivo consta de una lista de lugares, en donde se describe para cada uno
de ellos su nombre y su fábrica de componentes (name y type respectivamente).
Además, cada lugar contiene una lista de objetos XDataObject representados en
XML (ver más adelante). Luego, viene una lista de transiciones, cada una de las
cuales contiene su nombre, su fábrica de componentes, el nombre del evento al que
está asociada (name, type y event), y al igual que los lugares, una lista de objetos
XDataObject. Inmediatamente después, aparece una lista de aristas, donde para
cada una de ellas se describe su nombre, fábrica de componentes, el nombre del
nodo origen y el nombre del nodo destino (name, type, src y dst). Al igual que
56 Implementación

los lugares y transiciones, las aristas también poseen una lista de XDataObject.
Los objetos XDataObject se codifican en XML, especificando su identificador
y el nombre completamente calificado del XClass que lo describe (name y ty-
pe). Además, cada XDataObject posee una lista de propiedades tipo clave-valor
que representa la información que contenı́a el objeto en el momento en que se
almacenó como XML.
Finalmente, el archivo contiene una lista de eventos que pueden afectar a
la red de Petri, describiendo para cada uno de ellos su nombre y especificando
un XClass que define los parámetros que pueden venir adjuntos al evento. Una
descripción más completa y complementaria del formato del documento en XML
se puede apreciar en el apéndice A.
En general, todos los módulos del sistema necesitan manejar ambas represen-
taciones de una red de Petri (en memoria y en XML), pero más importante aún es
la necesidad de obtener una a partir de la otra. La clase PNetFactory cumple esta
función, brindando una serie de métodos (todos estáticos) que permiten obtener
el XML correspondiente a partir de un objeto PNetData y viceversa.

5.3. El Motor de Ejecución

5.3.1. Interfaz de Administración, Capturador de Eventos

La Figura 5.5 muestra el diagrama de clases de las interfaces en general. Los


métodos de administración y capturador de eventos son brindados en la inter-
faz EngineBusiness. Dicha interfaz brinda métodos acordes con las necesidades
de administración (revisar sección 4.2.1) y además permite enviar un evento a
una red en particular utilizando el método fireEvent. En general, esta interfaz es
constante, tanto si se usa el motor como librerı́a o como servicio. Si el motor es
utilizado como servicio, entonces éste se implanta con un Enterprise Java Bean
corriendo en un servidor de aplicaciones. En general, no existe diferencia desde
el punto de vista del cliente al utilizar el motor como servicio o como librerı́a,
5.3 El Motor de Ejecución 57

excepto que en el primer caso es necesario obtener una instancia de la interfaz


hogar del bean (utilizando JNDI) y en el segundo caso basta con instanciar el
motor para poder utilizarlo localmente.

Figura 5.5: Diagrama de Clases de la Interfaz del Motor

Existen también varias clases auxiliares que sirven para facilitar la comuni-
cación entre el cliente y el motor. Estas clases son PNetQuery, TransQuery y
PNetEvent. La primera sirve para encapsular la información referente al estado
de ejecución de una red de Petri (suspendida o ejecutando), y se utiliza funda-
mentalmente en el método queryPNet de EngineBusiness para listar las redes
que están corriendo en un motor de ejecución. La clase TransQuery se utiliza
para obtener, por medio del método queryTrans, la lista de transiciones de una
red y el estado de cada una de ellas (habilitada, deshabilitada o indeterminado).
Finalmente, PNetEvent representa un evento y es utilizado como parámetro del
método fireEvent.
58 Implementación

5.3.2. Manejador de Sucesos

El manejo de sucesos (o notificaciones a los clientes) se implanta utilizando


la arquitectura estándar de eventos de Java, es decir, un cliente registra una
interfaz “listener” con una fuente de eventos y espera que ésta invoque a alguno
de los métodos de la interfaz como respuesta a un evento. Un cliente de un
motor puede registrar una o más instancias de tipo EngineListener (que es un
“listener” convencional de Java), de modo que pueda recibir eventos según los
genere el motor. La interfaz no se registra directamente sobre el motor, sino sobre
una instancia de tipo EngineInfo. Este rodeo permite tener una forma estándar
de registrar “listeners” con un motor, independientemente de que esté corriendo
local (librerı́a) o remotamente (servicio).
El motor envı́a notificaciones cuando una red cambia de estado, es removida,
suspendida o resumida, y correspondientemente, los métodos pNetChanged, pNe-
tRemoved, pNetSuspended y pNetResumed, de cualquier interfaz EngineListener
registrada, son invocados. Si el motor está corriendo de forma local, hacer llegar
las notificaciones a los “listener” no resulta en lo absoluto un problema, pero si
el motor corre como servicio de forma remota, esto puede ser complicado. En el
último caso, la solución consiste en hacer llegar las notificaciónes por medio de
un servicio de mensajerı́a JMS a una instancia de tipo EngineInfo, de modo que
ésta pueda repartirlas a cada uno de sus “listener” locales registrados.

5.3.3. Repositorio de Redes de Petri

En la Figura 5.6 se muestra el diagrama de clases del motor de ejecución, que


básicamente está compuesto por el repositorio de redes de Petri y el manejador de
eventos del motor. La clase PNetRepository provee una interfaz bastante simple
para manejar las redes de Petri del repositorio. Hay métodos para agregar nuevas
redes al repositorio, eliminarlas del repositorio, obtener una red en particular o
listar las redes del repositorio (métodos addPNet, delPNet, getPNet y getPNets
respectivamente).
5.3 El Motor de Ejecución 59

Figura 5.6: Diagrama de Clases del Motor

En general, si el objeto PNetRepository es creado con un objeto de tipo ja-


va.sql.Connection (ver JDBC en la apéndice B) como parámetro del constructor,
entonces el repositorio utilizará dicha conexión para almacenar y recuperar redes
de una base de datos. Si la conexión no se especifica al momento de instanciar el
repositorio, entonces éste asumirá que no se desea hacer persistentes las redes en
una base de datos. Cuando el motor corre como una librerı́a, el cliente es respon-
sable de inicializar el objeto java.sql.Connection y de pasarlo como parámetro al
motor al momento de instanciarlo.
Por otra parte, si el motor corre como un servicio en un servidor de apli-
caciones, entonces el objeto java.sql.Connection es localizado utilizando JNDI,
mediante como nombre de la referencia “java:/PNetDS”. El servidor de aplica-
ciones debe ser correctamente configurado para que el motor pueda obtener un
java.sql.Connection mediante JNDI, utilizando “java:/PNetDS” como referencia.
Sin embargo, si el motor no puede obtener un java.sql.Connection, entonces asu-
mirá que no es necesario conectarse con una base de datos, y mantendrá todas
las redes de Petri en memoria mientras que el servicio esté funcionando.
La base de datos utilizada por el motor consta de una sola tabla llamada
60 Implementación

“pnets”. La tabla debe tener tres campos para que se puedan almacenar redes
de Petri: El primero es el identificador único de la red, debe llamarse “id” y ser
de tipo “VARCHAR(50)” (además es la clave primaria de la tabla). El segundo
campo representa el estado de ejecución de la red (ejecutando o suspendida),
debe llamarse “status” y ser de tipo entero. El motor utiliza un valor 0 para
indicar que una red está corriendo y un valor 1 para indicar que está suspendida.
Finalmente, el tercer campo, representa el contenido de la red en forma de XML.
Este campo debe ser de un tipo que soporte cadenas de caracteres de longitud
variable (el nombre del tipo varı́a mucho entre los distintos manejadores de bases
de datos) y debe llamarse “data”. Como ejemplo, a continuación se muestra la
sentencia SQL utilizada para crear la tabla “pnets”, utilizando PostgreSQL como
manejador de base de datos:

CREATE TABLE pnets


(
id VARCHAR(50) PRIMARY KEY,
status INT,
data TEXT
);

5.3.4. Manejador de Eventos


El manejador de eventos, que es implantado por la clase EngineImpl, se en-
carga de todo lo relacionado con el procesamiento de un evento por el motor.
Además, esta clase implementa EngineBusiness y sirve de puente entre la inter-
faz de administración y el repositorio de redes de Petri. Formalmente, esto resulta
ser una variación de la arquitectura del motor de la Figura 4.3. Sin embargo, tal
cambio en la implantación hace más fácil brindarle al cliente una interfaz ho-
mogénea, tanto si el motor es utilizado localmente o como un servicio. De hecho,
si el motor funciona como servicio, entonces la clase EngineImpl es instancia-
da y utilizada dentro de un Enterprise Java Bean, mientras que si es utilizado
5.3 El Motor de Ejecución 61

localmente, el cliente accede directamente a la clase EngineImpl.


En general, esta clase maneja todo lo relacionado con la concurrencia de even-
tos, procesándolos secuencialmente, de manera que dos eventos nunca se ejecuten
al mismo tiempo. Si bien esta técnica es poco eficiente, debido a que eventos que
podrı́an ejecutarse concurrentemente de una forma perfecta y sin peligro no lo
hacen, lo que permite garantizar fácilmente la integridad de la información con-
tenida en las redes de Petri. Sin embargo, en el futuro es recomendable implantar
esta clase de forma que dos o más eventos puedan ejecutarse de forma simultánea
(ver recomendaciones en la sección 6.2).

Seleccionar primera
Inicio transicion

Seleccionar siguiente
transicion initComponentRuntime testComponentRuntime

Si ¿Mas No
transiciones? ¿Habilitada?

No Si

Fin Almacenar en una lista

Figura 5.7: Ejecución de un Evento (Primera Etapa)

Cuando llega un evento al sistema, el motor lo procesa en dos etapas, una


de verificación y otra de ejecución. Básicamente, la primera etapa consiste en
inicializar los objetos ComponentRuntime (), agregarlos al contexto de ejecución,
determinar que transiciones están habilitadas y almacenarlas en una lista para
ejecutarlas posteriormente. La primera etapa sigue los pasos del diagrama de
flujo de la Figura 5.7, en ella son instanciados los objetos ComponentRuntime
e invocados los métodos initComponentRuntime y testComponentRuntime. El
objetivo de esta etapa es generar una lista de transiciones habilitadas, sin alterar
la información de la red de Petri asociada al evento que se está procesando.
62 Implementación

Ordenar lista por


Inicio
precedencias

Sacar transiciones con


mayor precedencia de la
lista

Ejecutar transiciones

Si

¿Ejecutaron Si La transaccion (Evento) ¿Mas


todas bien? finaliza bien (commit) transiciones?

No No

La transaccion del nivel


Fin
aborta (rollback)

Figura 5.8: Ejecución de un Evento (Segunda Etapa)

La segunda etapa (ver Figura 5.8) se encarga de ejecutar las transiciones habi-
litadas almacenadas en la lista generada por la primera etapa. Esta etapa agrupa
las transiciones habilitadas por precedencia, y trata de ejecutar en bloque y de
forma transaccional todas las transiciones asociadas a un mismo nivel de pre-
cedencia. Si alguna de las transiciones falla en su ejecución (por problemas de
conflicto, ver secciones 2.1.7 y 3.2.1), entonces la ejecución del nivel de prece-
dencia actual es abortada y las transiciones son alertadas por medio del método
rollbackComponentRuntime para que deshagan cualquier cambio realizado. Por
otro lado, si todas las transiciones de un mismo nivel de precedencia tienen éxito
en su ejecución, entonces son notificadas mediante el método commitComponen-
tRuntime para que hagan persistente cualquier cambio realizado.

5.4. El Editor de Redes de Petri

El editor de redes es la aplicación que permite generar fácilmente redes de


Petri e interactuar de forma sencilla y amigable con un motor de ejecución. Su
5.4 El Editor de Redes de Petri 63

objetivo fundamental es automatizar la creación de los archivos que definen las


redes mediante un ambiente gráfico (sección 5.2.2). En general, toda la GUI del
editor está implantanda utilizando algunos componentes de MBeans y otros de
Swing, y la interfaz hombre-máquina, que permite editar y visualizar las redes de
Petri, fue desarrollo por completo utilizando Acuarela (ver apéndice B)

Figura 5.9: Diagrama de Clases del Editor

5.4.1. Clases del Editor de Redes de Petri


En la Figura 5.9 se muestra el diagrama de clases del editor. La clase FrmMain
implanta la ventana principal del editor y sirve como punto de entrada al mismo.
En general, esta clase cumple el papel de “director de orquesta” de la aplicación,
siendo la encargada de leer el archivo descriptor de componentes, inicializar las
fábricas de componentes, instanciar el motor de ejecución interno del editor y
crear toda la GUI principal.
Por otra parte, tanto la clase FrmServer como FrmExport son las responsables
de establecer la conexión con un motor de ejecución (tanto el interno del editor
64 Implementación

como cualquier motor que esté corriendo remotamente como servicio). La primera
de estas clases maneja todo lo relacionado a la administración de un motor de
ejecución (listar, eliminar, suspender o resumir redes de Petri), mientras que la
segunda, maneja todo lo vinculado con importar y exportar redes de Petri hacia
y desde el motor.
La clase FrmEditor implanta la vista (ver Figura 4.4) y es la encargada de
interactuar con el documento y con Acuarela para permitir la edición de redes
de Petri. Esta clase tiene dos modalidades de ejecución; la primera permite al
usuario crear y editar redes de Petri, y la segunda le permite conectarse a una
red que esté corriendo en algún motor con el fin de supervisarla y/o enviarle
eventos manualmente.
Capı́tulo 6

Conclusiones y Recomendaciones

6.1. Conclusiones

En general, la idea de disponer de un motor de ejecución de redes de Petri


resulta bastante atractiva. Esto se debe a la gran cantidad de sistemas que se
pueden modelar utilizando redes de Petri. Si bien inicialmente este proyecto fue
concebido para hacer un motor de ejecución de redes de Petri con el fin de modelar
sistemas de producción (sobre todo pensando en el enfoque holónico), a medida
que se desarrollaba el software, se pudo apreciar que la utilidad del motor no se
limita únicamente a esas ramas de la industria. De hecho, una gran cantidad de
sistemas pueden ser modelados por medio de este tipo de redes, desde procesos
de producción, sistemas de inteligencia artificial, sistemas de información, etc.
El autor de este proyecto está particularmente ligado al mundo del desarrollo
de software, y se ha encontrado en más de un momento frente a sistemas complejos
que pueden ser fácilmente modelados utilizando redes de Petri, pero que, sin
embargo, resultan una verdadera pesadilla al momento de implantarlos. De esta
manera, si bien luego de programar varias soluciones similares, se puede definir
un mecanismo común a la hora de atacar esta clase de problemas, no queda más
remedio que reinventar la rueda en cada problema de este tipo. Sin embargo, al
disponer de un motor de ejecución de redes de Petri se tiene una herramienta
66 Conclusiones y Recomendaciones

poderosa que puede utilizarse para atacar esta clase de problemas siempre de la
misma forma. Mejor aún, si la herramienta puede expandirse fácilmente, entonces
se garantiza de esta forma que nunca escaseará un componente determinado.
Por todas estas razones, el autor considera que además del motor de ejecución,
los aportes más importantes del presente trabajo son la definición de un formato
de archivo XML que permita describir redes de Petri, ası́ como la especificación
de una arquitectura de componentes que permite expandir fácilmente el motor
y cualquier aplicación que utilice redes de Petri. En el primer caso, un archivo
estándar que sirve para describir y definir redes, permite el intercambio de las mis-
mas entre distintas aplicaciones. Por otra parte, la arquitectura de componentes
permite definir lugares, aristas y transiciones con casi cualquier comportamiento
deseado. Esto, en conjunto con el formato de archivo XML, se convierte en una
herramienta poderosa para definir, ejecutar, simular y editar redes de Petri.
El presente desarrollo (si bien es totalmente funcional) sirve como primera
aproximación a la solución del problema. De las aplicaciones en las que el motor
sea utilizado y de la experiencia obtenida desarrollando nuevos componentes se
deben derivar futuros trabajos al respecto. Sobre todo, es importante tener en
cuenta que existen muchos problemas en la industria (y no sólo los relacionados
con la industria de la manufactura, sino también los relacionados con la industria
del desarrollo de software) que pueden ser fácilmente resueltos utilizando redes de
Petri. Por esta razón, un motor que permita ejecutar tales tipos de redes, puede
resultar una herramienta invaluable, sobre todo si éste se combina con software
pensado para manejar procesos de producción, sobre todo dentro del enfoque
holónico.

6.2. Recomendaciones
Muchas de estas recomendaciones salen de la lista de “TODO”’s (“por hacer”)
del software, que generalmente está formada por comentarios en el código fuente
del mismo. Es importante recordar que el desarrollo de software es un proceso de
6.2 Recomendaciones 67

constante aprendizaje sobre el problema que se está resolviendo, de manera que


al implantar una solución, pueden surgir miles de nuevas ideas que resultan ten-
tadoras de incorporar al proyecto. Sin embargo, es necesario hacer una distinción
clara de lo que es en verdad factible agregar, porque de lo contrario, se puede
caer fácilmente en un desarrollo sin fin, que nunca culmina (o lo hace después de
mucho tiempo), debido al contingente de cambios que se van haciendo a medida
que se implanta el software.
Por lo general, respecto al desarrollo de software y a la comprensión del pro-
blema que se está resolviendo, siempre me ha gustado una cita que tuve el agrado
de leer en un artı́culo titulado “La Catedral y el Bazar” (Eric S. Raymond, 2000):

No se entiende cabalmente un problema hasta que se implementa


la primera solución. La siguiente vez quizás uno ya sepa lo suficiente
para solucionarlo. Ası́ que si quieres resolverlo, disponte a empezar de
nuevo al menos una vez.

Lo cual no quiere decir, en lo absoluto, que no se deba conocer el problema


antes de comenzar a implementar la primera solución, pero si que hay muchos
detalles que serán apreciados durante y una vez culminado el desarrollo. Es por
eso, que muchas de estas recomendaciones son sugerencias para una segunda
versión, y que la misma deberı́a estar precedida de un estudio sobre los escollos y
problemas con los que se ha topado esta primera versión, para de esta forma, poder
reflexionar al respecto y atacar cualquier desarrollo posterior con una conciencia
más profunda sobre el problema.

Hay que mejorar la arquitectura del API de componentes, de forma que se


pueda hacer una separación eficiente entre el motor y el editor. Actualmente,
ambos módulos están muy ligados entre si, lo que representa un error de
diseño, ya que por ejemplo, es necesario tener el jar de Acuarela en el
servidor de aplicaciones para que el motor pueda correr correctamente. Sin
embargo, esto se debe entre otras cosas, a un error de diseño en Acuarela,
que debe ser corregido antes de intentar tal separación.
68 Conclusiones y Recomendaciones

Es fundamental mejorar el manejo de concurrencia del motor. El bloqueo a


nivel de eventos es extremadamente seguro, pero demasiado ineficiente para
un ambiente de producción real. Por esta razón, es necesario implantar un
bloqueo a nivel de componentes e integrarlo con la noción transaccional de
la ejecución de un evento. Sin embargo, esto trae consigo las dificultades de
tener que manejar un grafo de recursos para poder luchar con situaciones
de abrazos mortales y otras complicaciones.

Es recomendable desarrollar un servicio Web que permita administrar un


motor y visualizar las redes de Petri que se estén ejecutando en el mismo.
Esto se puede desarrollar fácilmente utilizando Servlet o JSP en conjunto
con la capacidad gráfica de Acuarela.

Es recomendable separar la interfaz de administración del capturador de


eventos para adaptarse completamente a la arquitectura de la Figura 4.3. La
independencia entre estas interfaces le da más flexibilidad al motor cuando
corre como servicio, pero complica la API del mismo al momento de correr
como librerı́a. En general, este punto y el siguiente pueden comprometer la
compatibilidad de la API del motor entre el modo local y el modo remoto,
haciéndolas diferentes entre si.

Serı́a importante a largo plazo implementar todas las interfaces del motor
(administración, capturador de eventos y notificación de sucesos), utilizando
JMS de forma ası́ncrona o, en el peor de los casos, utilizando un Message
Driven Session Bean en lugar de un Session Bean. Esto harı́a más sencilla la
comunicación con el motor (desde el punto de vista del cliente y del servidor)
y permitirı́a facilitar la portabilidad del motor entre distintos servidores de
aplicaciones.

En general, es importante mejorar la bitácora del motor y permitir hacer


una supervisión remota del mismo. Tal mejora tiene como función facilitar
la depuración de los componentes añadidos por el usuario y además llevar
6.2 Recomendaciones 69

un registro más tangible de los distintos sucesos y eventos que ocurren en


el motor.

Es recomendable incrementar los niveles de seguridad del motor (en especial


cuando corre como un servicio y puede ser accedido de forma remota),
obligando a los clientes a identificarse por medio de un “login” y “password”.
Esto aumenta la complejidad de la base de datos y de las interfaces remotas
del motor, ya que por un lado es necesario almacenar una lista de usuarios, y
por el otro se debe implantar algún protocolo de autenticación de usuarios.

Puede ser útil modificar el repositorio de redes de Petri y la estructura


de tabla donde se almacenan las redes en la base de datos (ver sección
5.3.3), para que pueda almacenar la última fecha de acceso a una red y
otras estadı́sticas de utilidad. Esto puede permitir establecer un control
más profundo sobre las redes almacenadas y además sirve para eliminar de
la base de datos, redes que estén inactivas por un periodo mayor de cierto
tiempo.

Es necesario modificar la arquitectura de las transiciones, de modo que


pueda ser posible encadenar distintos efectos al momento de disparar una
transición. Por ejemplo, puede ser necesaria una transición que al dispararse
ejecute cierto código JavaTM y que además envı́e un evento a otra transición
(transiciones de las secciones 3.2.4 y 3.2.3 respectivamente)
70 Conclusiones y Recomendaciones
Apéndice A

Definición de Redes de Petri en


XML

Una red de Petri se puede describir en XML mediante una lista de lugares,
transiciones, aristas y eventos. A continuación se muestra un ejemplo de la es-
tructura del archivo XML:

<petrinet>
<place name = "..." type = "...">
<data-object name = "..." type = "...">
<property key = "..." value = "..."/>
...
</data-object>
...
</place>
...
<transition name = "..." type = "..." event = "...">
<data-object name = "..." type = "...">
<property key = "..." value = "..."/>
...
</data-object>
72 Definición de Redes de Petri en XML

...
</transition>
...
<edge name = "..." type = "..." src = "..." dst = "...">
<data-object name = "..." type = "...">
<property key = "..." value = "..."/>
...
</data-object>
...
</edge>
...
<event name = "...">
<class name="..."></class>
</event>
...
</petrinet>

El XML contiene:

1. Una lista de lugares que se describen por medio de la etiqueta <place


name=”...”type=”...”>. Donde name contiene el nombre del lugar y ty-
pe el nombre de la clase JavaTM que implementa ComponentFactory y que
sirve de fábrica para las instancias del componente.

2. Una lista de transiciones que se describen por medio de la etiqueta <transition


name=”...”type=”...”event=”...”>. Donde name contiene el nombre de la
transición, type el nombre de la clase JavaTM que implementa Component-
Factory y que sirve de fábrica para las instancias del componente y event
que corresponde al nombre del evento frente al cual responde la transición.

3. Una lista de aristas que se describen por medio de la etiqueta <edge na-
me=”...”type=”...”src=”...”dst = ”...”>. Donde name contiene el nombre
A Definición de Redes de Petri en XML 73

de la arista, type el nombre de la clase JavaTM que implementa Compo-


nentFactory y que sirve de fábrica para las instancias del componente, src
que es el nombre del nodo de origen (lugar o transición) y dst que es el
nombre del nodo de destino (lugar o transición).

4. Una lista de eventos asociados a la red que se describen por medio de la


etiqueta <event name=”...”>. Donde name contiene el nombre del evento.

Por otra parte, cada lugar, transición o arista contienen una lista de data
objects que sirven para describir las propiedades particulares de los componentes.
Un data object se representa mediante la etiqueta <data-object name = ”...”type
= ”...”>, donde name es el nombre del objeto y type es su descripción en XClass.
Cada data object contiene una lista de propiedades que se representan por medio
de etiquetas <property key = ”...”value = ”.../>, donde key representa el nombre
o la clave de la propiedad y value el valor de la misma. En general, dos instancias
distintas de un mismo tipo de componente comparten data objects del mismo tipo
y nombre, pero que están parametrizados de forma distinta según las necesidades
de cada instancia.
Finalmente, cada evento representado en la lista de eventos contiene un des-
criptor XClass que sirve para definir los parámetros asociados al mismo. En el
apéndice B se puede obtener información más detallada respecto a XClass.
Por ejemplo, un lugar de fichas (ver sección 3.3.2) puede describirse de esta
forma:

<place name="p1"
type="org.cyrano.pnet.plugins.plugins.TokenPlaceFactory">
<data-object name="aPlaceData"
type="org.cyrano.pnet.editor.acuarela.APlaceData">
<property key="X_obj" value="168"/>
<property key="X_lbl" value="168"/>
<property key="Y_obj" value="323"/>
<property key="Y_lbl" value="360"/>
74 Definición de Redes de Petri en XML

</data-object>
<data-object name="properties"
type="org.cyrano.pnet.mapping.TokenPlace">
<property key="token-cnt" value="1"/>
<property key="token-max" value="2"/>
<property key="token-min" value="0"/>
</data-object>
</place>

De esta forma, se describe un lugar llamado p1 que tiene como fábrica de com-
ponentes a la clase org.cyrano.pnet.plugins.plugins.TokenPlaceFactory. Además,
la descripción del lugar está formada dos data objects, el primero, llamado aPla-
ceData, que contiene información geométrica del mismo (donde dibujarlo) y el
segundo, llamado properties, que contiene la parametrización concreta que define
el comportamiento de este lugar en particular. En este caso, el data object pro-
perties especifica que el lugar contiene una ficha y que puede tener un máximo de
dos y un mı́nimo de cero (token-cnt, token-max y token-min respectivamente).
Un ejemplo que define una transición:

<transition name="t1"
type="org.cyrano.pnet.plugins.plugins.SimpleTransFactory"
event="evt_A">
<data-object name="aTransData"
type="org.cyrano.pnet.editor.acuarela.ATransData"> ...
</data-object>
<data-object name="default-properties"
type="org.cyrano.pnet.mapping.TransDefaultProperties">
<property key="precedence" value="0"/>
</data-object>
</transition>
A Definición de Redes de Petri en XML 75

En este caso, no se muestran las propiedades del data object aTransData, ya


que son muy parecidas y cumplen la misma función que las de aPlaceData del
ejemplo anterior. Sin embargo, se aprecia el data object default-properties, que de-
be estar presente en todas las transiciones y que además debe tener una propiedad
llamada precedence que es de tipo entero y representa el nivel de precedencia de
la transición (ver sección 3.2.1).
La descripción de una arista se muestra a continuación:

<edge name="edge1"
type="org.cyrano.pnet.plugins.plugins.TokenEdgeFactory"
src="t1" dst="p1">
<data-object name="default-properties"
type="org.cyrano.pnet.mapping.EdgeDefaultProperties">
<property key="inhibitor" value="true"/>
</data-object>
<data-object name="properties"
type="org.cyrano.pnet.mapping.TokenEdge">
<property key="token-weight" value="1"/>
</data-object>
</edge>

De igual forma, en el XML anterior se aprecian los data objects de una arista.
Este caso particular define una arista de fichas (ver sección 3.4.1), y el peso de
la misma se describe con en el data object properties por medio de la propiedad
entera token-weight. Por otra parte, el data object default-properties debe estar
presente en todos los tipos de aristas y debe contener al menos una propiedad de
tipo booleano llamada inhibitor, esto con el fin de representar arcos inhibidores
(ver sección 2.2).
76 Definición de Redes de Petri en XML
Apéndice B

Tecnologı́as y Términos más


Comunes en Java

Termino Java Definición


Applet Un applet es un programa Java que se ejecuta dentro de un
navegador. Los applets usan una interfaz gráfica con el usuario
y pueden tener texto, imágenes, botones, barras de desplaza-
miento y sonido. AWT y SWING son asociados frecuen-
temente con artı́culos y tutoriales sobre la construcción de
applets.
AWT AWT (Abstract Window Toolkit), según sus siglas en ingles,.
es un paquete de clases para la creación de componentes, tales
como botones, menús y barras de desplazamiento para Ap-
plets o aplicaciones independientes (standalone)
Java API Java API (Java Application Programing Interface), según sus
siglas en ingles, es un código pre-escrito, organizado en paque-
tes de tópicos similares. Todo el API de Java está incluido en
Java 2 Standart Edition descargable en la página de SUN
Microsystems URL: java.sun.com
78 Tecnologı́as y Términos más Comunes en Java

JavaBeans La arquitectura JavaBeans provee una forma de diseño de


software reusable que puede ser manipulada visualmente en
herramientas de construcción. Los Beans pueden ser simples
como botones o más complejos como herramientas de acceso
a bases de datos.
JFC JFC (Java Foundation Classes) son un juego de componentes
gráficos GUI y otros servicios que ayudan a la simplificación
de desarrollo y despliegue de aplicaciones Internet/Intranet.
Java Native JNI es la interfaz de programación nativa para Java que es
Interface parte del JDK. JNI aloja código Java para operar con aplica-
(JNI) ciones y librerı́as escritas en otros lenguajes como C, C++ y
ensamblador. Recomendado sólo para usuarios avanzados.
Java Server Con JSP se pueden crear paginas dinámicas en las cuales se
Pages (JSP) puede insertar código con HTML. Las páginas JSP procesan
formas, realizan cálculos, o pueden hacer cualquier cosa que
se pueda escribir en un programa Java.
Java Virtual La máquina virtual de Java ejecuta instrucciones que genera
Machine el compilador de Java. Este ambiente de ejecución o JVM en
la actualidad está implı́cito en varios productos como navega-
dores, sistemas operativos y servidores, entre otros.
JDBC JDBC es un API de Java para la ejecución de sentencias
SQL.Usando el API de JDBC se puede acceder a casi to-
das las fuentes de datos, desde bases de datos hasta hojas de
cálculo o algo más simple como archivos planos. En J2SE se
incluye el API de JDBC.
JDK JDK es el nombre corto para el conjunto de herramientas de
Java (Java Development Kit), esta constituido por el API, el
compilador, y la máquina virtual que varı́an de acuerdo a la
versión. El JDK es utilizado para compilar aplicaciones Java
y Applets.
B Tecnologı́as y Términos más Comunes en Java 79

Jini Jini es una tecnologı́a de redes que habilita cualquier servicio


a trabajar fluido y simple en red. La arquitectura permite
a cada servicio (programa o dispositivo) decir a otros como
hablar con el, sin necesidad de ningún administrador.
Swing El paquete de clases javax.swing es usado para crear compo-
nentes gráficos (GUI) para Applets o aplicaciones. El proyecto
Swing permite a programadores especificar un tema diferente
para cada plataforma (Look and Feel), o un único tema para
todas las plataformas. Swing es el nombre del proyecto para
componentes gráficos (GUI) de bajo peso en JFC
RMI RMI (Remote Method Invocation) según sus siglas en ingles,
permite a aplicaciones Java a comunicarse a través de la red.
Estas aplicaciones pueden estarse ejecutando en diferentes
computadores en lados opuestos del planeta. Este modelo per-
mite el acceso a un objeto remoto tan fácil como uno local.
Servlets Los Servlet son una extensión JavaTM de un servidor
WEB, con el fin de mejorar su funcionalidad. Los Servlets
comúnmente son utilizados para procesar formas, manejar re-
direcciones o autentificar nombres de usuarios y contraseñas,
y crear contenido dinámico.
XClass XClass es una tecnologı́a que tienen como objetivo brindar
soporte para manejar y describir meta-objetos, que pueden
ser utilizados de una forma práctica y sencilla, por una gran
variedad de aplicaciones.
Acuarela Acuarela es una librerı́a desarrollada en JavaTM diseñada
para facilitar el desarrollo de aplicaciones que contengan edi-
tores gráficos de objetos, herramientas de visualización y ani-
maciones en general. Se basa fundamentalmente en el modelo
MVC (Model View Controller).
80 Tecnologı́as y Términos más Comunes en Java

MBeans MBeans es una librerı́a de beans gráficos para hacer interfaces


hombre - máquina basadas en tecnologı́a Swing de JavaTM .
En general, MBeans pretende resolver y cubrir algunas defi-
ciencias de Swing para facilitar un poco la programación de
GUI.
MinoDB MinoDB es una librerı́a desarrollada en JavaTM que hace
posible la persistencia transparente de objetos JavaTM en
una base de datos relacional. La librerı́a permite mecanizar
y automatizar todo lo relacionado con el manejo de la base
de datos, de modo que el cliente pueda concentrarse más en
su aplicación y menos en el manejo de la persistencia de los
datos.
Cuadro B.1: Tecnologı́as más Comunes en Java
Apéndice C

Acrónimos

API: Application Programming Interface.


EJB: Enterprise Java Beans.
GUI: Graphics User Interface.
JDBC: Java Database Connectivity.
JMS: Java Message Service.
JNDI: Java Naming and Directory Interface.
JNI: Java Native Interface.
JSP: Java Server Pages.
JVM: Java Virtual Machine.
MVC: Model View Controller.
RMI: Remote Method Invocation.
SQL: Structured Query Language.
XML: Extensible Markup Language.
82 Acrónimos

Referencias
A.H. Lewis. (1999). An introduction to petri nets.
Eric S. Raymond. (2000). The cathedral and the bazaar.
http://www.tuxedo.org/ esr/writings/cathedral-bazaar/.
Gile, M. R., & DiCesare, F. (*). Toward distributed simulation of complex
discrete event systems represented by colored petri nets: A review.
Harald Störrle. (1998). An evaluation of high-end tools for petri nets.
Janette Cardoso, B. Pradin-Chézalviel. (1997, Jun). Logic and fuzzy petri nets.
A Workshop within the XVIII International Conference on Applications
and Theory of Petri Nets.
Janette Cardoso, Robert Valette. (1997). Redes de petri. Editora da Universidade
Federal de Santa Catarina.
Krzysztof Sacha. (2001, Dec). Fault analysis using petri nets.
Kurt Jensen. (1997). A brief introduction to coloured petri nets. 203-208. Lecture
Notes in Computer Science Vol. 1217, Springer-Verlag.
The petri nets world. (2004). URL: http://www.daimi.au.dk/PetriNets/. (An on-
line database of Petri net-related conferences, mailing lists, bibliographies,
tool databases, newsletters, research groups, etc)
Raskin, J., Tan, Y., & Torre, L. van der. (1996). How to model normative
behavior in petri nets.
Robert Esser. (1996). An object oriented petri net approach to embedded system
design. Unpublished doctoral dissertation.
Robert Valette, Brigitte Pradin-Chézalviel, François Girault. (*). An introduction
to petri net theory.
Valette, R. (1997). Some issues about petri net application to manufacturing and
process supervisory control. In ICATPN (p. 23-41).

Вам также может понравиться