#!/usr/bin/env python

# Introductory SimPy example to illustrate the modeling of "competing
# events" such as timeouts, especially using SimPy's cancel() method. 
# A network node sends but also sets a timeout period; if the node times
# out, it assumes the message it had sent was lost, and will send again.
# The time to get an acknowledgement for a message is exponentially
# distributed with mean 1.0, and the timeout period is also random, 
# exponentially distributed with mean 0.5.  (The latter is not common,
# but it allowed for an easy Markov check of the results.)  Immediately 
# after receiving an acknowledgement, the node sends out a new message.

# output should be about 0.67

# the main classes are:

#   Node, simulating the network node, with our instance being Nd
#   TimeOut, simulating a timeout timer, with our instance being TO
#   Acknowledge, simulating an acknowledgement, with our instance being ACK

# overview of program design:

#   Nd acts as the main "driver," with a loop that continually creates 
#   TimeOuts and Acknowledge objects, passivating itself until one of
#   those objects' events occurs; if for example the timeout occurs
#   before the acknowledge, the TO object will reactivate Nd and cancel
#   the ACK object's event, and vice versa

from __future__ import generators
from SimPy.Simulation import *
from SimPy.Monitor import *
from random import Random,expovariate,uniform

class Node(Process):
   def __init__(self):
      Process.__init__(self)  
      self.NMsgs = 0  # number of messages sent
      self.NTimeOuts = 0  # number of timeouts which have occurred
      self.ReactivatedCode = -1  # this will be 1 if timeout occurred, 2
                                 # ACK received  
   def Run(self):
      global TO
      global ACK
      while 1:
         self.NMsgs += 1
         # set up the timeout
         TO = TimeOut()
         activate(TO,TO.Run(),delay=0.0)
         # set up message send/ACK
         ACK = Acknowledge()
         activate(ACK,ACK.Run(),delay=0.0)
         yield passivate,self 
         if self.ReactivatedCode == 1:
            self.NTimeOuts += 1
         self.ReactivatedCode = -1

class TimeOut(Process):
   def __init__(self):
      Process.__init__(self)  
   def Run(self):
      global ACK
      yield hold,self,Rnd.expovariate(TORate)
      Nd.ReactivatedCode = 1  
      reactivate(Nd)
      self.cancel(ACK)
      del ACK

class Acknowledge(Process):
   def __init__(self):
      Process.__init__(self)  
   def Run(self):
      global TO
      yield hold,self,Rnd.expovariate(ACKRate)
      Nd.ReactivatedCode = 2  
      reactivate(Nd)
      self.cancel(TO)
      del TO

# "main" program starts here

# set rates for this model
ACKRate = 1/1.0
TORate = 1/0.5
Rnd = Random(12345)
initialize()  
Nd = Node();
activate(Nd,Nd.Run(),delay=0.0)
simulate(until=10000.0)
print "the percentage of timeouts was", float(Nd.NTimeOuts)/Nd.NMsgs

