I want to simulate a kind of communication process where the source constantly generates a message and send it to sink, while the message has to cover some distance before arrive at the destination. However, the distance may change over time, so the duration in the cable of each message is not the same. My first approach is to use env.timeout(t_i)
, where t_i
is a time of ith message in the cable. My code is looking like this,
import simpy
import random
class network:
def __init__(self) -> None:
self.env = simpy.Environment()
self.cable = simpy.Store(self.env)
def generateMessage(self):
i = 0
while True:
message = (f'message: {i}', f'travel time {0.1 + round(random.random(), 3)}', (round(random.random(), 3)))
i += 1
print(f'gen at: {self.env.now} {message}')
yield self.env.timeout(0.1)
self.cable.put(message)
def receieveMessage(self):
while True:
# wait for message to travel in the cable for 'sometime' before arrive
message = yield self.cable.get()
yield self.env.timeout(message[2]) #<- This is not the right approach
print(f'receieve at {self.env.now}, message: {message[:2]}')
def run(self):
self.env.process(self.generateMessage())
self.env.process(self.receieveMessage())
self.env.run(until=1)
q = network()
q.run()
Where the output is
gen at: 0 ('message: 0', 'travel time 0.22', 0.22)
gen at: 0.1 ('message: 1', 'travel time 0.71', 0.71)
gen at: 0.2 ('message: 2', 'travel time 0.377', 0.377)
gen at: 0.30000000000000004 ('message: 3', 'travel time 1.056', 1.056)
receieve at 0.32, message: ('message: 0', 'travel time 0.22')
gen at: 0.4 ('message: 4', 'travel time 0.138', 0.138)
gen at: 0.5 ('message: 5', 'travel time 0.45299999999999996', 0.45299999999999996)
gen at: 0.6 ('message: 6', 'travel time 1.024', 1.024)
gen at: 0.7 ('message: 7', 'travel time 0.695', 0.695)
gen at: 0.7999999999999999 ('message: 8', 'travel time 0.503', 0.503)
gen at: 0.8999999999999999 ('message: 9', 'travel time 0.702', 0.702)
gen at: 0.9999999999999999 ('message: 10', 'travel time 0.75', 0.75)
This is not what I want, because in this approach, each loop of receieveMessage
method wait for the travel_time
before next iteration instead of a message delay for travel_time
before get by receieveMessage
method.
The desired output should be something like this,
gen at: 0 ('message: 0', 'travel time 0.207')
gen at: 0.1 ('message: 1', 'travel time 0.735')
receieve at 0.207, message: ('message: 0', 'travel time 0.207')
gen at: 0.2 ('message: 2', 'travel time 0.498')
gen at: 0.3 ('message: 3', 'travel time 0.492')
gen at: 0.4 ('message: 4', 'travel time 0.864')
gen at: 0.5 ('message: 5', 'travel time 0.241')
gen at: 0.6 ('message: 6', 'travel time 0.505')
receieve at 0.698, message: ('message: 2', 'travel time 0.498')
gen at: 0.7 ('message: 7', 'travel time 0.76')
receieve at 0.741, message: ('message: 5', 'travel time 0.241')
receieve at 0.792, message: ('message: 3', 'travel time 0.492')
gen at: 0.8 ('message: 8', 'travel time 0.815')
receieve at 0.835, message: ('message: 1', 'travel time 0.735')
gen at: 0.9 ('message: 9', 'travel time 0.104')
gen at: 1 ('message: 10', 'travel time 0.524')
# Message of generated message which should not print out be cause the util arg.
receieve at 1.004, message: ('message: 9', 'travel time 0.104')
receieve at 1.105, message: ('message: 6', 'travel time 0.505')
receieve at 1.264, message: ('message: 4', 'travel time 0.864')
receieve at 1.46, message: ('message: 7', 'travel time 0.76')
receieve at 1.524, message: ('message: 10', 'travel time 0.524')
receieve at 1.615, message: ('message: 8', 'travel time 0.815')
How should I achieve this? I'm thinking of introducing another process for each message but that seems like it can only be an idea.
I think simpy is more useful if you can send more then one message at a time. I also think you are on the right track when you suggested creating another process.
I separated the generator from the send process so the messages can compete for a common resource (the channel) as a independent processes. Up the channels to see how messages send time stamps overlap
"""
quick sim to send messages over a network chanel
programmer: Michael R. Gibbs
"""
import simpy
import random
class Network():
def __init__(self, env, num_of_chanels):
"""
The num_of_chanels limits how many messages
can be "sending" at one time.
If all the chanels are busy, then new messages queue up
for next available chanel
"""
self.env = env
self.num_of_chanels = num_of_chanels
self.chanels = simpy.Resource(env, num_of_chanels)
def send_mess(self, mess):
"""
sim process for sending a message
All the send mess process queue up for a channel resource
Then with the channel 'sends' the messaage
The release the channel for the next senm mess process in the queue
mess is a tuple of (id, message text, send time)
"""
print(f'{self.env.now}: - message: {mess[0]} being queued')
with self.chanels.request() as req:
yield req
print(f'{self.env.now}: - message: {mess[0]} being sent')
yield self.env.timeout(mess[2])
print(f'{self.env.now}: - message: {mess[0]} has been sent {mess[1]}')
def gen_messages(env, network):
"""
Generates a series of messages with a random send time
"""
id = 1
while True:
# change the randint params to stress the queue more
yield env.timeout(random.randint(1,3))
# (id, mess text, send time)
mess = (id, 'mess ' + str(id), random.randint(1,4))
id += 1
# no yield here, just drop and go
env.process(network.send_mess(mess))
# create and start sim
env = simpy.Environment()
network = Network(env,1) # start with just one chanel
env.process(gen_messages(env, network))
env.run(50)