Bel. Ciência da Computação (UFPI) • FACOMP & PPGCC - ICEN/UFPA • Membro do LAAI • Pesquisador de Inteligência Computacional Aplicada • Disciplinas de Fundamentos da Computação e Matemática Computacional • Desenvolvedor de Software Livre Filipe Saraiva | UFPA | 4 / 67
um sistema real através de modelos. A possibilidade de criar e simular fenômenos desejados permite conferir quão representativas seriam as mudanças, colaborando, dessa forma, com a tomada de decisões [BATEMAN et al, 2013]. Filipe Saraiva | UFPA | 8 / 67
as filas, maior o tempo de espera dos clientes; • Quanto maior o tempo de espera dos clientes, menos atrativo fica o supermercado; • Quanto maior o número de caixas, mais rápido é o atendimento aos clientes; • Quanto maior o número de caixas, maior os custos de mantê-los; • Nem sempre a disponibilidade da quantidade máxima de caixas é necessária; Como avaliar a tomada de decisão sobre dimunir/manter/aumentar o número de caixas? Filipe Saraiva | UFPA | 10 / 67
resolução de problemas; • A simulação pode prever resultados; • A simulação considera variâncias do sistema; • A simulação promove soluções totais; • A simulação pode ser financeiramente viável. Filipe Saraiva | UFPA | 11 / 67
não prevê o futuro; • A simulação não é uma ferramenta estritamente de otimização; • A simulação não substitui o processo de tomada de decisão; • A simulação não é uma panaceia. Filipe Saraiva | UFPA | 12 / 67
de tempo, modificando o estado do sistema. Não há modificação do estado do sistema entre eventos consecutivos. • Simulação Contínua - a simulação acompanha a dinâmica do sistema ao longo do tempo, sem saltos discretos de um evento a outro. Normalmente simulações contínuas são descritas em forma de equações. Filipe Saraiva | UFPA | 13 / 67
discreta. É de fácil uso e disponibilizado como software livre, sendo portanto amplamente adotado na academia e indústria. Website: https://simpy.readthedocs.io/ Filipe Saraiva | UFPA | 17 / 67
como root utilize o gerenciador de pacotes pip, conforme abaixo: # pip install simpy Após instalado, execute o interpretador Python e tente importar o SimPy para verificar se funcionou. $ python > > > import simpy > > > Filipe Saraiva | UFPA | 18 / 67
componentes que nos permite modelar e simular sistemas de maneira discreta. Fundamentalmente, o SimPy é um escalonador de processos que agenda eventos das entidades a serem executados em uma linha de tempo comum. Filipe Saraiva | UFPA | 20 / 67
componente do SimPy) que registra os Events, que são as funções que devem ser executadas. Assim, o Environment registra os eventos e cuida de acompanhar o tempo de simulação, executando-o e suspendendo-os no momento correto. Filipe Saraiva | UFPA | 21 / 67
temos uma classe Python Pessoa que recebe como argumento no método construtor a variável env. env é um Environment do SimPy. Ela registrará o método run da classe na instrução self.env.process(self.run()). class Pessoa : def __init__ ( self , env ) : s e l f . env = env s e l f . env . process ( s e l f . run ( ) ) Filipe Saraiva | UFPA | 22 / 67
a atividade que a classe Pessoa irá executar para a simulação. Nele, a pessoa passará 2 tempos da simulação estudando e em seguida 5 tempos nas redes sociais. Após isso, ela voltará aos estudos e continuará nesse ciclo, indefinidamente. O método run é implementado da seguinte maneira: Filipe Saraiva | UFPA | 23 / 67
: while True : print ( ’Comecou a estudar em ’ , s e l f . env . now) tempo_estudo = 2 y i e l d s e l f . env . timeout ( tempo_estudo ) print ( ’ Foi para as redes sociais em ’ , s e l f . env . now) tempo_redes_sociais = 5 y i e l d s e l f . env . timeout ( tempo_redes_sociais ) Filipe Saraiva | UFPA | 24 / 67
while True: destaca que aquele bloco é um loop infinito; • Perceba o uso do yield, para suspender a execução de processos; • O yield lança um self.env.timeout($variavel). O método timeout de uma env significa que o relógio global de simulação mudará para o tempo em que aquela atividade terá sido finalizada. Filipe Saraiva | UFPA | 25 / 67
interpretador Python, carregar o SimPy, o arquivo da classe, instanciar objetos e executar. $ python >>> import simpy >>> exec(open(’pessoa.py’).read()) >>> env = simpy.Environment() >>> p = Pessoa(env) >>> env.run(until=20) Começou a estudar em 0 Foi para as redes sociais em 2 Começou a estudar em 7 Foi para as redes sociais em 9 Começou a estudar em 14 Foi para as redes sociais em 16 Filipe Saraiva | UFPA | 26 / 67
importa o módulo SimPy para o interpretador; • exec(open(’pessoa.py’).read()) é o comando usado para carregar o arquivo com a classe “Pessoa” no interpretador Python; • env = simpy.Environment() instancia a classe Environment; • p = Pessoa(env) instancia um objeto Pessoa passando env como argumento; • env.run(until=20) executa a simulação. O argumento until fixa o tempo em que a simulação irá terminar. • Em itálico temos a saída da simulação. Filipe Saraiva | UFPA | 27 / 67
ocorre, sendo o responsável por cadastrar os processos e eventos que serão executados durante a simulação, sequenciá-los, e registrar o tempo global de simulação. Filipe Saraiva | UFPA | 29 / 67
simpy.Environment(), passando-a para alguma variável. No SimPy também há uma classe voltada para simulações em tempo-real chamada RealtimeEnvironment. Filipe Saraiva | UFPA | 30 / 67
que serão simulados no Environment. Para isso, utiliza-se o método process, passando como argumento o método que será registrado. Por exemplo: class Pessoa : def __init__ ( self , env ) : . . . s e l f . env . process ( s e l f . run ( ) ) Temos o registro do método run no Environment que foi passado como argumento para a classe. Filipe Saraiva | UFPA | 31 / 67
hora de realizar a simulação. Fazendo env ser nossa instância do Environment, há 2 possibilidades de rodar a simulação: • env.run() executará a simulação até que não haja mais processos e eventos a serem simulados. É possível passar o parâmetro until=$valor para ser usado como o limite máximo do tempo de simulação a ser executado. • env.step() executa a próxima atividade da simulação. É necessário chamar novamente o step() para realizar a atividade seguinte. Filipe Saraiva | UFPA | 32 / 67
que pode ser acessado através do atributo now em env.now. Vale recordar que o “tempo de simulação” não tem uma unidade de tempo determinada, sendo mais como um meio de verificar momentos discretos quando uma atividade inicia ou finaliza. Também cabe destacar que, apesar de estarmos utilizando programação orientada a objetos, não há impedimentos de que uma função convencional, da programação imperativa, possa ser cadastrada como um processo ou evento no SimPy. Filipe Saraiva | UFPA | 33 / 67
quando realizamos um yield em um processo enviando um timeout. Dessa forma, estamos determinando que um evento foi executado. Para o exemplo do método “run” da classe “Pessoa”, o yield utilizado representa esse tipo de uso de eventos. . . . def run ( s e l f ) : while True : print ( ’Comecou a estudar em ’ , s e l f . env . now) tempo_estudo = 2 y i e l d s e l f . env . timeout ( tempo_estudo ) Filipe Saraiva | UFPA | 36 / 67
servirão para modificar o fluxo de eventos do SimPy. Para utilizar esse recurso, basta instanciar um atributo com env.event() e passá-lo com um yield. Isso suspenderá a execução de uma dada função até que o evento mude de estado para succeed. Nesse momento, o fluxo de execução retornará para o yield que lançou o evento. Filipe Saraiva | UFPA | 37 / 67
entra na mesma e fica no aguardo da chegada do mecânico. class Oficina : def __init__ ( self , env ) : s e l f . env = env s e l f . mecanico_disponivel = s e l f . env . event ( ) s e l f . env . process ( s e l f . chegou_carro ( ) ) s e l f . env . process ( s e l f . chegou_mecanico ( ) ) Filipe Saraiva | UFPA | 38 / 67
atributo mecanico_disponivel – a ideia é que quando o mecânico estiver disponível, será disparado um evento que poderá ser utilizado em nosso código. Passamos para as funções, que já foram registradas no construtor da classe. def chegou_carro ( s e l f ) : y i e l d s e l f . mecanico_disponivel print ( ’ Desmonte do carro em ’ , s e l f . env . now) def chegou_mecanico ( s e l f ) : y i e l d s e l f . env . timeout (13) s e l f . mecanico_disponivel . succeed ( ) Filipe Saraiva | UFPA | 39 / 67
pois ela foi escalonada primeiro no construtor. Percebemos que nela há um yield self.mecanico_disponivel, que posterga a continuação da execução da função para após o evento self.mecanico_disponivel ser lançado. A próxima função a ser executada é a chegou_mecanico, que contabiliza um tempo de execução no yield e em seguida lança o evento com a função succeed do evento instanciado, utilizada na instrução self.mecanico_disponivel.succeed(). Filipe Saraiva | UFPA | 40 / 67
de execução retorna ao yield da função chegou_carro(), permitindo então a execução do resto daquela função. Como exemplo, remova a instrução que torna sucesso o evento self.mecanico_disponivel. O que ocorre com o código e porque isso acontece? Filipe Saraiva | UFPA | 41 / 67
recebe um Exception do Python como argumento que será lançado assim que a função for chamada. Outro detalhe importante é que os próprios processos são Eventos: é possível fazer um yield env.process(funcao) que o yield permitirá a execução da parte posterior da função apenas após o processo ter sido finalizado com sucesso. Filipe Saraiva | UFPA | 42 / 67
operadores lógicos no yield de eventos. Dessa forma, tem-se: • Para que uma ação seja executada apenas após algum evento a dispare, basta utilizar o operador lógico ou (“|”). Ex.: yield envent1 | event2; • Para que uma ação seja executada apenas após que todos os eventos sejam executados, basta utilizar o operador lógico and (“&”). Ex.: yield envent1 & event2; • Operações lógicas mais complexas são possíveis a partir do uso dos operadores lógicos citados. Filipe Saraiva | UFPA | 43 / 67
no vídeo game esperando uma ligação para sair. No exemplo, ela sairá assim que terminar de jogar ou receber uma ligação. O início do código: class Sair : def __init__ ( self , env , t_video_game , t_ligacao ) : s e l f . env = env s e l f . ev_video_game = s e l f . env . event ( ) s e l f . ev_ligacao = s e l f . env . event ( ) s e l f . env . process ( s e l f . decide_sair ( ) ) s e l f . env . process ( s e l f . video_game ( t_video_game ) ) s e l f . env . process ( s e l f . ligacao ( t_ligacao ) ) Filipe Saraiva | UFPA | 44 / 67
e ev_ligacao, que simbolizam o momento em que a pessoa pára de jogar vídeo game ou recebe uma ligação, respectivamente. Em seguida, são cadastrados os processos decide_sair(), video_game e ligacao, que representam a decisão de sair, a função que processa a atividade de jogar vídeo game, e a que processa o recebimento de uma ligação. Filipe Saraiva | UFPA | 45 / 67
decide_sair ( s e l f ) : y i e l d s e l f . ev_video_game | s e l f . ev_ligacao print ( ’ Saio em ’ , s e l f . env . now) def video_game ( self , t_video_game ) : y i e l d s e l f . env . timeout ( t_video_game ) s e l f . ev_video_game . succeed ( ) def ligacao ( self , t_ligacao ) : y i e l d s e l f . env . timeout ( t_ligacao ) s e l f . ev_ligacao . succeed ( ) Filipe Saraiva | UFPA | 46 / 67
a ser executado é o decide_sair(), que na primeira instrução já lança um yield para os 2 eventos da classe. Como eles tem uma operação lógica “ou”, o método aguardará que um dos eventos seja lançado para continuar a execução. Os outros 2 métodos apenas lançam os respectivos tempos para suas atividades, e em seguida, lançam os respectivos eventos. Esse lançamento retornará o fluxo de execução do programa para o método decide_sair(). Como exercício, transforme a execução para que a decisão de sair aconteça apenas após todas as atividades terem sido realizadas. Filipe Saraiva | UFPA | 47 / 67
a partir da formação de um ponto de congestão no sistema. Dessa forma, processos que necessitam de um recurso devem formar uma fila e aguardar que o recurso esteja liberado para poder usá-lo. Filipe Saraiva | UFPA | 49 / 67
maneiras de utilização: • Resources – recursos que podem ser utilizados por um número limitado de processos por vez (exemplo: bombas em um posto de combustível); • Containers – recursos que modelam a produção ou consumo de algo homogêneo e não diferenciável (exemplo: combustível, minério); • Store – recurso que permite a produção e consumo de objetos Python. Filipe Saraiva | UFPA | 50 / 67
uma mesma concepção básica. Um recurso é um tipo de estrutura com uma capacidade máxima. Processos podem tentar colocar algo na estrutura ou retirar algo dela. Caso a estrutura esteja cheia ou vazia, os processos precisarão fazer uma fila e esperar que vague algum espaço para iniciar uma operação. Filipe Saraiva | UFPA | 51 / 67
e, após processar a atividade, disponibilizá-lo para o próximo processo em espera (exemplo: bomba em um posto de combustível). Para tanto são utilizados 2 métodos de uma instância de Resources: • request() para solicitar o recurso; • release() para liberar o recurso (normalmente feito de maneira automática ao fim da atividade). Filipe Saraiva | UFPA | 52 / 67
disponíveis no SimPy: • Resource – padrão, convencional; • PriorityResource – que atribui prioridades aos processos da fila e utiliza essa característica para ordená-los; • PreemptiveResource – adiciona preempção às prioridades, permitindo que processos com prioridades maiores finalizem a execução de um processo com prioridade menor que esteja na sua frente. Filipe Saraiva | UFPA | 53 / 67
simpy.Resource(env, capacity=), que recebe um ambiente e tem uma capacidade como parâmetro no construtor. Demais classes Resource são instanciadas de maneira similar, inclusive com a mesma quantidade de parâmetros. Para requisitar acesso a um recurso, utilizaremos gerenciadores de contexto do Python a partir do seguinte padrão: with recursos . request ( ) as recurso : y i e l d recurso . . . Filipe Saraiva | UFPA | 54 / 67
variável para ele com o nome “recurso”. A instrução a seguir lança um yield com a variável criada. Essa instrução aguardará que o recurso esteja disponível para então seguir com o método. with recursos . request ( ) as recurso : y i e l d recurso . . . Filipe Saraiva | UFPA | 55 / 67
para abastecimento. O método da classe Posto que faz o abastecimento é a seguinte: def abastecer ( self , carro ) : y i e l d s e l f . env . timeout ( carro . tempo_chegada ) print ( carro .nome, ’ chegou em ’ , s e l f . env . now) with s e l f . bombas . request ( ) as bomba : y i e l d bomba print ( carro .nome, ’ i n i c i o u abastecimento em ’ , s e l f . env . now) y i e l d s e l f . env . timeout ( carro . tempo_abastecimento ) print ( carro .nome, ’ saiu do abastecimento em ’ , s e l f . env . now) Filipe Saraiva | UFPA | 56 / 67
primeira instrução yield atrasa o processamento do abastecimento para o tempo de chegada do carro. Em seguida, no with ..., vemos a chamada da função “request”, que solicita o recursos “bombas”. A instrução seguinte, um yield, suspende essa execução até que alguma bomba esteja disponível. Estando disponível, temos um terceiro yield que processa o tempo de abastecimento do carro. Filipe Saraiva | UFPA | 57 / 67
que pode ser adicionado ou retirado. Imagine um tanque de combustível que é reduzido ao longo do tempo por conta dos abastecimentos – esse é um tipo de contêiner no SimPy. No instanciamento de um Container, utiliza-se no construtor uma referência para um Environment, um “capacity” para dizer a quantidade de algo no contêiner, e um “init” que indica qual quantidade inicia no contêiner. Filipe Saraiva | UFPA | 58 / 67
adicionar um tanque de combustível. Nosso tanque terá capacidade de 100 litros e iniciará a simulação cheio: No próximo slide temos a adição de uma lógica que chamará um caminhão tanque quando tiver menos de 50 litros no tanque: bombas = simpy . Resource ( env , capacity =1) tanque = simpy . Container ( env , i n i t =100, capacity =100) p = Posto ( env , bombas , tanque ) Filipe Saraiva | UFPA | 59 / 67
y i e l d s e l f . env . timeout ( carro . tempo_chegada ) print ( carro .nome, ’ chegou em ’ , s e l f . env . now) with s e l f . bombas . request ( ) as bomba : y i e l d bomba print ( carro .nome, ’ i n i c i o u abastecimento em ’ , s e l f . env . now) y i e l d s e l f . tanque . get ( carro . combustivel ) print ( ’ Tanque com %d l i t r o s ’ % ( s e l f . tanque . l e v e l ) ) y i e l d s e l f . env . timeout ( carro . tempo_abastecimento ) print ( carro .nome, ’ saiu do abastecimento em ’ , s e l f . env . now) i f s e l f . tanque . l e v e l < 50: print ( ’Chama caminhao de combustivel em ’ , s e l f . env . now) s e l f . env . process ( s e l f . reabastece ( ) ) y i e l d s e l f . env . timeout (5) Filipe Saraiva | UFPA | 60 / 67
quantidade de recursos no contêiner utilizamos a função “get()”, usando como argumento um inteiro para representar a quantidade que iremos reduzir. No método reabastece, temos a função “put()”, que coloca o valor inteiro que utilizarmos como argumento no contêiner. def reabastece ( s e l f ) : y i e l d s e l f . env . timeout (5) print ( ’ Caminhao tanque chegou em ’ , s e l f . env . now) y i e l d s e l f . tanque . put ( s e l f . tanque . capacity − s e l f . tanque . l e v e l ) print ( ’ Reabastecido − tanque com %d l i t r o s ’ % ( s e l f . tanque . l e v e l ) ) Filipe Saraiva | UFPA | 61 / 67
as funções “capacity()”, que informa a capacidade do mesmo, e a função “level()”, que informa a quantidade atual de elementos no contêiner. Filipe Saraiva | UFPA | 62 / 67
complexo que os vistos até agora: o gerenciamento de uso de pistas de pouso e locais de embarque/desembarque em um aeroporto. Filipe Saraiva | UFPA | 64 / 67
para simulações discretas em Python; • Nessa apresentação vimos os principais componentes de simulações do SimPy: Environment, Events e Resources; • É importante verificar se a modelagem responde às necessidades do problema e o quanto ela está adequada para os estudos; • O SimPy fica mais interessante quando trabalhado com outras bibliotecas do Python, como as científicas SciPy, NumPy e matplotlib. Filipe Saraiva | UFPA | 66 / 67