Solução dos desafios 2 e 3
Desafio 2: é comum que os comandos de criação de entidades nos softwares proprietários tenham a opção de limitar o número máximo de entidades geradas durante a simulação.
Modifique a funçãogeraChegadas
de modo que ela receba como parâmetro onumeroMaxChegadas
e limite a criação de entidades a este número.
Neste caso, o script em Python é autoexplicativo, apenas note que limitei o número de chegadas em 5 e fiz isso antes da chamada do processo gerado pela função geraChegadas()
:
import random # gerador de números aleatórios
import simpy # biblioteca de simulação
def geraChegadas(env, nome, taxa, numeroMaxChegadas):
# função que cria chegadas de entidades no sistema
contaChegada = 0
while (contaChegada < numeroMaxChegadas:
yield env.timeout(random.expovariate(1/taxa))
contaChegada += 1
print("%s %i chega em: %.1f " % (nome, contaChegada, env.now))
random.seed(1000) # semente do gerador de números aleatórios
env = simpy.Environment() # cria o environment do modelo
# cria o processo de chegadas
env.process(geraChegadas(env, "Cliente", 2, 5))
env.run(until=10) # executa a simulação por 10 unidades de tempo
Desafio 3: modifique a função
geraChegadas
de modo que as chegadas entre entidades sejam distribuídas segundo uma distribuição triangular de moda 1, menor valor 0,1 e maior valor 1,1.
Neste caso, precisamos verificar na documentação da biblioteca random
, quais são nossas opções. A tabela a seguir, resume as distribuições disponíveis:
Função | Distribuição |
---|---|
random.random() |
gera números aleatórios no intervalo [0.0, 1.0) |
random.uniform(a, b) |
uniforme no intervalo [a, b] |
random.triangular(low, high, mode) |
triangular com menor valor low, maior valor high e moda mode |
random.betavariate(alpha, beta) |
beta com parâmetros alpha e beta |
random.expovariate(lambd) |
exponencial com média 1/lambd |
random.gammavariate(alpha, beta) |
gamma com parâmetros alpha e beta |
random.gauss(mu, sigma) |
normal com média mu e desvio padrão sigma |
random.lognormvariate(mu, sigma) |
lognormal com média mu e desvio padrão sigma |
random.normalvariate(mu, sigma) |
equivalente à random.gauss, mas um pouco mais lenta |
random.vonmisesvariate(mu, kappa) |
distribuição de von Mises com parâmetros mu e kappa |
random.paretovariate(alpha) |
pareto com parâmetro alpha |
random.weibullvariate(alpha, beta) |
weibull com parâmetros alpha e beta |
A biblioteca NumPy, que veremos oportunamente, possui mais opções para distribuições estatísticas. Por enquanto, o desafio 3 pode ser solucionado de maneira literal:
import random # gerador de números aleatórios
import simpy # biblioteca de simulação
def geraChegadas(env, nome, numeroMaxChegadas):
# função que cria chegadas de entidades no sistema
contaChegada = 0
while (contaChegada < numeroMaxChegadas:
yield env.timeout(random.triangular(0.1,1,1.1))
contaChegada += 1
print("%s %i chega em: %.1f " % (nome, contaChegada, env.now)))
random.seed(1000) # semente do gerador de números aleatórios
env = simpy.Environment() # cria o environment do modelo
# cria o processo de chegadas
env.process(geraChegadas(env, "Cliente, 5))
env.run(until=10)
Dica
Os modelos de simulação com muitos processos de chegadas e atendimento, tendem a utilizar diversas funções diferentes de distribuição de probabilidades, deixando as coisas meio confusas para o programador.
Uma dica bacana é criar uma função que armazene todas as distribuições do modelo em um único lugar, como uma prateleira de distribuições.
Por exemplo, imagine um modelo em SimPy que possui 3 processos: um exponencial com média 10 min, um triangular com parâmetros (10, 20, 30) min e um normal com média 0 e desvio 1 minuto. A função distribution()
a seguir, armazena todos os geradores de números aleatórios em um único local:
import random
def distributions(tipo):
return {
'arrival': random.expovariate(1/10.0),
'singing': random.triangular(10, 20, 30),
'applause': random.gauss(10, 1),
}.get(tipo, 0.0)
O próximo exemplo testa como chamar a função:
tipo = 'arrival'
print(tipo, distributions(tipo))
tipo = 'singing'
print(tipo, distributions(tipo))
tipo = 'applause'
print(tipo, distributions(tipo))
O qual produz a saída:
arrival 6.231712146858156
singing 22.192356552471104
applause 10.411795571842426
Essa foi a nossa dica do dia!
Fique a vontade para implementar funções de geração de números aleatórios ao seu gosto. Note, e isso é importante, que praticamente todos os seus modelos de simulação em SimPy precisarão deste tipo de função!
Uma última observação:
Atenção à eficiência do código. Como a finalidade deste texto é prioritariamente didática, as chamadas às funções de geração de números aleatórios respeitam a lógica do aprendizado. Contudo, tais chamadas não são exatamente eficientes... Por exemplo, você consegue descobrir qual das duas chamadas a seguir é a mais eficiente e por quê?
while True:
yield env.timeout(random.expovariate(1.0/2.0))
Ou:
while True:
yield env.timeout(random.expovariate(0.5))
Resposta: note que, no primeiro caso, a cada novo número gerado é realizada uma operação de divisão. No segundo caso, isso não ocorre, deixando o tempo de processamento bem mais rápido.
Teste seus conhecimentos:
- Acrescente ao programa inicial, uma função
distribution
como a proposta na Dica do Dia e faça o tempo entre chegadas sucessivas de entidades chamar a função para obter o valor correto. - Considere que 50% das entidades geradas durante a simulação são do sexo feminino e 50% do sexo masculino. Modifique o programa para que ele sorteie o gênero dos clientes. Faça esse sorteio dentro da função
distribution
já criada.