Simpy Discrete Event Simulation: Customer/entity request never fulfilled in system with server blocking blocking when queue of next server is full - events

I have been struggling with coding this Discrete Event Simulation for a couple of days now. I got some answers of of this website, but still cannot run my simulation properly.
The problem that arises is: The first customer (thus every customer) does get through the queue of the first service, but can never actually enter the server for some reason. I hope some experts are able to pinpoint the problem quickly, as it is probably just a stupid mistake. The customer does go through the zero'th server and the queue for server 1.
I added #### just before and after the line where the mistake is.
Thanks in advance.
class Entity(object):
pass
def process0(env, entity, process_0_res, process_1_q):
print(f' {env.now} customer {entity.id} is in system')
with process_0_res.request() as res_req:
yield res_req
print(f'{env.now} customer {entity.id} is in queue 1')
yield process_1_q.put(entity)
def process1_broker(env, process_1_q, process_1_res):
while True:
# is resource available?
res_req = process_1_res.request()
yield res_req
# is customer available?
entity = yield process_1_q.get()
# save resource request to release later
entity.res_req = res_req
# start process
env.process(process1(env,entity,process_1_res, process_2_q))
def process1(env, entity, process_1_res, process_2_q):
with process_1_res.request() as res_req:
print(f'{env.now} customer {entity.id} should now request server 1')
#########
yield res_req
#########
print(f' {env.now} customer {entity.id} in process 1')
yield env.timeout(2)
yield process_2_q.put(entity)
def process2_broker(env, process_2_q, process_2_res):
while True:
res_req = process_2_res.request()
yield res_req
entity = yield process_2_q.get()
entity.res_req = res_req
env.process(process2(env,entity,process_2_res, process_3_q))
def process2(env, entity, process_2_res, process_3_q):
print(f' {env.now} customer {entity.id} in process 2')
with process_2_res.request() as res_req:
yield res_req
yield env.timeout(np.random.exponential(mu[1]))
yield process_3_q.put(entity)
def process3_broker(env, process_3_q, process_3_res):
while True:
res_req = process_3_res.request()
yield res_req
entity = yield process_3_q.get()
entity.res_req = res_req
env.process(process3(env,entity,process_3_res, process_4_q))
def process3(env, entity, process_3_res, process_4_q):
print(f' {env.now} customer {entity.id} in process 3')
with process_3_res.request() as res_req:
yield res_req
yield env.timeout(np.random.exponential(mu[2]))
yield process_4_q.put(entity)
def process4_broker(env, process_4_q, process_4_res):
while True:
res_req = process_4_res.request()
yield res_req
entity = yield process_3_q.get()
entity.res_req = res_req
env.process(process4(env,entity,process_4_res))
def process4(env, entity, process_4_res):
print(f' {env.now} customer {entity.id} in process 4')
with process_4_res.request() as res_req:
yield res_req
yield env.timeout(np.random.exponential(mu[3]))
yield process_4_res.release(entity.res_req)
print(f' {env.now} customer {entity.id} leaves system')
def gen_entities(env, process_0_res, process_1_q):
next_id = 1
while True:
yield env.timeout(np.random.exponential(labda))
entity = Entity()
entity.id = next_id
next_id += 1
env.process(process0(env, entity, process_0_res, process_1_q))
env = simpy.Environment()
process_0_res = simpy.Resource(env, capacity = 1)
process_1_res = simpy.Resource(env, capacity = 1)
process_2_res = simpy.Resource(env, capacity = 1)
process_3_res = simpy.Resource(env, capacity = 1)
process_4_res = simpy.Resource(env, capacity = 1)
process_1_q = simpy.Store(env, capacity = 5)
process_2_q = simpy.Store(env, capacity = 4)
process_3_q = simpy.Store(env, capacity = 3)
process_4_q = simpy.Store(env, capacity = 2)
env.process(gen_entities(env, process_0_res, process_1_q))
env.process(process1_broker(env, process_1_q, process_1_res))
env.process(process2_broker(env, process_2_q, process_2_res))
env.process(process3_broker(env, process_3_q, process_3_res))
env.process(process4_broker(env, process_4_q, process_4_res))
env.run(10)

In your code you are seizing resources twice, once in the broker, and then again in the process. so if you only have one resource, the second seize will never happen. also, you are only releasing one of the two resources, so you will run out of resources at some point.
Since the broker already seizes a resource and saves it in the entity, the process does not need to do a second seize. However, the process will still need to release the resource that was seized in the broker.
I think I fixed your code
import simpy
import random
import numpy as np
class Entity(object):
pass
def process0(env, entity, process_0_res, process_1_q):
print(f' {env.now} customer {entity.id} is in system')
with process_0_res.request() as res_req:
yield res_req
yield process_1_q.put(entity)
print(f'{env.now} customer {entity.id} is in queue 1')
def process1_broker(env, process_1_q, process_1_res):
while True:
# is resource available?
res_req = process_1_res.request()
yield res_req
# is customer available?
entity = yield process_1_q.get()
# save resource request to release later
entity.res_req = res_req
# start process
env.process(process1(env,entity,process_1_res, process_2_q))
def process1(env, entity, process_1_res, process_2_q):
# the resouce for process 1 has already been seized by the broker
print(f' {env.now} customer {entity.id} in process 1')
with entity.res_req:
#print(f'{env.now} customer {entity.id} should now request server 1')
#########
#yield res_req
#########
#print(f' {env.now} customer {entity.id} in process 1')
yield env.timeout(2)
yield process_2_q.put(entity)
def process2_broker(env, process_2_q, process_2_res):
while True:
res_req = process_2_res.request()
yield res_req
entity = yield process_2_q.get()
entity.res_req = res_req
env.process(process2(env,entity,process_2_res, process_3_q))
def process2(env, entity, process_2_res, process_3_q):
print(f' {env.now} customer {entity.id} in process 2')
with entity.res_req:
#yield res_req
yield env.timeout(np.random.exponential(2))
yield process_3_q.put(entity)
def process3_broker(env, process_3_q, process_3_res):
while True:
res_req = process_3_res.request()
yield res_req
entity = yield process_3_q.get()
entity.res_req = res_req
env.process(process3(env,entity,process_3_res, process_4_q))
def process3(env, entity, process_3_res, process_4_q):
print(f' {env.now} customer {entity.id} in process 3')
with entity.res_req:
#yield res_req
yield env.timeout(np.random.exponential(2))
yield process_4_q.put(entity)
def process4_broker(env, process_4_q, process_4_res):
while True:
res_req = process_4_res.request()
yield res_req
entity = yield process_4_q.get()
entity.res_req = res_req
env.process(process4(env,entity,process_4_res))
def process4(env, entity, process_4_res):
print(f' {env.now} customer {entity.id} in process 4')
with entity.res_req:
#yield res_req
yield env.timeout(np.random.exponential(2))
#yield process_4_res.release(entity.res_req)
print(f' {env.now} customer {entity.id} leaves system')
def gen_entities(env, process_0_res, process_1_q):
next_id = 1
while True:
yield env.timeout(np.random.exponential(2))
entity = Entity()
entity.id = next_id
next_id += 1
env.process(process0(env, entity, process_0_res, process_1_q))
env = simpy.Environment()
process_0_res = simpy.Resource(env, capacity = 1)
process_1_res = simpy.Resource(env, capacity = 1)
process_2_res = simpy.Resource(env, capacity = 1)
process_3_res = simpy.Resource(env, capacity = 1)
process_4_res = simpy.Resource(env, capacity = 1)
process_1_q = simpy.Store(env, capacity = 5)
process_2_q = simpy.Store(env, capacity = 4)
process_3_q = simpy.Store(env, capacity = 3)
process_4_q = simpy.Store(env, capacity = 2)
env.process(gen_entities(env, process_0_res, process_1_q))
env.process(process1_broker(env, process_1_q, process_1_res))
env.process(process2_broker(env, process_2_q, process_2_res))
env.process(process3_broker(env, process_3_q, process_3_res))
env.process(process4_broker(env, process_4_q, process_4_res))
env.run(100)

Related

Ruby publish/subscribe implementation issue

I'm experimenting with Observable Ruby class that should be capable to provide Publish/Subscribe pattern.
I wrote this code to test the behavior:
require "observer"
class Ticker ### Periodically fetch a stock price.
include Observable
attr_accessor :sleep_time
def initialize(symbol, sleep_time)
#symbol = symbol
#sleep_time = sleep_time
end
def run
last_price = nil
loop do
price = Price.fetch(#symbol)
print "#{#symbol}, Current price: #{price}\n"
if price != last_price
changed # notify observers
last_price = price
notify_observers(price)
end
sleep #sleep_time
end
end
end
class Price ### A mock class to fetch a stock price (60 - 140).
def self.fetch(symbol)
60 + rand(80)
end
end
class Notification
attr_accessor :name, :sleep_time
def initialize(ticker, name, sleep_time)
#name = name
#sleep_time = sleep_time
ticker.add_observer(self)
end
def update(price)
puts #name + ': ' + price.to_s
sleep #sleep_time
end
end
ticker = Ticker.new("MSFT", 0.5)
t1 = Thread.new { slow_notification = Notification.new(ticker, 'thread 1', 0) }
t2 = Thread.new { fast_notification = Notification.new(ticker, 'thread 2', 5) }
ticker.run
I was expecting that fast_notification sent the notification faster than slow_notification cause the sleep time for the first is one is 0 seconds and for the other one 5 seconds. In practice, they run at the same time (every 5 seconds).
In order to handle it in an async way, the thread creation should be within the main loop. I got the expected behavior with these changes:
require "observer"
class Ticker ### Periodically fetch a stock price.
include Observable
attr_accessor :sleep_time
def initialize(symbol, sleep_time)
#symbol = symbol
#sleep_time = sleep_time
end
def run
last_price = nil
loop do
price = Price.fetch(#symbol)
#print "#{#symbol}, Current price: #{price}\n"
if price != last_price
changed # notify observers
last_price = price
Thread.new { notify_observers(price, Time.now) }
end
sleep #sleep_time
end
end
end
class Price ### A mock class to fetch a stock price (60 - 140).
def self.fetch(symbol)
7600 + rand(800)
end
end
class Notification
attr_accessor :name, :sleep_time, :working, :mutex, :counter
def initialize(ticker, name, sleep_time)
#name = name
#sleep_time = sleep_time
#working = false
#mutex = Mutex.new
#counter = 0
ticker.add_observer(self)
end
def do_something(price)
puts #name + ': ' + price.to_s
sleep #sleep_time
end
def update(price, time)
#mutex.synchronize{
do_something(price)
}
end
end
ticker = Ticker.new("MSFT", 0.5)
Notification.new(ticker, 'Fast notification', 0)
Notification.new(ticker, 'Slow notification', 5)
ticker.run
I think you should flush the buffer. Try add STDOUT.flush after print.
def run
last_price = nil
loop do
price = Price.fetch(#symbol)
print "#{#symbol}, Current price: #{price}\n"
STDOUT.flush
if price != last_price
changed # notify observers
last_price = price
notify_observers(price)
end
sleep #sleep_time
end
end

Cannot get a method instance to function; How to use an Each Do method in ruby?

I have an specific method that I would like to call so that I can see the balances for 'accounts'. The method is;
def report_balances(accounts)
accounts.each do |account|
puts account.balance
end
end
I am not sure but I have either built the above method incorrectly or I am calling it incorrectly or maybe I have placed the method correctly in my code.
class BankAccount
attr_reader :balance
def initialize(balance)
#balance = balance
end
def deposit(amount)
#balance += amount if amount >= 0
end
def withdraw(amount)
#balance -= amount if #balance >= amount
end
end
class SavingsAccount < BankAccount
attr_reader :number_of_withdrawals
APY = 0.0017
def initialize(balance)
super(balance) # calls the parent method
#number_of_withdrawals = 0 # then continues here
end
def end_of_month_closeout
if #balance > 0
interest_gained = (#balance * APY) / 12
#balance += interest_gained
end
#number_of_withdrawals = 0
end
def report_balances(accounts)
accounts.each do |account|
puts account.balance
end
end
end
I would like to see the balances of the objects:
my_account = SavingsAccount.new(100)
and
account = BankAccount.new(2500)
by calling
'report_balances(accounts)'
How would this be accomplished?
Think of my_account = SavingsAccount.new(100) as creating a new account, but what you're asking is I want to see all the balances of a list of accounts. Since each account has a balance, you can do:
[my_account, other_account].each do |account|
puts account.balance
end
I'd recommend moving your report_balances method to a class method or out of that class all together but that's a topic for a different discussion.

Ruby - Object-oriented Store example

I am starting my studies with Ruby and OO and I have received a test to do about OO.
It is a store scenario where I have to create the classes, methods, calls ...
For now I have create the class Payment and Product classes (Physical, Membership, Digital)
class Payment
attr_reader :authorization_number, :amount, :invoice, :order, :payment_method, :paid_at, :shipping
def initialize(attributes = {})
#authorization_number, #amount = attributes.values_at(:authorization_number, :amount)
#invoice, #order = attributes.values_at(:invoice, :order)
#payment_method = attributes.values_at(:payment_method)
#shipping.extend Shipping
end
def pay(paid_at = Time.now)
#amount = order.total_amount
#authorization_number = Time.now.to_i
#invoice = Invoice.new(order.get_address, order.get_address, order)
#paid_at = paid_at
order.close(#paid_at)
extend
def paid?
!paid_at.nil?
end
def prepare_shipping(order)
???
end
end
class Product
attr_reader :name, :description, :type
def initialize(name, description, type)
#name = name
#description = description
#type = type
end
end
class Digital < Product
include DigitalProduct
def initialize(name, description, type)
super(name, description, type)
end
def matches?(query)
query=="digital"
end
end
class Physical < Product
def initialize(name, description, type)
super(name, description, type)
end
def matches?(query)
query=="physical"
end
end
class Membership < Product
attr_reader :membership_status
include DigitalProduct
def initialize(name, description, type)
super(name, description, type)
end
def matches?(query)
query=="membership"
end
def activate_membership()
#membership_status = true;
end
end
Here is my problem:
1.If the product is physical I have to generate a shipping label.
1.1 If its a book I have to notify that this product doesn't have taxes.
2.If the product is membership I have to activate the signature and notify via email the buyer.
If the product is digital I have to send a email for the buyer and give $10 discount in this product.
I am doing pretty good, but now I would like your opinion to know what is the best way following OO techniques (SOLID) to create the points above.
ps: I tried to create but my solution was really poor with many ifs.
I think there is a better way to do it with
I have other classes if you need more info tell me please.
Sorry about my poor english.
I have developed this solution, I think it is good ... maybe you can give me a feedback about it.
So to solve the problem about shipping and discount rules I created 2 modules Shipping and Discount
module Discount
def prepare_discount(order)
total_discount = 0
order.get_items.each do |item|
total_discount += item.product.discount
end
return total_discount
end
def discount
if matches?("physical")
return discount_for_physical_product
elsif matches?("digital")
return discount_for_digital_product
elsif matches?("membership")
return discount_for_membership_product
elsif matches?("default")
return discount_for_default_product
end
end
def discount_for_physical_product
price_discount_for_physical_product = 0
return price_discount_for_physical_product
end
def discount_for_digital_product
price_discount_for_digital_product = 10.00
return price_discount_for_digital_product
end
def discount_for_default_product
price_discount_for_digital_product = 10.00
return price_discount_for_digital_product
end
def discount_for_membership_product
price_discount_for_membership_product = 0
return price_discount_for_membership_product
end
end
module Shipping
def prepare_shipping(order)
order.get_items.each do |item|
item.product.shipping
end
end
def shipping
if matches?("physical")
shipping_for_physical_product
elsif matches?("digital")
shipping_for_digital_product
elsif matches?("membership")
shipping_for_membership_product
elsif matches?("default")
shipping_for_default_product
end
end
def shipping_for_physical_product
case #type
when :book
create_shipping_label
notify_buyer_product_without_taxes
else
create_shipping_label
end
end
def shipping_for_digital_product
notify_buyer_via_email
end
def shipping_for_membership_product
notify_buyer_via_email
activate_membership
end
def shipping_for_default_product
create_shipping_label
notify_buyer_via_email
end
def create_shipping_label
puts "Creating shipping label ..."
end
def notify_buyer_product_without_taxes
puts "Product exempt from taxes, as provided in the Constitution Art. 150, VI, d."
end
def notify_buyer_via_email
puts "Sending email ..."
end
end
This way Product and Order can extend both modules and implement the logic.

obj.method(argument)

I am looking at this code:
class Student
attr_accessor :first_name, :last_name, :age
def initialize(first, last, age)
#first_name = first
#last_name = last
#age = age
end
def birthday
#age += 1
end
end
class ViewStudent
def initialize(student)
#student = student
end
def do_something
puts "Student name: #{#student.first_name} #{#student.last_name}"
end
end
class UpdateStudent
def initialize(student)
#student = student
end
def do_something
puts "What is the student's first name?"
#student.first_name = gets.chomp
puts "What is the student's last name?"
#student.last_name = gets.chomp
puts "Updated student: #{#student.first_name} #{#student.last_name}"
end
end
choices = [ViewStudent, UpdateStudent]
student = Student.new("John", "Doe", 18)
puts "Select 1 to view student or 2 to update student."
selection = gets.chomp.to_i
obj = choices[selection - 1]
obj = obj.new(student)
obj.do_something
In the last five lines, I understand that selection = gets.chomp.to_i converts the selection options to integers, but how does that work in tandem with obj = choices[selection - 1]?
I'm also not sure what obj = obj.new(student) and obj.do_something do. It looks like a local variable is being set to create a new object with student as the argument. However, obj isn't a class or method to call on?
I can also gather that obj.do_something calls the methods defined for both ViewStudent and UpdateStudent given the selection.
I saw this, but it doesn't answer my question.
obj = choices[selection - 1] just select ViewStudent if 1 and UpdateStudent if 2 from your array by index (choices[0] or choices[1]).
Then you creating an instance of selected class (ViewStudent.new or UpdateStudent.new) and call do_something method on this instance, because this methos difined in both classes:
obj = choices[selection - 1] # obj is ViewStudent or UpdateStudent class
obj = obj.new(student) # obj is ViewStudent or UpdateStudent instance
obj.do_something # call `do something` method on instance

Modeling a cookie with object composition in Ruby

I'm a new Rubyist and am wondering how I can access the ingredients class from individual cookies? As we all know, cookies are made of different ingredients. How can I specify default ingredients for individual cookies without setting default values? Even if I had default values, how would I update those to reflect the most current "recipe"? Please and thanks!
#Cookie Factory
module CookieFactory
def self.create(args)
cookie_batch = []
args.each do |cookie|
cookie_batch << PeanutButter.new if cookie == "peanut butter"
cookie_batch << ChocholateChip.new if cookie == "chocolate chip"
cookie_batch << Sugar.new if cookie == "sugar"
end
return cookie_batch
end
end
#Classes/Subclasses
class Ingredients
attr_reader
def initialize(contents = {})
# contents = defaults.merge(contents)
#sugar = contents.fetch(:sugar, "1.5 cups")
#salt = contents.fetch(:salt, "1 teaspoon")
#gluten = contents.fetch(:gluten, "0")
#cinnamon = contents.fetch(:cinnamon, "0.5 teaspoon")
end
end
class Cookie
attr_reader :status, :ingredients
def initialize(ingredients = {})
#ingredients = ingredients
#status = :doughy
super()
end
def bake!
#status = :baked
end
end
class PeanutButter < Cookie
attr_reader :peanut_count
def initialize
#peanut_count = 100
super()
end
def defaults
{
:peanut_shells => 5
}
end
end
class Sugar < Cookie
attr_reader :sugar
def initialize
#sugar = "1_cup"
super()
end
end
class ChocholateChip < Cookie
attr_reader :choc_chip_count
def initialize
#choc_chip_count = 200
super()
end
end
You can use Hash#merge to acheive this behavior:
class PeanutButter < Cookie
attr_reader :peanut_count
def initialize(ingredients)
#peanut_count = 100
super(ingredients.merge(defaults))
end
def defaults
{
:peanut_shells => 5
}
end
end

Resources