Desafio 15: considere que na barbearia, 40% dos clientes escolhem seu barbeiro favorito, sendo que, 30% preferem o barbeiro A, 10% preferem o barbeiro B e nenhum prefere o barbeiro C (o proprietário do salão). Construa um modelo de simulação representativo deste sistema.
Como existe preferência pelo barbeiro, naturalmente a escolha mais simples é trabalharmos com o FilterStore. O código a seguir, cria uma lista de barbeiros com os nomes, outra com os respectivos Resources, um dicionário para localizarmos o barbeiro por seu nome e, por fim, um FilterStore com os nomes dos barbeiros:
random.seed(50)env = simpy.Environment()# cria 3 barbeiros diferentes e armazena em um dicionáriobarbeirosNomes = ['Barbeiro A','Barbeiro B','Barbeiro C']barbeirosList = [simpy.Resource(env, capacity=1)for i inrange(3)]barbeirosDict =dict(zip(barbeirosNomes, barbeirosList))# cria um FilterStore para armazenar os barbeirosbarbeariaStore = simpy.FilterStore(env, capacity=3)barbeariaStore.items = barbeirosNomes# inicia processo de chegadas de clientesenv.process(chegadaClientes(env, barbeariaStore))env.run(until =20)
Quando um cliente chega, existe 40% de chance dele preferir o barbeiro A e 10% de preferir o barbeiro B. O código a seguir atribui o barbeiro utilizando-se da função random:
defchegadaClientes(env,barbeariaStore):# gera clientes exponencialmente distribuídos i =0whileTrue:yield env.timeout(random.expovariate(1/TEMPO_CHEGADAS)) i +=1# tem preferência por barbeiro? r = random.random()if r <=0.30: barbeiroEscolhido ='Barbeiro A'elif r <=0.40: barbeiroEscolhido ='Barbeiro B'else: barbeiroEscolhido ='Sem preferência'print("%5.1f Cliente %i chega.\t\t%s."%(env.now, i, barbeiroEscolhido))# inicia processo de atendimento env.process(atendimento(env, i, barbeiroEscolhido, barbeariaStore))
Por fim, o processo de atendimento deve diferenciar os clientes que possuem um barbeiro favorito. Assim, devemos criar uma função anônima lambda para resgatar o barbeiro correto do FilterStore:
defatendimento(env,cliente,barbeiroEscolhido,barbeariaStore):# ocupa um barbeiro específico e realiza o corte chegada = env.nowif barbeiroEscolhido !='Sem preferência':# retira do FilterStore o barbeiro escolhido no processo anterior barbeiro =yield barbeariaStore.get(lambdabarbeiro: barbeiro==barbeiroEscolhido)else:# cliente sem preferência, retira o primeiro barbeiro do FilterStore barbeiro =yield barbeariaStore.get() espera = env.now - chegadaprint("%5.1f Cliente %i inicia.\t\t%s ocupado.\tTempo de fila: %2.1f"%(env.now, cliente, barbeiro, espera))# ocupa o recurso barbeirowith barbeirosDict[barbeiro].request()as req:yield req tempoCorte = random.normalvariate(*TEMPO_CORTE)yield env.timeout(tempoCorte)print("%5.1f Cliente %i termina.\t%s liberado."%(env.now, cliente, barbeiro))# devolve o barbeiro para o FilterStore barbeariaStore.put(barbeiro)
Note, no código anterior, que, caso o cliente não tenha barbeiro preferido, o getdo FilterStore é utilizado sem nenhuma função lambda dentro do parêntesis.
Por fim, o código completo do modelo:
import simpyimport randomTEMPO_CHEGADAS =5# intervalo entre chegadas de clientesTEMPO_CORTE = [10,2] # tempo médio de corte defchegadaClientes(env,barbeariaStore):# gera clientes exponencialmente distribuídos i =0whileTrue:yield env.timeout(random.expovariate(1/TEMPO_CHEGADAS)) i +=1# tem preferência por barbeiro? r = random.random()if r <=0.30: barbeiroEscolhido ='Barbeiro A'elif r <=0.40: barbeiroEscolhido ='Barbeiro B'else: barbeiroEscolhido ='Sem preferência'print("%5.1f Cliente %i chega.\t\t%s."%(env.now, i, barbeiroEscolhido))# inicia processo de atendimento env.process(atendimento(env, i, barbeiroEscolhido, barbeariaStore))defatendimento(env,cliente,barbeiroEscolhido,barbeariaStore):#ocupa um barbeiro específico e realiza o corte chegada = env.nowif barbeiroEscolhido !='Sem preferência':# retira do FilterStore o barbeiro escolhido no processo anterior barbeiro =yield barbeariaStore.get(lambdabarbeiro: barbeiro==barbeiroEscolhido)else:# cliente sem preferência, retira o primeiro barbeiro do FilterStore barbeiro =yield barbeariaStore.get() espera = env.now - chegadaprint("%5.1f Cliente %i inicia.\t\t%s ocupado.\tTempo de fila: %2.1f"%(env.now, cliente, barbeiro, espera))# ocupa o recurso barbeirowith barbeirosDict[barbeiro].request()as req:yield req tempoCorte = random.normalvariate(*TEMPO_CORTE)yield env.timeout(tempoCorte)print("%5.1f Cliente %i termina.\t%s liberado."%(env.now, cliente, barbeiro))# devolve o barbeiro para o FilterStore barbeariaStore.put(barbeiro)random.seed(50)env = simpy.Environment()# cria 3 barbeiros diferentes e armazena em um dicionáriobarbeirosNomes = ['Barbeiro A','Barbeiro B','Barbeiro C']barbeirosList = [simpy.Resource(env, capacity=1)for i inrange(3)]barbeirosDict =dict(zip(barbeirosNomes, barbeirosList))# cria um FilterStore para armazenar os barbeirosbarbeariaStore = simpy.FilterStore(env, capacity=3)barbeariaStore.items = barbeirosNomes# inicia processo de chegadas de clientesenv.process(chegadaClientes(env, barbeariaStore))env.run(until =20)
Quando executado por apenas 20 minutos, o modelo anterior fornece:
3.4 Cliente 1 chega. Barbeiro A.3.4 Cliente 1 inicia. Barbeiro A ocupado. Tempo de fila:0.08.5 Cliente 2 chega. Sem preferência.8.5 Cliente 2 inicia. Barbeiro B ocupado. Tempo de fila:0.09.0 Cliente 3 chega. Barbeiro A.9.8 Cliente 4 chega. Sem preferência.9.8 Cliente 4 inicia. Barbeiro C ocupado. Tempo de fila:0.011.8 Cliente 1 termina. Barbeiro A liberado.11.8 Cliente 3 inicia. Barbeiro A ocupado. Tempo de fila:2.816.6 Cliente 2 termina. Barbeiro B liberado.19.0 Cliente 4 termina. Barbeiro C liberado.
Desafio 16: acrescente ao modelo da barbearia, a possibilidade de desistência e falta do barbeiro. Neste caso, existe 5% de chance de um barbeiro faltar em determinado dia. Além disso, considere 3 novas situações:
Se o barbeiro favorito faltar, o respectivo cliente vai embora;
O cliente que não possuir um barbeiro favorito olha a fila de clientes: se houver mais de 6 clientes em fila, ele desiste e vai embora;
O cliente que possui um barbeiro favorito, não esperará se houver mais de 3 clientes esperando seu barbeiro favorito.
Como teremos de identificar quantos clientes estão aguardando o respectivo barbeiro favorito, uma saída seria utilizar um dicionário para armazenar o número de clientes em fila (outra possibilidade seria um Store específico para cada fila):
random.seed(25)env = simpy.Environment()# cria 3 barbeiros diferentes e armazena em um dicionáriobarbeirosNomes = ['Barbeiro A','Barbeiro B','Barbeiro C']# falta de um barbeiroif random.random()<=0.05: barbeirosNomes.remove(random.choice((barbeirosNomes)))barbeirosList = [simpy.Resource(env, capacity=1)for i inrange(len(barbeirosNomes))]barbeirosDict =dict(zip(barbeirosNomes, barbeirosList))# dicionário para armazenar o número de clientes em fila de favoritos filaDict ={k:0for k in barbeirosNomes}# cria um FilterStore para armazenar os barbeirosbarbeariaStore = simpy.FilterStore(env, capacity=3)barbeariaStore.items = barbeirosNomes# inicia processo de chegadas de clientesenv.process(chegadaClientes(env, barbeariaStore))env.run(until =30)
Para garantir a falta de um barbeiro em 5% das simulações, foi novamente utilizado o comando random.random e o comando [random.choice](https://docs.python.org/dev/library/random.html#random.choice), que seleciona uniformemente um elemento da lista barbeirosNomes:
if random.random()<=0.05: barbeirosNomes.remove(random.choice((barbeirosNomes)))
Na linha anterior, além de sortearmos um dos barbeiros, ele é removido da lista de barbeiros, o que facilita o processo de desistência do cliente.
O processo de chegadas de clientes não precisa ser modificado em relação ao desafio anterior, contudo, o processo de atendimento precisa armazenar o número de clientes em fila por barbeiro - que pode ser feito por meio de um dicionário - e o número de clientes em fila total - que pode ser feito por meio de uma variável global que armazena o número total de clientes em fila.
Uma possível codificação para a nova função atendimento seria:
defatendimento(env,cliente,barbeiroEscolhido,barbeariaStore):# ocupa um barbeiro específico e realiza o corteglobal filaAtual # número de clientes em fila chegada = env.nowif barbeiroEscolhido !='Sem preferência':if barbeiroEscolhido notin barbeirosDict:# caso o barbeiro tenha faltado, desiste do atendimentoprint("%5.1f Cliente %i desiste.\t%s ausente."%(env.now, cliente, barbeiroEscolhido)) env.exit()if filaDict[barbeiroEscolhido]>3:# caso a fila seja maior do que 6, desiste do atendimentoprint("%5.1f Cliente %i desiste.\t%s com mais de 3 clientes em fila."%(env.now, cliente, barbeiroEscolhido)) env.exit()# cliente atual entra em fila e incrementa a fila do barbeiro favorito filaAtual +=1 filaDict[barbeiroEscolhido]= filaDict[barbeiroEscolhido]+1 barbeiro =yield barbeariaStore.get(lambdabarbeiro: barbeiro==barbeiroEscolhido) filaDict[barbeiroEscolhido]= filaDict[barbeiroEscolhido]-1else:# cliente sem preferência, verifica o tamanho total da filaif filaAtual >6:# caso a fila seja maior do que 6, desiste do atendimentoprint("%5.1f Cliente %i desiste.\tFila com mais de 6 clientes em fila."%(env.now, cliente)) env.exit()else:# cliente entra em fila e pega o primeiro barbeiro livre filaAtual +=1 barbeiro =yield barbeariaStore.get()# cliente já tem barbeiro, então sai da fila filaAtual -=1 espera = env.now - chegadaprint("%5.1f Cliente %i inicia.\t\t%s ocupado.\tTempo de fila: %2.1f"%(env.now, cliente, barbeiro, espera))# ocupa o recurso barbeirowith barbeirosDict[barbeiro].request()as req:yield req tempoCorte = random.normalvariate(*TEMPO_CORTE)yield env.timeout(tempoCorte)print("%5.1f Cliente %i termina.\t%s liberado."%(env.now, cliente, barbeiro))# devolve o barbeiro para o FilterStore barbeariaStore.put(barbeiro)
O código completo do modelo do desafio, ficaria:
import simpyimport randomTEMPO_CHEGADAS =5# intervalo entre chegadas de clientesTEMPO_CORTE = [10,2] # tempo médio de corte filaAtual =0# armazena o tamanho atual da fila de clientesdefchegadaClientes(env,barbeariaStore):# gera clientes exponencialmente distribuídos i =0whileTrue:yield env.timeout(random.expovariate(1/TEMPO_CHEGADAS)) i +=1# tem preferência por barbeiro? r = random.random()if r <=0.30: barbeiroEscolhido ='Barbeiro A'elif r <=0.40: barbeiroEscolhido ='Barbeiro B'else: barbeiroEscolhido ='Sem preferência'print("%5.1f Cliente %i chega.\t\t%s."%(env.now, i, barbeiroEscolhido))# inicia processo de atendimento env.process(atendimento(env, i, barbeiroEscolhido, barbeariaStore))defatendimento(env,cliente,barbeiroEscolhido,barbeariaStore):#ocupa um barbeiro específico e realiza o corteglobal filaAtual chegada = env.nowif barbeiroEscolhido !='Sem preferência':if barbeiroEscolhido notin barbeirosDict:# caso o barbeiro tenha faltado, desiste do atendimentoprint("%5.1f Cliente %i desiste.\t%s ausente."%(env.now, cliente, barbeiroEscolhido)) env.exit()if filaDict[barbeiroEscolhido]>3:# caso a fila seja maior do que 6, desiste do atendimentoprint("%5.1f Cliente %i desiste.\t%s com mais de 3 clientes em fila."%(env.now, cliente, barbeiroEscolhido)) env.exit()# cliente atual entra em fila e incrementa a fila do barbeiro favorito filaAtual +=1 filaDict[barbeiroEscolhido]= filaDict[barbeiroEscolhido]+1 barbeiro =yield barbeariaStore.get(lambdabarbeiro: barbeiro==barbeiroEscolhido) filaDict[barbeiroEscolhido]= filaDict[barbeiroEscolhido]-1else:# cliente sem preferência, verifica o tamanho total da filaif filaAtual >6:# caso a fila seja maior do que 6, desiste do atendimentoprint("%5.1f Cliente %i desiste.\tFila com mais de 6 clientes em fila."%(env.now, cliente)) env.exit()else:# cliente entra em fila e pega o primeiro barbeiro livre filaAtual +=1 barbeiro =yield barbeariaStore.get()# cliente já tem barbeiro, então sai da fila filaAtual -=1 espera = env.now - chegadaprint("%5.1f Cliente %i inicia.\t\t%s ocupado.\tTempo de fila: %2.1f"%(env.now, cliente, barbeiro, espera))# ocupa o recurso barbeirowith barbeirosDict[barbeiro].request()as req:yield req tempoCorte = random.normalvariate(*TEMPO_CORTE)yield env.timeout(tempoCorte)print("%5.1f Cliente %i termina.\t%s liberado."%(env.now, cliente, barbeiro))# devolve o barbeiro para o FilterStore barbeariaStore.put(barbeiro)random.seed(25)env = simpy.Environment()# cria 3 barbeiros diferentes e armazena em um dicionáriobarbeirosNomes = ['Barbeiro A','Barbeiro B','Barbeiro C']# falta de um barbeiroif random.random()<=0.05: barbeirosNomes.remove(random.choice((barbeirosNomes)))barbeirosList = [simpy.Resource(env, capacity=1)for i inrange(len(barbeirosNomes))]barbeirosDict =dict(zip(barbeirosNomes, barbeirosList))# dicionário para armazenar o número de clientes em fila de favoritos filaDict ={k:0for k in barbeirosNomes}# cria um FilterStore para armazenar os barbeirosbarbeariaStore = simpy.FilterStore(env, capacity=3)barbeariaStore.items = barbeirosNomes# inicia processo de chegadas de clientesenv.process(chegadaClientes(env, barbeariaStore))env.run(until =30)
Quando executado por apenas 30 minutos, o modelo anterior fornece:
13.1 Cliente 1 chega. Sem preferência.13.1 Cliente 1 inicia. Barbeiro A ocupado. Tempo de fila:0.014.3 Cliente 2 chega. Barbeiro A.26.6 Cliente 1 termina. Barbeiro A liberado.26.6 Cliente 2 inicia. Barbeiro A ocupado. Tempo de fila:12.329.6 Cliente 3 chega. Sem preferência.29.6 Cliente 3 inicia. Barbeiro B ocupado. Tempo de fila:0.0
Teste seus conhecimentos
Considere que a barbearia opera 6 horas por dia. Acrescente ao seu modelo às estatísticas de clientes atendidos, clientes que desistiram (e por qual razão), ocupação dos barbeiros e tempo médio de espera em fila por barbeiro.
Dada a presente demanda da barbearia, quantos barbeiros devem estar trabalhando, caso o proprietário pretenda que o tempo médio de espera em fila seja inferior a 15 minutos?
Refaça o exemplo do PriorityStore da seção anterior, armazenando não mais o nome do barbeiro, mas o seu respectivo resource.