#!/usr/bin/env python

# MachRep3Regen.py

# SimPy example:  Variation of Mach1.py, Mach2.py.  Two machines, but
# sometimes break down.  Up time is exponentially distributed with mean
# 1.0, and repair time is exponentially distributed with mean 0.5.  In
# this example,there is only one repairperson, and she is not summoned
# until both machines are down.  We find the proportion of time that
# both machines are up simultaneously (mathematically this can be shown
# to be 0.181818...), extending our point estimate for that proportion
# to an approximate 90% regenerative confidence interval.  We choose as our
# regeneration points the times at which we enter the "full capacity"
# state, i.e. both machines up (valid only if up time is exponentially
# distributed).

from SimPy.Simulation import *
from random import Random,expovariate
from math import *

class G:  # globals
   Rnd = Random(12345)
   RepairPerson = Resource(1)

class MachineClass(Process):
   MachineList = []  # list of all objects of this class
   UpRate = 1/1.0
   RepairRate = 1/0.5
   TotalUpTime = 0.0  # total up time for all machines
   NextID = 0  # next available ID number for MachineClass objects
   NUp = 0  # number of machines currently up
   CMon = Monitor() 
   DMon = Monitor()
   # these concern the regeneration cycles
   StartCycle = 0.0  # time the current cycle started
   BeginningOfCycle = True  # both machines are up now; once one machine
                            # goes down, they will never both be up
                            # again during this cycle
   BothUpThisCycle = None  # amount of time both machines up in this cycle
   def __init__(self):
      Process.__init__(self)  
      self.StartUpTime = None  # time the current up period started
      self.ID = MachineClass.NextID   # ID for this MachineClass object
      MachineClass.NextID += 1
      MachineClass.MachineList.append(self)
      MachineClass.NUp += 1  # start in up mode
   def Run(self):
      while 1:
         self.StartUpTime = now()  
         yield hold,self,G.Rnd.expovariate(MachineClass.UpRate)
         UpTime = now() - self.StartUpTime
         MachineClass.TotalUpTime += UpTime
         MachineClass.NUp -= 1
         if MachineClass.BeginningOfCycle:
            MachineClass.BothUpThisCycle = now() - MachineClass.StartCycle
            MachineClass.BeginningOfCycle = False
         # update number of up machines
         # if only one machine down, then wait for the other to go down
         if MachineClass.NUp == 1:
            yield passivate,self
         # here is the case in which we are the second machine down;
         # either (a) the other machine was waiting for this machine to 
         # go down, or (b) the other machine is in the process of being
         # repaired 
         elif G.RepairPerson.n == 1:
            reactivate(MachineClass.MachineList[1-self.ID])
         # now go to repair
         yield request,self,G.RepairPerson
         yield hold,self,G.Rnd.expovariate(MachineClass.RepairRate)
         MachineClass.NUp += 1
         if MachineClass.NUp == 2:
            MachineClass.CMon.observe(MachineClass.BothUpThisCycle)
            MachineClass.DMon.observe(now()-MachineClass.StartCycle)
            MachineClass.StartCycle = now()
            MachineClass.BeginningOfCycle = True
         yield release,self,G.RepairPerson

def Regen(CMon,DMon,Z):
   CBar = CMon.mean()
   DBar = DMon.mean()
   GammaHat = CBar/DBar
   CDBar = 0.0
   N = len(CMon)
   for i in range(N):
      CDBar += CMon[i][1] * DMon[i][1]
   CDBar /= N
   CDBar -= CBar * DBar
   S2 = CMon.var() + GammaHat**2 * DMon.var() - 2 * GammaHat * CDBar
   Radius = Z * sqrt(S2) / (DBar*sqrt(N)) 
   return GammaHat,Radius

def main():
   initialize()  
   for I in range(2):
      M = MachineClass()
      activate(M,M.Run())
   MaxSimtime = 1000.0
   simulate(until=MaxSimtime)
   # for a 90% CI, use 1.65
   (Center,Radius) = Regen(MachineClass.CMon,MachineClass.DMon,1.65)
   print 'center, radius of CI:', Center, Radius

if __name__ == '__main__':  main()
