예제 #1
0
    def __init__(self, nseed=2147483647, heir=None):
        """
        Initiates the random stream using the input seed 'nseed' and Python's 
        __init__ constructor method. Unless...
        ...the input seed 'nseed' happens to be a list or tuple of numbers 
        in [0.0, 1.0], in which case this external feed will be used as the 
        basis of all random variate generation for the instance and will be 
        used in place of consecutively sampled numbers from Python's built-in 
        "random" method! 
        """

        if isinstance(nseed, int):
            assert is_posinteger(nseed), \
               "The seed (if not a feed) must be a positive integer in ABCRand!"
            rstream = Random(nseed)
            self._feed = False
            self.runif01 = rstream.random
            if heir != "InverseRandomStream":
                self.randrange = rstream.randrange
                self.randint = rstream.randint
                self.vonmisesvariate = rstream.vonmisesvariate
                # Random.paretovariate and Random.weibullvariate
                # are used by methods in GeneralRandomStream
                self._paretovariate = rstream.paretovariate
                self._weibullvariate = rstream.weibullvariate

        else:  # nseed is a list or tuple
            # Check to see beforehand that no numbers
            # from the feed is outside [0.0, 1.0]
            for x in nseed:
                assert 0.0 <= x <= 1.0, \
                    "number from feed is outside of [0.0, 1.0] in ABCRand!"
            self._feed = Stack(nseed)  # Creates a Stack object
            self.runif01 = self.__rfeed01
예제 #2
0
    def __init__(self, eventlist=[], timelist=[], sort=False):
        """
        Creates two new stacks: one for the events and one for the corresponding 
        time points. The events could for instance be described by strings. The 
        times are (of course) floating-point numbers. The two stacks can be 
        filled here but they have to be synchronized and in temporal order. 
        """

        assert len(timelist) == len(eventlist), \
                "input lists are of unequal length in EventScheduleStack!"

        # If sort:    # to be added later

        self.__eventstack = Stack(eventlist)
        self.__timestack = Stack(timelist)
예제 #3
0
    def __init__(self, eventlist=[], timelist=[], sort=False):
        """
        Creates two new stacks: one for the events and one for the corresponding 
        time points. The events could for instance be described by strings. The 
        times are (of course) floating-point numbers. The two stacks can be 
        filled here but they have to be synchronized and in temporal order. 
        """

        assert len(timelist) == len(eventlist), \
                "input lists are of unequal length in EventScheduleStack!"

        # If sort:    # to be added later

        self.__eventstack = Stack(eventlist)
        self.__timestack  = Stack(timelist)
예제 #4
0
    def __init__(self, nseed=2147483647, heir=None):
        """
        Initiates the random stream using the input seed 'nseed' and Python's 
        __init__ constructor method. Unless...
        ...the input seed 'nseed' happens to be a list or tuple of numbers 
        in [0.0, 1.0], in which case this external feed will be used as the 
        basis of all random variate generation for the instance and will be 
        used in place of consecutively sampled numbers from Python's built-in 
        "random" method! 
        """

        if isinstance(nseed, int):
            assert is_posinteger(nseed), \
               "The seed (if not a feed) must be a positive integer in ABCRand!"
            rstream      = Random(nseed)
            self._feed   = False
            self.runif01 = rstream.random
            if heir != "InverseRandomStream":
                self.randrange       = rstream.randrange
                self.randint         = rstream.randint
                self.vonmisesvariate = rstream.vonmisesvariate
                # Random.paretovariate and Random.weibullvariate
                # are used by methods in GeneralRandomStream
                self._paretovariate  = rstream.paretovariate
                self._weibullvariate = rstream.weibullvariate

        else:  # nseed is a list or tuple
            # Check to see beforehand that no numbers 
            # from the feed is outside [0.0, 1.0]
            for x in nseed:
                assert 0.0 <= x <= 1.0, \
                    "number from feed is outside of [0.0, 1.0] in ABCRand!"
            self._feed   = Stack(nseed)  # Creates a Stack object
            self.runif01 = self.__rfeed01
예제 #5
0
def flattened(matrix, stack=False):
    """
    OUTPUT WILL  N O T  BE ON Matrix OBJECT FORMAT!!!

    Places all elements in one single  l i s t,  row-by-row. 
    
    NB. flattened returns a simple Python list (or a Stack 
    if so desired), not a single-row 'Matrix' matrix! 
    """

    nrows, ncols = sized(matrix, 'flattened')

    sequence = []
    for k in range(0, nrows):
        sequence.extend(list(matrix[k]))

    if stack: sequence = Stack(sequence)

    return sequence
예제 #6
0
    def __init__(self, line, nserv):
        """
        line  is 'Line' or 'LineStack'
        nserv is the initial number of servers
        """

        # Attributes made available from the outside (not assignable, though):
        # --------------------------------------------------------------------
        
        self.__narriv    = 0     # Accumulated number of arrivers until present
        
        self.__ninline   = 0     # Present number waiting in line

        self.__nfreeserv = nserv # Present number of free servers

        self.__nbalked   = 0     # Accumulated number of balkers

        self.__nreneged  = 0     # Accumulated number of renegers  

        self.__nescaped  = 0     # = self.__nbalked + self.__nreneged


        # Attributes not available from the outside:
        # --------------------------------------------------------------------
        
        self.__nserv    = nserv # Initial number of free servers

        if   line == 'Line':       self.__line = Deque()
        elif line == 'LineStack':  self.__line = Stack()
        else: raise Error("'line' must be 'Line' or 'LineStack' in ABCLine!")

                                # dict containing the "reneging times" (time  
        self.__renegers = {}    # points of the future reneging events) with 
                                # the corresponding arrival times as keys

        self.__wtimes   = array('d', [])   
                          # 'd' array of waiting times for those not escaped

        self.__length   = {}    # dict containing the line length history with 
                                # the corresponding time spans as keys

        self.__prevctl  = 0.0   # The previous clock time when the line length 
                                # was last changed

                                # dict containing the history of the number of 
        self.__systemh  = {}    # customers in the system with the corresponding
                                # time spans as keys

        self.__prevcts  = 0.0   # The previous clock time when the number of 
                                # customers in system was last changed

        self.__length   = {}    # dict containing the line length history with 
                                # the corresponding time spans as keys

        self.__prevctl  = 0.0   # The previous clock time when the line length 
                                # was last changed

                                # dict containing the history of the number of 
        self.__idleh    = {}    # of free/idle servers in the system with the 
                                # corresponding time spans as keys

        self.__prevcti  = 0.0   # The previous clock time when the number of 
예제 #7
0
class ABCLine(metaclass=ABCMeta):
    """
    This class contains everything that is common to the Line and LineStack 
    classes. Since this is also an abstract base class, it cannot be used in a 
    standalone fashion. Its methods and attributes can only be reached through 
    its subclasses Line and LineStack, which inherit from this class.
    """
# ------------------------------------------------------------------------------

    @abstractmethod
    def __init__(self, line, nserv):
        """
        line  is 'Line' or 'LineStack'
        nserv is the initial number of servers
        """

        # Attributes made available from the outside (not assignable, though):
        # --------------------------------------------------------------------
        
        self.__narriv    = 0     # Accumulated number of arrivers until present
        
        self.__ninline   = 0     # Present number waiting in line

        self.__nfreeserv = nserv # Present number of free servers

        self.__nbalked   = 0     # Accumulated number of balkers

        self.__nreneged  = 0     # Accumulated number of renegers  

        self.__nescaped  = 0     # = self.__nbalked + self.__nreneged


        # Attributes not available from the outside:
        # --------------------------------------------------------------------
        
        self.__nserv    = nserv # Initial number of free servers

        if   line == 'Line':       self.__line = Deque()
        elif line == 'LineStack':  self.__line = Stack()
        else: raise Error("'line' must be 'Line' or 'LineStack' in ABCLine!")

                                # dict containing the "reneging times" (time  
        self.__renegers = {}    # points of the future reneging events) with 
                                # the corresponding arrival times as keys

        self.__wtimes   = array('d', [])   
                          # 'd' array of waiting times for those not escaped

        self.__length   = {}    # dict containing the line length history with 
                                # the corresponding time spans as keys

        self.__prevctl  = 0.0   # The previous clock time when the line length 
                                # was last changed

                                # dict containing the history of the number of 
        self.__systemh  = {}    # customers in the system with the corresponding
                                # time spans as keys

        self.__prevcts  = 0.0   # The previous clock time when the number of 
                                # customers in system was last changed

        self.__length   = {}    # dict containing the line length history with 
                                # the corresponding time spans as keys

        self.__prevctl  = 0.0   # The previous clock time when the line length 
                                # was last changed

                                # dict containing the history of the number of 
        self.__idleh    = {}    # of free/idle servers in the system with the 
                                # corresponding time spans as keys

        self.__prevcti  = 0.0   # The previous clock time when the number of 
                                # free/idle servers was last changed

    # end of __init__

# ------------------------------------------------------------------------------

    def __getattr__(self, attr_name):
        """
        This method overrides the built-in __getattr__ and makes the values
        of the internal attributes externally available.
        """

        if   attr_name == "ninline":    return self.__ninline
        elif attr_name == "nfreeserv":  return self.__nfreeserv
        elif attr_name == "narriv":     return self.__narriv
        elif attr_name == "nbalked":    return self.__nbalked
        elif attr_name == "renegers":   return self.__renegers
        elif attr_name == "nreneged":   return self.__nreneged
        elif attr_name == "nescaped":   return self.__nescaped

    # end of __getattr__

# ------------------------------------------------------------------------------

    def __setattr__(self, attr_name, value):
        """
        This method overrides the built-in __setattr__ and makes the values of 
        the internal attributes externally available more difficult to screw 
        up from the outside.
        """

        if attr_name == "ninline"   \
        or attr_name == "nfreeserv" \
        or attr_name == "narriv"    \
        or attr_name == "nbalked"   \
        or attr_name == "renegers"  \
        or attr_name == "nreneged"  \
        or attr_name == "nescaped"  :
            errtxt1 = ": Can't change value/length of attribute"
            errtxt2 = " from the outside!"
            raise Error(attr_name + errtxt1 + errtxt2)

        else:
            self.__dict__[attr_name] = value

    # end of __setattr__

# ------------------------------------------------------------------------------

    def place_last_in_line(self, times):
        """
        Add one or several arrival times at the back end of the line. 
        The length of the expanded line is returned. 

        NB The elements in an iterable will be placed so that the last 
        will be the last in the line etc.

        Arguments:
        ----------
        times     single time or tuple/list of times

        Outputs:
        ----------
        The number presently in the expanded line
        """

        try:       # First try the hunch that the argument is a sequence:
            self.__narriv += len(times)
            for tim in times:
                self.__length[tim-self.__prevctl]  = len(self.__line)
                self.__systemh[tim-self.__prevcts] = len(self.__line) + \
                                                 self.__nserv - self.__nfreeserv
                self.__prevctl = tim
                self.__prevcts = tim
            
        except TypeError:   # ..otherwise the arg is a single number
            self.__narriv += 1
            self.__length[times-self.__prevctl]  = len(self.__line)
            self.__systemh[times-self.__prevcts] = len(self.__line) + \
                                               self.__nserv - self.__nfreeserv
            self.__prevctl = times
            self.__prevcts = times

        self.__line.push(times)
            
        self.__ninline = len(self.__line)

        return self.__ninline

    # end of place_last_in_line

# ------------------------------------------------------------------------------

    def place_first_in_line(self, times):
        """
        Add one or several arrival times at the front end of the line. 
        The length of the expanded line is returned. 

        NB The elements in an iterable will be placed so that the first 
        will be the first in the line etc.

        Arguments:
        ----------
        times     single time or tuple/list of times

        Outputs:
        ----------
        The length of the expanded line 
        """

        try:          # First try the hunch that the argument is a sequence:
            self.__narriv += len(times)
            for tim in times:
                self.__length[tim-self.__prevctl]  = len(self.__line)
                self.__systemh[tim-self.__prevcts] = len(self.__line) + \
                                                 self.__nserv - self.__nfreeserv
                self.__prevctl = tim
                self.__prevcts = tim

        except TypeError:   # ..otherwise the arg is a single number
            self.__narriv += 1
            self.__length[times-self.__prevctl]  = len(self.__line)
            self.__systemh[times-self.__prevctl] = len(self.__line) + \
                                               self.__nserv - self.__nfreeserv
            self.__prevctl = times
            self.__prevcts = times

        self.__line.unshift(times)
            
        self.__ninline = len(self.__line)

        return self.__ninline

    # end of place_first_in_line

# ------------------------------------------------------------------------------

    def call_next_in_line(self, calltime):
        """
        Fetch the first arrival time at the front end of the line,
        remove it from the line, and make one server busy.
        
        Outputs:
        --------
        The arrival time at the front end of the line 
        """

        self.__length[calltime-self.__prevctl]  = len(self.__line)
        self.__idleh[calltime-self.__prevcti]   = self.nfreeserv
        self.__systemh[calltime-self.__prevcts] = len(self.__line) + \
                                              self.__nserv - self.__nfreeserv
        self.__prevctl    = calltime
        self.__prevcti    = calltime
        self.__prevcts    = calltime

        arrivaltime       = self.__line.shift()

        self.__nfreeserv -= 1
        if self.__nfreeserv < 0:
            raise Error("Number of servers are negative in call_next_in_line!")

        self.__wtimes.append(calltime-arrivaltime)

        self.__ninline    = len(self.__line)

        return arrivaltime

    # end of call_next_in_line

# ------------------------------------------------------------------------------

    def remove_last_in_line(self, tim):
        """
        Fetch the last arrival time at the back end of the line and remove 
        it from the line. To be used - for instance - when a customer that 
        already has been placed in line balks (even balkers must first be 
        placed in line - before balking!).

        Outputs:
        --------
        The arrival time at the back end of the line 
        """

        self.__length[tim-self.__prevctl]  = len(self.__line)
        self.__systemh[tim-self.__prevcts] = len(self.__line) + \
                                         self.__nserv - self.__nfreeserv
        self.__prevctl = tim
        self.__prevcts = tim

        lasttime       = self.__line.pop()

        self.__ninline = len(self.__line)

        return lasttime

    # end of remove_last_in_line

# ------------------------------------------------------------------------------

    def server_freed_up(self, tim):
        """
        Adds '1' to the present number of free servers - 
        to be used when a customer has been served.
        May also be used to change the total number of servers 
        during the course of a simulation.
        """

        self.__idleh[tim-self.__prevcti]   = self.nfreeserv
        self.__systemh[tim-self.__prevcts] = len(self.__line) + \
                                         self.__nserv - self.__nfreeserv
        self.__nfreeserv += 1
        self.__prevcti = tim
        self.__prevcts = tim

    # end of server_freed_up

# ------------------------------------------------------------------------------

    def balker(self, tim):
        """
        Removes the arrival just placed last in line. Returns the arrival 
        time of the balker.
        
        NB. Balkers must first be placed in line - before balking!
        Outputs:
        --------
        The arrival time of the balker 
        """

        self.__nbalked  += 1
        self.__nescaped += 1
        
        return self.remove_last_in_line(tim)

    # end of balker

# ------------------------------------------------------------------------------

    def reneger(self, arentime):
        """
        To be used when a "renege" type event is picked up by get_next_event 
        and removes the corresponding arrival time (arentime) in the line of 
        arrival times GIVEN that it has not been removed already from calling  
        call_next_in_line (the existence of the corresponding arrival time in 
        the line is checked first). 
        """

        arrivaltime = self.__renegers[arentime]
        del self.__renegers[arentime]

        if arrivaltime in self.__line:
            self.__length[arentime-self.__prevctl]  = len(self.__line)
            self.__systemh[arentime-self.__prevcts] = len(self.__line) + \
                                                  self.__nserv - self.__nfreeserv
            self.__prevctl   = arentime
            self.__prevcts   = arentime
            self.__line.remove(arrivaltime)
            self.__ninline   = len(self.__line)
            self.__nreneged += 1
            self.__nescaped += 1
            return True 
        else:
            return False

    # end of reneger

# ------------------------------------------------------------------------------

    def waiting_times_all(self):
        """
        Returns an unsorted 'd' array containing the waiting times 
        for all served. 
        """

        return self.__wtimes

    # end of waiting_times_all

# ------------------------------------------------------------------------------

    def waiting_times_linedup(self):
        """
        Returns an unsorted 'd' array containing the waiting times 
        only for those who had to wait in line before being served. 
        """

        wtimesshort = array('d', [])
        for wt in self.__wtimes:
            if wt != 0.0: wtimesshort.append(wt)

        return wtimesshort

    # end of waiting_times_linedup

# ------------------------------------------------------------------------------

    def line_stats(self):
        """
        Returns a dict containing line length statistics with the line lengths 
        as keys and the corresponding times as values.
        """

        # This method turns the self.__length dict around: the statistics are 
        # collected with the line length as keys and the corresponding times 
        # as values (there will be fewer elements in the returned dict than in 
        # self.__length, of course...)

        statdict = {}
        for keytime in self.__length:
            try:
                statdict[self.__length[keytime]] += keytime
            except KeyError:
                statdict[self.__length[keytime]]  = keytime

        return statdict

    # end of line_stats

# ------------------------------------------------------------------------------

    def idle_stats(self):
        """
        Returns a dict containing server statistics with the number of idle 
        servers as keys and the corresponding times as values.
        """

        # This method turns the self.__idleh dict around: the statistics are 
        # collected with the number of free/idle servers in the system as keys 
        # and the corresponding times as values (there will be fewer elements 
        # in the returned dict than in self.__idleh, of course...)

        statdict = {}
        for keytime in self.__idleh:
            try:
                statdict[self.__idleh[keytime]] += keytime
            except KeyError:
                statdict[self.__idleh[keytime]]  = keytime

        return statdict

    # end of idle_stats

# ------------------------------------------------------------------------------

    def system_stats(self):
        """
        Returns a dict containing statistics for the total number of customers 
        in the system as keys and the corresponding times as values.
        """

        # This method turns the self.__systemh dict around: the statstics are 
        # collected with the number of customers in the system as keys and the 
        # corresponding times as values (there will be fewer elements in the 
        # returned dict than in self.__systemh, of course...)

        statdict = {}
        for keytime in self.__systemh:
            try:
                statdict[self.__systemh[keytime]] += keytime
            except KeyError:
                statdict[self.__systemh[keytime]]  = keytime

        return statdict

    # end of system_stats

# ------------------------------------------------------------------------------

# end of ABCLine

# ------------------------------------------------------------------------------
예제 #8
0
class ABCRand(metaclass=ABCMeta):
    """
    This class contains everything that is common to the GeneralRandomStream 
    and InverseRandomStream classes. Since this is also an abstract base 
    class, it cannot be used in a standalone fashion. Its methods and 
    attributes can only be reached through its subclasses GeneralRandomStream 
    and InverseRandomStream, which inherit from this class.

    ABCRand imports (and uses) some of the methods from Python's built-in 
    Random class including the "Mersenne Twister". This makes the Mersenne 
    Twister the basic rng of ABCRand and its heirs. All methods in ABCRand 
    that are not taken from Random are inverse-based, but the methods from 
    Random are generally not inverse-based. It may be noted that the Mersenne 
    Twister is a very reputable random number generator having a period of 
    2**19937-1.

    The following methods from Python's own Random class are inheritable from 
    ABCRand: randrange, randint, choice, shuffle, sample, vonmisesvariate, 
    paretovariate and weibullvariate.

    All the methods in ABCRand are inherited by GeneralRandomStream including 
    the ones imported from Random. The methods added by GeneralRandomStream do 
    NOT return the inverse of the [0.0, 1.0] random numbers from the basic rng.
    
    InverseRandomStream inherits the methods in ABCRand with the EXCEPTION 
    of the methods from Random (the Mersenne Twister is still there, though), 
    making all the methods in InverseRandomStream inverse-based, including 
    the methods added in the latter.
    
    The docstring documentation of Random, GeneralRandomStream and 
    InverseRandomStream must always be consulted before using the methods 
    inherited from ABCRand!

    NB  Some methods may return float('inf') or float('-inf') !
    """
# ------------------------------------------------------------------------------

    @abstractmethod
    def __init__(self, nseed=2147483647, heir=None):
        """
        Initiates the random stream using the input seed 'nseed' and Python's 
        __init__ constructor method. Unless...
        ...the input seed 'nseed' happens to be a list or tuple of numbers 
        in [0.0, 1.0], in which case this external feed will be used as the 
        basis of all random variate generation for the instance and will be 
        used in place of consecutively sampled numbers from Python's built-in 
        "random" method! 
        """

        if isinstance(nseed, int):
            assert is_posinteger(nseed), \
               "The seed (if not a feed) must be a positive integer in ABCRand!"
            rstream      = Random(nseed)
            self._feed   = False
            self.runif01 = rstream.random
            if heir != "InverseRandomStream":
                self.randrange       = rstream.randrange
                self.randint         = rstream.randint
                self.vonmisesvariate = rstream.vonmisesvariate
                # Random.paretovariate and Random.weibullvariate
                # are used by methods in GeneralRandomStream
                self._paretovariate  = rstream.paretovariate
                self._weibullvariate = rstream.weibullvariate

        else:  # nseed is a list or tuple
            # Check to see beforehand that no numbers 
            # from the feed is outside [0.0, 1.0]
            for x in nseed:
                assert 0.0 <= x <= 1.0, \
                    "number from feed is outside of [0.0, 1.0] in ABCRand!"
            self._feed   = Stack(nseed)  # Creates a Stack object
            self.runif01 = self.__rfeed01

    # end of __init__

# ------------------------------------------------------------------------------

    def __rfeed01(self):
        """
        Will be used as the "getter" of numbers in [0.0, 1.0] 
        when the input to the class is a feed rather than a 
        positive integer seed!
        """
                                    # Removes one number at a time 
        return self._feed.shift()   # from the stack

    # end of __rfeed01

# ------------------------------------------------------------------------------

    def runif_int0N(self, number):
        """
        Generator of uniformly distributed integers in [0, number) (also the 
        basis of some other procedures for generating random variates). 
        Numbers returned are 0 through number-1. NB!!!!!!!
        """

        assert is_posinteger(number)

        return int(number*self.runif01())

    # end of runif_int0N

# ------------------------------------------------------------------------------

    def rsign(self):
        """
        Returns -1.0 or 1.0 with probability 0.5 for each. 
        """

        x = self.runif01()
        if x <= 0.5: return -1.0
        else:        return  1.0

    # end of rsign

# ------------------------------------------------------------------------------

    def runifab(self, left, right): 
        """
        Generator of uniformly distributed floats between 'left' and 'right'. 
        """

        assert right >= left, "support span must not be negative in runifab!"

        x  =  left + (right-left)*self.runif01()

        x  =  kept_within(left, x, right)

        return x

    # end of runifab

# ------------------------------------------------------------------------------

    def rchistogram(self, values, qumul):
        """
        Generates random variates from an input CUMULATIVE histogram.
        'values' is a list/tuple with FLOATS in ascending order - A MUST! 
        These values represent bin end points and must be one more than 
        the number of cumulative frequencies, and where...
        ...'qumul' are the corresponding CUMULATIVE FREQUENCIES such that 
        qumul[k] = P(x<=values[k+1]).
        
        The cumulative frequencies must of course obey qumul[k+1] >= qumul[k],
        otherwise an exception will be raised!
        
        The values of the random variate are assumed to be uniformly 
        distributed within each bin.
        """

        p = self.runif01()

        x = ichistogram(p, values, qumul)

        return x

    # end of rchistogram

# ------------------------------------------------------------------------------

    def rchistogram_int(self, values, qumul):
        """
        Generates random variates from an input CUMULATIVE histogram.
        'values' is a list/tuple with INTEGERS in ascending order - A MUST! 
        These values represent bin end points and must be one more than 
        the number of cumulative frequencies, and where...
        ...'qumul' are the corresponding CUMULATIVE FREQUENCIES such that 
        qumul[k] = P(x<=values[k+1]).

        NB The first element of the values list is will never be returned!
        The first integer to be returned is values[0] + 1   !!!!

        The cumulative frequencies must of course obey qumul[k+1] >= qumul[k],
        otherwise an exception will be raised!
        
        The integer values of the random variate are assumed to be uniformly 
        distributed within each bin.
        """

        p = self.runif01()

        x = ichistogram_int(p, values, qumul)

        return x

    # end of rchistogram_int

# ------------------------------------------------------------------------------

    def rtriang(self, left, mode, right):
        """
        Generator of triangularly distributed random numbers on [left, right] 
        with the peak of the pdf at mode. 
        """

        assert left <= mode and mode <= right, \
                                  "mode out of support range in rtriang!"

        p  =  self.runif01()

        span    =  right - left
        spanlo  =  mode  - left
        spanhi  =  right - mode
        #height  =  2.0 / span
        #surf1   =  0.5 * spanlo * height
        #surf1   =  spanlo/float(span)

        #if p <= surf1:
        if p <= spanlo/float(span):
            #x  =  sqrt(2.0*spanlo*p/height)
            x  =  sqrt(spanlo*span*p)
        else:
            #x  =  span - sqrt(2.0*spanhi*(1.0-p)/height)
            x  =  span - sqrt(spanhi*span*(1.0-p))
        x += left

        x  = kept_within(left, x, right)

        return x

    # end of rtriang

# ------------------------------------------------------------------------------

    def rtri_unif_tri(self, a, b, c, d):
        """
        Triangular-uniform-triangular distribution with support on [a, d] and 
        with breakpoints in b and c
                      ------
        pdf:        /        \
                   /           \
            ------               -------                                         
        """

        # Input check -----------------------
        assert a <= b and b <= c and c <= d, \
                                "break points scrambled in rtri_unif_tri!"
        # -----------------------------------


        if d == a: return a


        dcba   =  d + c - b - a
        h      =  2.0 / dcba
        first  =  0.5 * h * (b-a)
        p      =  self.runif01()
        poh    =  0.5 * p * dcba

        if p <= first:
            x  =  sqrt(2.0*(b-a)*poh) + a
        elif first < p <= first + h*(c-b):
            x  =  (c-b)*(poh-0.5*(b-a)) + b
        else:
            x  =  d - sqrt((d-c)*dcba*(1.0-p))

        x  = kept_within(a, x, d)

        return x

    # end of rtri_unif_tri

# ------------------------------------------------------------------------------

    def rkumaraswamy(self, a, b, x1, x2):
        """
        The Kumaraswamy distribution f = a*b*x**(a-1) * (1-x**a)**(b-1)
                                     F = 1 - (1-x**a)**b
                                     a, b >= 0; 0 <= x <= 1
        The Kumaraswamy is very similar to the beta distribution !!!
        
        x2 >= x1 !!!! 
        """

        assert a  >  0.0, "shape parameters in rkumaraswamy must be positive!"
        assert b  >  0.0, "shape parameters in rkumaraswamy must be positive!"
        assert x2 >= x1,  "support range in rkumaraswamy must not be negative!"

        y  =  (1.0 - (1.0-self.runif01())**(1.0/b)) ** (1.0/a)

        x  =  y*(x2-x1) + x1

        x  =  kept_within(x1, x, x2)

        return x

    # end of rkumaraswamy

# ------------------------------------------------------------------------------

    def rsinus(self, left, right):
        """
        The "sinus distribution". 
        """

        assert right >= left, "support range must not be negative in rsinus!"

        #x  =  left  +  (right-left) * acos(1.0-2.0*self.runif01()) / PI
        x  =  left  +  (right-left) * PIINV*acos(1.0-2.0*self.runif01())

        x  =  kept_within(left, x, right)

        return x

    # end of rsinus

# ------------------------------------------------------------------------------
 
    def rgeometric(self, phi):
        """
        The geometric distribution with p(K=k) = phi * (1-phi)**(k-1)  and 
        P(K>=k) = sum phi * (1-phi)**k = 1 - q**k, where q = 1 - phi and  
        0 < phi <= 1 is the success frequency or "Bernoulli probability" 
        and K >= 1 is the number of  trials to the first success in a series 
        of Bernoulli trials. It is easy to prove that P(k) = 1 - (1-phi)**k: 
        let q = 1 - phi. p(k) = (1-q) * q**(k-1) = q**(k-1) - q**k. Then P(1) = 
        p(1) = 1 - q. P(2) = p(1) + p(2) = 1 - q + q - q**2 = 1 - q**2. 
        Induction can be used to show that P(k) = 1 - q**k = 1 - (1-phi)**k 
        """

        assert 0.0 <= phi and phi <= 1.0, \
                      "success frequency must be in [0.0, 1.0] in rgeometric!"

        if phi == 1.0: return 1   # Obvious...

        p  = self.runif01()

        q  =  1.0 - phi

        if phi < 0.25:            # Use the direct inversion formula
            lnq   = - safelog(q)
            ln1mp = - safelog(1.0 - p)
            kg    =  1 + int(ln1mp/lnq)

        else:             # Looking for the passing point is more efficient for 
            kg = 1        # phi >= 0.25 (it's still inversion)
            u  = p
            a  = phi
            while True:
                u = u - a
                if u > 0.0:
                    kg += 1
                    a  *= q
                else:
                    break

        return kg

    # end of rgeometric

# ------------------------------------------------------------------------------

    def remp_exp(self, values, npexp=0, ordered=False, \
                                        xmax=float('inf'), pmax=1.0): 
        """
        The mixed expirical/exponential distribution from Bratley, Fox and 
        Schrage. A polygon (piecewise linearly interpolated cdf) is used 
        together with a (shifted) exponential for the tail. The procedure 
        is designed so as to preserve the mean of the input sample.
        
        The input is a set of observed points (vector) and an integer 
        representing the npexp largest points that will be used to formulate 
        the exponential tail.
        
        NB it is assumed that x is in [0, inf) (with the usual cutoff 
        provisions)  !!!!!
        
        The function may also be used for a piecewise linear cdf without the 
        exponential tail - corrections are made to preserve the mean in this 
        case as well !!! 
        """

        assert xmax >= 0.0, "xmax must be a non-negative float in remp_exp!"
        self._checkpmax(pmax, 'remp_exp')

        pmx = pmax
        #if xmax < float('inf'):
            #pmx = min(pmax, cemp_exp(values, npexp, ordered, xmax))

        p  =  pmx * self.runif01()
        x  =  iemp_exp(p, values, npexp, ordered)

        return x

    # end of remp_exp

# ------------------------------------------------------------------------------

    def rexpo_gen(self, a, b, c, xmin=float('-inf'), xmax=float('inf'), \
                                 pmin=0.0, pmax=1.0):   
        """
        The generalized continuous double-sided exponential 
        distribution (x in R):
        x <= c: f  =  [a*b/(a+b)] * exp(+a*[x-c])
                F  =   [b/(a+b)]  * exp(+a*[x-c])
        x >= c: f  =  [a*b/(a+b)] * exp(-b*[x-c])
                F  =  1 - [a/(a+b)]*exp(-b*[x-c])
        a > 0, b > 0
        
        NB The symmetrical double-sided exponential sits in rlaplace!
        """

        self._checkminmax(xmin, xmax, pmin, pmax, 'rexpo_gen')

        pmn = pmin
        pmx = pmax
        if xmin > float('-inf'): pmn = max(pmin, cexpo_gen(a, b, c, xmin))
        if xmax < float('inf'):  pmx = min(pmax, cexpo_gen(a, b, c, xmax))

        p  =  pmn + (pmx-pmn)*self.runif01()
        x  =  iexpo_gen(p, a, b, c)

        return x

    # end of rexpo_gen

# ------------------------------------------------------------------------------

    def rlaplace(self, loc, scale, xmin=float('-inf'), xmax=float('inf'), \
                                   pmin=0.0, pmax=1.0):
        """
        The Laplace aka the symmetrical double-sided exponential distribution 
        f = ((1/2)/s)) * exp(-abs([x-l]/s))
        F = (1/2)*exp([x-l]/s)  {x <= 0},  F = 1 - (1/2)*exp(-[x-l]/s)  {x >= 0}
        s >= 0  
        """

        self._checkminmax(xmin, xmax, pmin, pmax, 'rlaplace')

        pmn = pmin
        pmx = pmax
        if xmin > float('-inf'): pmn = max(pmin, claplace(shift, scale, xmin))
        if xmax < float('inf'):  pmx = min(pmax, claplace(shift, scale, xmax))

        p  =  pmn + (pmx-pmn)*self.runif01()
        x  =  ilaplace(p, loc, scale)

        return x

    # end of rlaplace

# ------------------------------------------------------------------------------

    def rtukeylambda_gen(self, lam1, lam2, lam3, lam4, pmin=0.0, pmax=1.0):
        """
        The Friemer-Mudholkar-Kollia-Lin generalized Tukey lambda distribution.
        lam1 is a location parameter and lam2 a scale parameter. lam3 and lam4
        are associated with the shape of the distribution. 
        """

        assert lam2 > 0.0, \
          "shape parameter lam2 must be a positive float in rtukeylambda_gen!"
        assert 0.0 <= pmin < pmax, \
                             "pmin must be in [0.0, pmax) in rtukeylambda_gen!"
        assert pmin < pmax <= 1.0, \
                             "pmax must be in (pmin, 1.0] in rtukeylambda_gen!"        

        p  =  pmin + (pmax-pmin)*self.runif01()

        if lam3 == 0.0:
            q3 = safelog(p)
        else:
            q3 = (p**lam3-1.0) / lam3

        if lam4 == 0.0:
            q4 = safelog(1.0-p)
        else:
            q4 = ((1.0-p)**lam4 - 1.0) / lam4

        x  =  lam1 + (q3-q4)/lam2

        return x

    # end of rtukeylambda_gen

# ------------------------------------------------------------------------------

    def rcauchy(self, location, scale, xmin=float('-inf'), xmax=float('inf'), \
                                       pmin=0.0, pmax=1.0):
        """
        Generator of random variates from the Cauchy distribution: 
        f = 1 / [s*pi*(1 + [(x-l)/s]**2)]
        F = (1/pi)*arctan((x-l)/s) + 1/2
        (also known as the Lorentzian or Lorentz distribution)
        
        scale must be >= 0 
        """

        self._checkminmax(xmin, xmax, pmin, pmax, 'rcauchy')

        pmn = pmin
        pmx = pmax
        if xmin > float('-inf'):
            pmn = max(pmin, ccauchy(location, scale, xmin))
        if xmax < float('inf'):
            pmx = min(pmax, ccauchy(location, scale, xmax))
        
        p  =  pmn + (pmx-pmn)*self.runif01()
        x  =  icauchy(p, location, scale)

        return x

    # end of rcauchy

# ------------------------------------------------------------------------------

    def rextreme_I(self, type, mu, scale, \
                                        xmin=float('-inf'), xmax=float('inf'), \
                                        pmin=0.0, pmax=1.0):
        """
        Extreme value distribution type I (aka the Gumbel distribution or 
        Gumbel distribution type I):
        F = exp{-exp[-(x-mu)/scale]}       (max variant)
        f = exp[-(x-mu)/scale] * exp{-exp[-(x-mu)/scale]} / scale
        F = 1 - exp{-exp[+(x-mu)/scale]}   (min variant)
        f = exp[+(x-mu)/scale] * exp{-exp[+(x-mu)/scale]} / scale

        type must be 'max' or 'min'
        scale must be > 0.0
        """

        self._checkminmax(xmin, xmax, pmin, pmax, 'rextreme_I')

        pmn = pmin
        pmx = pmax
        if xmin > float('-inf'):
            pmn = max(pmin, cextreme_I(type, mu, scale, xmin))
        if xmax < float('inf'):
            pmx = min(pmax, cextreme_I(type, mu, scale, xmax))

        p  =  pmn + (pmx-pmn)*self.runif01()
        x  =  iextreme_I(p, type, mu, scale)

        return x

    # end of rextreme_I

# ------------------------------------------------------------------------------

    def rextreme_gen(self, type, shape, mu, scale, \
                                      xmin=float('-inf'), xmax=float('inf'), \
                                      pmin=0.0, pmax=1.0):
        """
        Generalized extreme value distribution:
        F = exp{-[1-shape*(x-mu)/scale]**(1/shape)}       (max version)
        f = [1-shape*(x-mu)/scale]**(1/shape-1) * 
                                 exp{-[1-shape*(x-mu)/scale]**(1/shape)} / scale
        F = 1 - exp{-[1+shape*(x-mu)/scale]**(1/shape)}   (min version)
        f = [1+shape*(x-mu)/scale]**(1/shape-1) * 
                                 exp{-[1+shape*(x-mu)/scale]**(1/shape)} / scale
        shape  < 0 => Type II
        shape  > 0 => Type III
        shape -> 0 => Type I - Gumbel

        type must be 'max' or 'min'
        scale must be > 0.0

        A REASONABLE SCHEME SEEMS TO BE mu = scale WHICH SEEMS TO LIMIT THE
        DISTRIBUTION TO EITHER SIDE OF THE Y-AXIS!
        """

        self._checkminmax(xmin, xmax, pmin, pmax, 'rextreme_gen')

        pmn = pmin
        pmx = pmax
        if xmin > float('-inf'):
            pmn = max(pmin, cextreme_gen(type, shape, mu, scale, xmin))
        if xmax < float('inf'):
            pmx = min(pmax, cextreme_gen(type, shape, mu, scale, xmax))

        p  =  pmn + (pmx-pmn)*self.runif01()
        x  =  iextreme_gen(p, type, shape, mu, scale)

        return x

    # end of rextreme_gen

# ------------------------------------------------------------------------------

    def rlogistic(self, mu, scale, xmin=float('-inf'), xmax=float('inf'), \
                                   pmin=0.0, pmax=1.0):  
        """
        The logistic distribution: F = 1 / {1 + exp[-(x-m)/s]}; x on R
        m is the mean and mode, and s is a scale parameter (s >= 0) 
        """

        self._checkminmax(xmin, xmax, pmin, pmax, 'rlogistic')

        pmn = pmin
        pmx = pmax
        if xmin > float('-inf'): pmn = max(pmin, clogistic(mu, scale, xmin))
        if xmax < float('inf'):  pmx = min(pmax, clogistic(mu, scale, xmax))

        p  =  pmn + (pmx-pmn)*self.runif01()
        x  =  ilogistic(p, mu, scale)

        return x

    # end of rlogistic

# ------------------------------------------------------------------------------

    def rrayleigh(self, sigma, xmax=float('inf'), pmax=1.0):  
        """
        The Rayleigh distribution:
        f = (x/s**2) * exp[-x**2/(2*s**2)]
        F = 1 - exp[-x**2/(2*s**2)]
        x, s >= 0 
        """

        assert xmax >= 0.0, "xmax must be a non-negative float in rrayleigh!"
        self._checkpmax(pmax, 'rrayleigh')

        pmx = pmax
        if xmax < float('inf'): pmx = min(pmax, crayleigh(sigma, xmax))

        p  =  pmx * self.runif01()
        x  =  irayleigh(p, sigma)

        return x

    # end of rrayleigh

# ------------------------------------------------------------------------------

    def rpareto_zero(self, lam, xm, xmax=float('inf'), pmax=1.0):   
        """
        The Pareto distribution with the support shifted to [0, inf):
        f = lam * xm**lam / (x+xm)**(lam+1)
        F = 1 - [xm/(x+xm)]**lam
        x in [0, inf)
        lam > 0
        For lam < 1 all moments are infinite
        For lam < 2 all moments are infinite except for the mean
        """

        assert xmax >= 0.0, "xmax must be a non-negative float in rpareto_zero!"
        self._checkpmax(pmax, 'rpareto_zeroero')

        pmx = pmax
        if xmax < float('inf'): pmx = min(pmax, cpareto_zero(lam, xm, xmax))

        p  =  pmx * self.runif01()
        x  =  ipareto_zero(p, lam, xm)

        return x

    # end of rpareto_zero

# ------------------------------------------------------------------------------

    def rkodlin(self, gam, eta, xmax=float('inf'), pmax=1.0):  
        """
        The Kodlin distribution, aka the linear hazard rate distribution:
        f = (gam + eta*x) * exp{-[gam*x + (1/2)*eta*x**2]},
        F = 1 - exp{-[gam*x + (1/2)*eta*x**2]};  x, gam, eta >= 0 
        """

        assert xmax >= 0.0, "xmax must be a non-negative float in rkodlin!"
        self._checkpmax(pmax, 'rkodlin')

        pmx = pmax
        if xmax < float('inf'): pmx = min(pmax, ckodlin(scale, xmax))

        p  =  pmx * self.runif01()
        x  =  ikodlin(p, gam, eta)

        return x

    # end of rkodlin

# ------------------------------------------------------------------------------
# Auxiliary functions
# ------------------------------------------------------------------------------

    def _checkpmax(self, pmax, caller='caller'):

        assert 0.0 <= pmax and pmax <= 1.0, \
                      "pmax must be in [0.0, 1.0] in" + caller + "!"

    # end of _checkpmax

# ------------------------------------------------------------------------------

    def _checkminmax(self, xmin, xmax, pmin, pmax, caller='caller'):

        assert xmax >= xmin,        \
                     "xmax must be >= xmin in " + caller + "!"
        assert  0.0 <= pmin <= pmax, \
                     "pmin must be in [0.0, pmax] in " + caller + "!"
        assert pmin <= pmax <= 1.0,\
                     "pmax must be in [pmin, 1.0] in " + caller + "!"

    # end of _checkminmax

# ------------------------------------------------------------------------------

# end of ABCRand

# ------------------------------------------------------------------------------
예제 #9
0
class EventScheduleStack:
    """
    The class defines a schedule of events in a general discrete-event 
    simulation. It relies on the misclib.Stack class (but it does not 
    inherit from the Stack class). 

    EventScheduleStack is less efficient (=slower) than the heap-based 
    EventSchedule class but the two classes are otherwise equivalent in 
    principle. Since it uses the Stack class also for the event types,
    and since all the methods of the list class are available via the 
    Stack class, EventScheduleStack may be used for creating subclasses 
    to this class which can handle more complex types of schedules than 
    those that can be handled by the dict-based EventSchedule.

    An additional feature of EventScheduleStack is that it can handle TIES 
    perfectly (the dict-based EventSchedule only handles ties approximately).
    """

    # ------------------------------------------------------------------------------

    def __init__(self, eventlist=[], timelist=[], sort=False):
        """
        Creates two new stacks: one for the events and one for the corresponding 
        time points. The events could for instance be described by strings. The 
        times are (of course) floating-point numbers. The two stacks can be 
        filled here but they have to be synchronized and in temporal order. 
        """

        assert len(timelist) == len(eventlist), \
                "input lists are of unequal length in EventScheduleStack!"

        # If sort:    # to be added later

        self.__eventstack = Stack(eventlist)
        self.__timestack = Stack(timelist)

    # end of __init__

# ------------------------------------------------------------------------------

    def put_event(self, eventtype, eventtime):
        """
        Method used to place an event in the event schedule. The event is placed 
        in temporal order in the synchronized stacks eventstack and timestack. 
        """

        # Place in correct time order (what push and unshift returns - the
        # number of  elements in the Stack After the "putting" - is not needed
        # for anything and it is OK to do as below)

        if not self.__timestack or eventtime >= self.__timestack[
                -1]:  # Put last
            self.__timestack.push(eventtime)
            self.__eventstack.push(eventtype)
            #self.__timestack  = [eventtime] # Does not work - turns the
            #self.__eventstack = [eventtype] # Stack into a list again

        elif eventtime < self.__timestack[0]:  # Put first
            self.__timestack.unshift(eventtime)
            self.__eventstack.unshift(eventtype)

        else:
            index = bisect(self.__timestack, eventtime)
            self.__timestack.splice(index, 0, eventtime)
            self.__eventstack.splice(index, 0, eventtype)

        return eventtime  # For symmetry with put_event of EventSchedule

    # end of put_event

# ------------------------------------------------------------------------------

    def show_next_event(self):
        """
        Just look at the next event without touching the stack. 
        """

        #if not equal_length(self.__eventstack, self.__timestack):
        # is not needed due to put_event

        try:
            nextevent = self.__eventstack[0]
        except IndexError:
            nextevent = None

        try:
            nexttime = self.__timestack[0]
        except IndexError:
            nexttime = None

        return nextevent, nexttime

    # end of show_next_event

# ------------------------------------------------------------------------------

    def get_next_event(self):
        """
        Method used to get the next stored event (the first in time) from the 
        event schedule. The synchronized stacks eventstack and timestack are 
        shifted in Perl fashion - i. e. the element that is returned is also 
        removed from the stack. Returns None if the stacks are empty. 
        """

        #if not equal_length(self.__eventstack, self.__timestack):
        # is not needed due to put_event

        nextevent = self.__eventstack.shift()  # None if empty
        nexttime = self.__timestack.shift()  # None if empty

        return nextevent, nexttime

    # end of get_next_event

# ------------------------------------------------------------------------------

    def zap_events(self):
        """
        Empty the schedule to allow for a restart. Return the length of 
        the stack as it was before zapping.
        """

        #if not equal_length(self.__eventstack, self.__timestack):
        # is not needed due to put_event

        length = self.__eventstack.zap()
        length = self.__timestack.zap()

        return length

    # end of zap_events


# ------------------------------------------------------------------------------

# end of EventScheduleStack

# ------------------------------------------------------------------------------
예제 #10
0
class EventScheduleStack:
    """
    The class defines a schedule of events in a general discrete-event 
    simulation. It relies on the misclib.Stack class (but it does not 
    inherit from the Stack class). 

    EventScheduleStack is less efficient (=slower) than the heap-based 
    EventSchedule class but the two classes are otherwise equivalent in 
    principle. Since it uses the Stack class also for the event types,
    and since all the methods of the list class are available via the 
    Stack class, EventScheduleStack may be used for creating subclasses 
    to this class which can handle more complex types of schedules than 
    those that can be handled by the dict-based EventSchedule.

    An additional feature of EventScheduleStack is that it can handle TIES 
    perfectly (the dict-based EventSchedule only handles ties approximately).
    """
# ------------------------------------------------------------------------------

    def __init__(self, eventlist=[], timelist=[], sort=False):
        """
        Creates two new stacks: one for the events and one for the corresponding 
        time points. The events could for instance be described by strings. The 
        times are (of course) floating-point numbers. The two stacks can be 
        filled here but they have to be synchronized and in temporal order. 
        """

        assert len(timelist) == len(eventlist), \
                "input lists are of unequal length in EventScheduleStack!"

        # If sort:    # to be added later

        self.__eventstack = Stack(eventlist)
        self.__timestack  = Stack(timelist)

    # end of __init__

# ------------------------------------------------------------------------------

    def put_event(self, eventtype, eventtime):
        """
        Method used to place an event in the event schedule. The event is placed 
        in temporal order in the synchronized stacks eventstack and timestack. 
        """

        # Place in correct time order (what push and unshift returns - the 
        # number of  elements in the Stack After the "putting" - is not needed 
        # for anything and it is OK to do as below)

        if not self.__timestack or eventtime >= self.__timestack[-1]: # Put last
            self.__timestack.push(eventtime)
            self.__eventstack.push(eventtype)
            #self.__timestack  = [eventtime] # Does not work - turns the 
            #self.__eventstack = [eventtype] # Stack into a list again
    
        elif eventtime < self.__timestack[0]:                # Put first
            self.__timestack.unshift(eventtime)
            self.__eventstack.unshift(eventtype)

        else:
            index = bisect(self.__timestack, eventtime)
            self.__timestack.splice(index, 0, eventtime)
            self.__eventstack.splice(index, 0, eventtype)

        return eventtime  # For symmetry with put_event of EventSchedule

    # end of put_event

# ------------------------------------------------------------------------------

    def show_next_event(self):
        """
        Just look at the next event without touching the stack. 
        """

        #if not equal_length(self.__eventstack, self.__timestack):
        # is not needed due to put_event

        try:
            nextevent = self.__eventstack[0]
        except IndexError:
            nextevent = None

        try:
            nexttime  = self.__timestack[0]
        except IndexError:
            nexttime  =  None

        return nextevent, nexttime

    # end of show_next_event

# ------------------------------------------------------------------------------

    def get_next_event(self):
        """
        Method used to get the next stored event (the first in time) from the 
        event schedule. The synchronized stacks eventstack and timestack are 
        shifted in Perl fashion - i. e. the element that is returned is also 
        removed from the stack. Returns None if the stacks are empty. 
        """

        #if not equal_length(self.__eventstack, self.__timestack):
        # is not needed due to put_event

        nextevent = self.__eventstack.shift()   # None if empty
        nexttime  = self.__timestack.shift()    # None if empty

        return nextevent, nexttime

    # end of get_next_event

# ------------------------------------------------------------------------------

    def zap_events(self):
        """
        Empty the schedule to allow for a restart. Return the length of 
        the stack as it was before zapping.
        """

        #if not equal_length(self.__eventstack, self.__timestack):
        # is not needed due to put_event

        length = self.__eventstack.zap()
        length = self.__timestack.zap()

        return length

    # end of zap_events

# ------------------------------------------------------------------------------

# end of EventScheduleStack

# ------------------------------------------------------------------------------
예제 #11
0
    def __init__(self, line, nserv):
        """
        line  is 'Line' or 'LineStack'
        nserv is the initial number of servers
        """

        # Attributes made available from the outside (not assignable, though):
        # --------------------------------------------------------------------

        self.__narriv = 0  # Accumulated number of arrivers until present

        self.__ninline = 0  # Present number waiting in line

        self.__nfreeserv = nserv  # Present number of free servers

        self.__nbalked = 0  # Accumulated number of balkers

        self.__nreneged = 0  # Accumulated number of renegers

        self.__nescaped = 0  # = self.__nbalked + self.__nreneged

        # Attributes not available from the outside:
        # --------------------------------------------------------------------

        self.__nserv = nserv  # Initial number of free servers

        if line == 'Line': self.__line = Deque()
        elif line == 'LineStack': self.__line = Stack()
        else: raise Error("'line' must be 'Line' or 'LineStack' in ABCLine!")

        # dict containing the "reneging times" (time
        self.__renegers = {}  # points of the future reneging events) with
        # the corresponding arrival times as keys

        self.__wtimes = array('d', [])
        # 'd' array of waiting times for those not escaped

        self.__length = {}  # dict containing the line length history with
        # the corresponding time spans as keys

        self.__prevctl = 0.0  # The previous clock time when the line length
        # was last changed

        # dict containing the history of the number of
        self.__systemh = {}  # customers in the system with the corresponding
        # time spans as keys

        self.__prevcts = 0.0  # The previous clock time when the number of
        # customers in system was last changed

        self.__length = {}  # dict containing the line length history with
        # the corresponding time spans as keys

        self.__prevctl = 0.0  # The previous clock time when the line length
        # was last changed

        # dict containing the history of the number of
        self.__idleh = {}  # of free/idle servers in the system with the
        # corresponding time spans as keys

        self.__prevcti = 0.0  # The previous clock time when the number of
예제 #12
0
class ABCLine(metaclass=ABCMeta):
    """
    This class contains everything that is common to the Line and LineStack 
    classes. Since this is also an abstract base class, it cannot be used in a 
    standalone fashion. Its methods and attributes can only be reached through 
    its subclasses Line and LineStack, which inherit from this class.
    """
    # ------------------------------------------------------------------------------

    @abstractmethod
    def __init__(self, line, nserv):
        """
        line  is 'Line' or 'LineStack'
        nserv is the initial number of servers
        """

        # Attributes made available from the outside (not assignable, though):
        # --------------------------------------------------------------------

        self.__narriv = 0  # Accumulated number of arrivers until present

        self.__ninline = 0  # Present number waiting in line

        self.__nfreeserv = nserv  # Present number of free servers

        self.__nbalked = 0  # Accumulated number of balkers

        self.__nreneged = 0  # Accumulated number of renegers

        self.__nescaped = 0  # = self.__nbalked + self.__nreneged

        # Attributes not available from the outside:
        # --------------------------------------------------------------------

        self.__nserv = nserv  # Initial number of free servers

        if line == 'Line': self.__line = Deque()
        elif line == 'LineStack': self.__line = Stack()
        else: raise Error("'line' must be 'Line' or 'LineStack' in ABCLine!")

        # dict containing the "reneging times" (time
        self.__renegers = {}  # points of the future reneging events) with
        # the corresponding arrival times as keys

        self.__wtimes = array('d', [])
        # 'd' array of waiting times for those not escaped

        self.__length = {}  # dict containing the line length history with
        # the corresponding time spans as keys

        self.__prevctl = 0.0  # The previous clock time when the line length
        # was last changed

        # dict containing the history of the number of
        self.__systemh = {}  # customers in the system with the corresponding
        # time spans as keys

        self.__prevcts = 0.0  # The previous clock time when the number of
        # customers in system was last changed

        self.__length = {}  # dict containing the line length history with
        # the corresponding time spans as keys

        self.__prevctl = 0.0  # The previous clock time when the line length
        # was last changed

        # dict containing the history of the number of
        self.__idleh = {}  # of free/idle servers in the system with the
        # corresponding time spans as keys

        self.__prevcti = 0.0  # The previous clock time when the number of
        # free/idle servers was last changed

    # end of __init__

# ------------------------------------------------------------------------------

    def __getattr__(self, attr_name):
        """
        This method overrides the built-in __getattr__ and makes the values
        of the internal attributes externally available.
        """

        if attr_name == "ninline": return self.__ninline
        elif attr_name == "nfreeserv": return self.__nfreeserv
        elif attr_name == "narriv": return self.__narriv
        elif attr_name == "nbalked": return self.__nbalked
        elif attr_name == "renegers": return self.__renegers
        elif attr_name == "nreneged": return self.__nreneged
        elif attr_name == "nescaped": return self.__nescaped

    # end of __getattr__

# ------------------------------------------------------------------------------

    def __setattr__(self, attr_name, value):
        """
        This method overrides the built-in __setattr__ and makes the values of 
        the internal attributes externally available more difficult to screw 
        up from the outside.
        """

        if attr_name == "ninline"   \
        or attr_name == "nfreeserv" \
        or attr_name == "narriv"    \
        or attr_name == "nbalked"   \
        or attr_name == "renegers"  \
        or attr_name == "nreneged"  \
        or attr_name == "nescaped"  :
            errtxt1 = ": Can't change value/length of attribute"
            errtxt2 = " from the outside!"
            raise Error(attr_name + errtxt1 + errtxt2)

        else:
            self.__dict__[attr_name] = value

    # end of __setattr__

# ------------------------------------------------------------------------------

    def place_last_in_line(self, times):
        """
        Add one or several arrival times at the back end of the line. 
        The length of the expanded line is returned. 

        NB The elements in an iterable will be placed so that the last 
        will be the last in the line etc.

        Arguments:
        ----------
        times     single time or tuple/list of times

        Outputs:
        ----------
        The number presently in the expanded line
        """

        try:  # First try the hunch that the argument is a sequence:
            self.__narriv += len(times)
            for tim in times:
                self.__length[tim - self.__prevctl] = len(self.__line)
                self.__systemh[tim-self.__prevcts] = len(self.__line) + \
                                                 self.__nserv - self.__nfreeserv
                self.__prevctl = tim
                self.__prevcts = tim

        except TypeError:  # ..otherwise the arg is a single number
            self.__narriv += 1
            self.__length[times - self.__prevctl] = len(self.__line)
            self.__systemh[times-self.__prevcts] = len(self.__line) + \
                                               self.__nserv - self.__nfreeserv
            self.__prevctl = times
            self.__prevcts = times

        self.__line.push(times)

        self.__ninline = len(self.__line)

        return self.__ninline

    # end of place_last_in_line

# ------------------------------------------------------------------------------

    def place_first_in_line(self, times):
        """
        Add one or several arrival times at the front end of the line. 
        The length of the expanded line is returned. 

        NB The elements in an iterable will be placed so that the first 
        will be the first in the line etc.

        Arguments:
        ----------
        times     single time or tuple/list of times

        Outputs:
        ----------
        The length of the expanded line 
        """

        try:  # First try the hunch that the argument is a sequence:
            self.__narriv += len(times)
            for tim in times:
                self.__length[tim - self.__prevctl] = len(self.__line)
                self.__systemh[tim-self.__prevcts] = len(self.__line) + \
                                                 self.__nserv - self.__nfreeserv
                self.__prevctl = tim
                self.__prevcts = tim

        except TypeError:  # ..otherwise the arg is a single number
            self.__narriv += 1
            self.__length[times - self.__prevctl] = len(self.__line)
            self.__systemh[times-self.__prevctl] = len(self.__line) + \
                                               self.__nserv - self.__nfreeserv
            self.__prevctl = times
            self.__prevcts = times

        self.__line.unshift(times)

        self.__ninline = len(self.__line)

        return self.__ninline

    # end of place_first_in_line

# ------------------------------------------------------------------------------

    def call_next_in_line(self, calltime):
        """
        Fetch the first arrival time at the front end of the line,
        remove it from the line, and make one server busy.
        
        Outputs:
        --------
        The arrival time at the front end of the line 
        """

        self.__length[calltime - self.__prevctl] = len(self.__line)
        self.__idleh[calltime - self.__prevcti] = self.nfreeserv
        self.__systemh[calltime-self.__prevcts] = len(self.__line) + \
                                              self.__nserv - self.__nfreeserv
        self.__prevctl = calltime
        self.__prevcti = calltime
        self.__prevcts = calltime

        arrivaltime = self.__line.shift()

        self.__nfreeserv -= 1
        if self.__nfreeserv < 0:
            raise Error("Number of servers are negative in call_next_in_line!")

        self.__wtimes.append(calltime - arrivaltime)

        self.__ninline = len(self.__line)

        return arrivaltime

    # end of call_next_in_line

# ------------------------------------------------------------------------------

    def remove_last_in_line(self, tim):
        """
        Fetch the last arrival time at the back end of the line and remove 
        it from the line. To be used - for instance - when a customer that 
        already has been placed in line balks (even balkers must first be 
        placed in line - before balking!).

        Outputs:
        --------
        The arrival time at the back end of the line 
        """

        self.__length[tim - self.__prevctl] = len(self.__line)
        self.__systemh[tim-self.__prevcts] = len(self.__line) + \
                                         self.__nserv - self.__nfreeserv
        self.__prevctl = tim
        self.__prevcts = tim

        lasttime = self.__line.pop()

        self.__ninline = len(self.__line)

        return lasttime

    # end of remove_last_in_line

# ------------------------------------------------------------------------------

    def server_freed_up(self, tim):
        """
        Adds '1' to the present number of free servers - 
        to be used when a customer has been served.
        May also be used to change the total number of servers 
        during the course of a simulation.
        """

        self.__idleh[tim - self.__prevcti] = self.nfreeserv
        self.__systemh[tim-self.__prevcts] = len(self.__line) + \
                                         self.__nserv - self.__nfreeserv
        self.__nfreeserv += 1
        self.__prevcti = tim
        self.__prevcts = tim

    # end of server_freed_up

# ------------------------------------------------------------------------------

    def balker(self, tim):
        """
        Removes the arrival just placed last in line. Returns the arrival 
        time of the balker.
        
        NB. Balkers must first be placed in line - before balking!
        Outputs:
        --------
        The arrival time of the balker 
        """

        self.__nbalked += 1
        self.__nescaped += 1

        return self.remove_last_in_line(tim)

    # end of balker

# ------------------------------------------------------------------------------

    def reneger(self, arentime):
        """
        To be used when a "renege" type event is picked up by get_next_event 
        and removes the corresponding arrival time (arentime) in the line of 
        arrival times GIVEN that it has not been removed already from calling  
        call_next_in_line (the existence of the corresponding arrival time in 
        the line is checked first). 
        """

        arrivaltime = self.__renegers[arentime]
        del self.__renegers[arentime]

        if arrivaltime in self.__line:
            self.__length[arentime - self.__prevctl] = len(self.__line)
            self.__systemh[arentime-self.__prevcts] = len(self.__line) + \
                                                  self.__nserv - self.__nfreeserv
            self.__prevctl = arentime
            self.__prevcts = arentime
            self.__line.remove(arrivaltime)
            self.__ninline = len(self.__line)
            self.__nreneged += 1
            self.__nescaped += 1
            return True
        else:
            return False

    # end of reneger

# ------------------------------------------------------------------------------

    def waiting_times_all(self):
        """
        Returns an unsorted 'd' array containing the waiting times 
        for all served. 
        """

        return self.__wtimes

    # end of waiting_times_all

# ------------------------------------------------------------------------------

    def waiting_times_linedup(self):
        """
        Returns an unsorted 'd' array containing the waiting times 
        only for those who had to wait in line before being served. 
        """

        wtimesshort = array('d', [])
        for wt in self.__wtimes:
            if wt != 0.0: wtimesshort.append(wt)

        return wtimesshort

    # end of waiting_times_linedup

# ------------------------------------------------------------------------------

    def line_stats(self):
        """
        Returns a dict containing line length statistics with the line lengths 
        as keys and the corresponding times as values.
        """

        # This method turns the self.__length dict around: the statistics are
        # collected with the line length as keys and the corresponding times
        # as values (there will be fewer elements in the returned dict than in
        # self.__length, of course...)

        statdict = {}
        for keytime in self.__length:
            try:
                statdict[self.__length[keytime]] += keytime
            except KeyError:
                statdict[self.__length[keytime]] = keytime

        return statdict

    # end of line_stats

# ------------------------------------------------------------------------------

    def idle_stats(self):
        """
        Returns a dict containing server statistics with the number of idle 
        servers as keys and the corresponding times as values.
        """

        # This method turns the self.__idleh dict around: the statistics are
        # collected with the number of free/idle servers in the system as keys
        # and the corresponding times as values (there will be fewer elements
        # in the returned dict than in self.__idleh, of course...)

        statdict = {}
        for keytime in self.__idleh:
            try:
                statdict[self.__idleh[keytime]] += keytime
            except KeyError:
                statdict[self.__idleh[keytime]] = keytime

        return statdict

    # end of idle_stats

# ------------------------------------------------------------------------------

    def system_stats(self):
        """
        Returns a dict containing statistics for the total number of customers 
        in the system as keys and the corresponding times as values.
        """

        # This method turns the self.__systemh dict around: the statstics are
        # collected with the number of customers in the system as keys and the
        # corresponding times as values (there will be fewer elements in the
        # returned dict than in self.__systemh, of course...)

        statdict = {}
        for keytime in self.__systemh:
            try:
                statdict[self.__systemh[keytime]] += keytime
            except KeyError:
                statdict[self.__systemh[keytime]] = keytime

        return statdict

    # end of system_stats


# ------------------------------------------------------------------------------

# end of ABCLine

# ------------------------------------------------------------------------------
예제 #13
0
class ABCRand(metaclass=ABCMeta):
    """
    This class contains everything that is common to the GeneralRandomStream 
    and InverseRandomStream classes. Since this is also an abstract base 
    class, it cannot be used in a standalone fashion. Its methods and 
    attributes can only be reached through its subclasses GeneralRandomStream 
    and InverseRandomStream, which inherit from this class.

    ABCRand imports (and uses) some of the methods from Python's built-in 
    Random class including the "Mersenne Twister". This makes the Mersenne 
    Twister the basic rng of ABCRand and its heirs. All methods in ABCRand 
    that are not taken from Random are inverse-based, but the methods from 
    Random are generally not inverse-based. It may be noted that the Mersenne 
    Twister is a very reputable random number generator having a period of 
    2**19937-1.

    The following methods from Python's own Random class are inheritable from 
    ABCRand: randrange, randint, choice, shuffle, sample, vonmisesvariate, 
    paretovariate and weibullvariate.

    All the methods in ABCRand are inherited by GeneralRandomStream including 
    the ones imported from Random. The methods added by GeneralRandomStream do 
    NOT return the inverse of the [0.0, 1.0] random numbers from the basic rng.
    
    InverseRandomStream inherits the methods in ABCRand with the EXCEPTION 
    of the methods from Random (the Mersenne Twister is still there, though), 
    making all the methods in InverseRandomStream inverse-based, including 
    the methods added in the latter.
    
    The docstring documentation of Random, GeneralRandomStream and 
    InverseRandomStream must always be consulted before using the methods 
    inherited from ABCRand!

    NB  Some methods may return float('inf') or float('-inf') !
    """
    # ------------------------------------------------------------------------------

    @abstractmethod
    def __init__(self, nseed=2147483647, heir=None):
        """
        Initiates the random stream using the input seed 'nseed' and Python's 
        __init__ constructor method. Unless...
        ...the input seed 'nseed' happens to be a list or tuple of numbers 
        in [0.0, 1.0], in which case this external feed will be used as the 
        basis of all random variate generation for the instance and will be 
        used in place of consecutively sampled numbers from Python's built-in 
        "random" method! 
        """

        if isinstance(nseed, int):
            assert is_posinteger(nseed), \
               "The seed (if not a feed) must be a positive integer in ABCRand!"
            rstream = Random(nseed)
            self._feed = False
            self.runif01 = rstream.random
            if heir != "InverseRandomStream":
                self.randrange = rstream.randrange
                self.randint = rstream.randint
                self.vonmisesvariate = rstream.vonmisesvariate
                # Random.paretovariate and Random.weibullvariate
                # are used by methods in GeneralRandomStream
                self._paretovariate = rstream.paretovariate
                self._weibullvariate = rstream.weibullvariate

        else:  # nseed is a list or tuple
            # Check to see beforehand that no numbers
            # from the feed is outside [0.0, 1.0]
            for x in nseed:
                assert 0.0 <= x <= 1.0, \
                    "number from feed is outside of [0.0, 1.0] in ABCRand!"
            self._feed = Stack(nseed)  # Creates a Stack object
            self.runif01 = self.__rfeed01

    # end of __init__

# ------------------------------------------------------------------------------

    def __rfeed01(self):
        """
        Will be used as the "getter" of numbers in [0.0, 1.0] 
        when the input to the class is a feed rather than a 
        positive integer seed!
        """
        # Removes one number at a time
        return self._feed.shift()  # from the stack

    # end of __rfeed01

# ------------------------------------------------------------------------------

    def runif_int0N(self, number):
        """
        Generator of uniformly distributed integers in [0, number) (also the 
        basis of some other procedures for generating random variates). 
        Numbers returned are 0 through number-1. NB!!!!!!!
        """

        assert is_posinteger(number)

        return int(number * self.runif01())

    # end of runif_int0N

# ------------------------------------------------------------------------------

    def rsign(self):
        """
        Returns -1.0 or 1.0 with probability 0.5 for each. 
        """

        x = self.runif01()
        if x <= 0.5: return -1.0
        else: return 1.0

    # end of rsign

# ------------------------------------------------------------------------------

    def runifab(self, left, right):
        """
        Generator of uniformly distributed floats between 'left' and 'right'. 
        """

        assert right >= left, "support span must not be negative in runifab!"

        x = left + (right - left) * self.runif01()

        x = kept_within(left, x, right)

        return x

    # end of runifab

# ------------------------------------------------------------------------------

    def rchistogram(self, values, qumul):
        """
        Generates random variates from an input CUMULATIVE histogram.
        'values' is a list/tuple with FLOATS in ascending order - A MUST! 
        These values represent bin end points and must be one more than 
        the number of cumulative frequencies, and where...
        ...'qumul' are the corresponding CUMULATIVE FREQUENCIES such that 
        qumul[k] = P(x<=values[k+1]).
        
        The cumulative frequencies must of course obey qumul[k+1] >= qumul[k],
        otherwise an exception will be raised!
        
        The values of the random variate are assumed to be uniformly 
        distributed within each bin.
        """

        p = self.runif01()

        x = ichistogram(p, values, qumul)

        return x

    # end of rchistogram

# ------------------------------------------------------------------------------

    def rchistogram_int(self, values, qumul):
        """
        Generates random variates from an input CUMULATIVE histogram.
        'values' is a list/tuple with INTEGERS in ascending order - A MUST! 
        These values represent bin end points and must be one more than 
        the number of cumulative frequencies, and where...
        ...'qumul' are the corresponding CUMULATIVE FREQUENCIES such that 
        qumul[k] = P(x<=values[k+1]).

        NB The first element of the values list is will never be returned!
        The first integer to be returned is values[0] + 1   !!!!

        The cumulative frequencies must of course obey qumul[k+1] >= qumul[k],
        otherwise an exception will be raised!
        
        The integer values of the random variate are assumed to be uniformly 
        distributed within each bin.
        """

        p = self.runif01()

        x = ichistogram_int(p, values, qumul)

        return x

    # end of rchistogram_int

# ------------------------------------------------------------------------------

    def rtriang(self, left, mode, right):
        """
        Generator of triangularly distributed random numbers on [left, right] 
        with the peak of the pdf at mode. 
        """

        assert left <= mode and mode <= right, \
                                  "mode out of support range in rtriang!"

        p = self.runif01()

        span = right - left
        spanlo = mode - left
        spanhi = right - mode
        #height  =  2.0 / span
        #surf1   =  0.5 * spanlo * height
        #surf1   =  spanlo/float(span)

        #if p <= surf1:
        if p <= spanlo / float(span):
            #x  =  sqrt(2.0*spanlo*p/height)
            x = sqrt(spanlo * span * p)
        else:
            #x  =  span - sqrt(2.0*spanhi*(1.0-p)/height)
            x = span - sqrt(spanhi * span * (1.0 - p))
        x += left

        x = kept_within(left, x, right)

        return x

    # end of rtriang

# ------------------------------------------------------------------------------

    def rtri_unif_tri(self, a, b, c, d):
        """
        Triangular-uniform-triangular distribution with support on [a, d] and 
        with breakpoints in b and c
                      ------
        pdf:        /        \
                   /           \
            ------               -------                                         
        """

        # Input check -----------------------
        assert a <= b and b <= c and c <= d, \
                                "break points scrambled in rtri_unif_tri!"
        # -----------------------------------

        if d == a: return a

        dcba = d + c - b - a
        h = 2.0 / dcba
        first = 0.5 * h * (b - a)
        p = self.runif01()
        poh = 0.5 * p * dcba

        if p <= first:
            x = sqrt(2.0 * (b - a) * poh) + a
        elif first < p <= first + h * (c - b):
            x = (c - b) * (poh - 0.5 * (b - a)) + b
        else:
            x = d - sqrt((d - c) * dcba * (1.0 - p))

        x = kept_within(a, x, d)

        return x

    # end of rtri_unif_tri

# ------------------------------------------------------------------------------

    def rkumaraswamy(self, a, b, x1, x2):
        """
        The Kumaraswamy distribution f = a*b*x**(a-1) * (1-x**a)**(b-1)
                                     F = 1 - (1-x**a)**b
                                     a, b >= 0; 0 <= x <= 1
        The Kumaraswamy is very similar to the beta distribution !!!
        
        x2 >= x1 !!!! 
        """

        assert a > 0.0, "shape parameters in rkumaraswamy must be positive!"
        assert b > 0.0, "shape parameters in rkumaraswamy must be positive!"
        assert x2 >= x1, "support range in rkumaraswamy must not be negative!"

        y = (1.0 - (1.0 - self.runif01())**(1.0 / b))**(1.0 / a)

        x = y * (x2 - x1) + x1

        x = kept_within(x1, x, x2)

        return x

    # end of rkumaraswamy

# ------------------------------------------------------------------------------

    def rsinus(self, left, right):
        """
        The "sinus distribution". 
        """

        assert right >= left, "support range must not be negative in rsinus!"

        #x  =  left  +  (right-left) * acos(1.0-2.0*self.runif01()) / PI
        x = left + (right - left) * PIINV * acos(1.0 - 2.0 * self.runif01())

        x = kept_within(left, x, right)

        return x

    # end of rsinus

# ------------------------------------------------------------------------------

    def rgeometric(self, phi):
        """
        The geometric distribution with p(K=k) = phi * (1-phi)**(k-1)  and 
        P(K>=k) = sum phi * (1-phi)**k = 1 - q**k, where q = 1 - phi and  
        0 < phi <= 1 is the success frequency or "Bernoulli probability" 
        and K >= 1 is the number of  trials to the first success in a series 
        of Bernoulli trials. It is easy to prove that P(k) = 1 - (1-phi)**k: 
        let q = 1 - phi. p(k) = (1-q) * q**(k-1) = q**(k-1) - q**k. Then P(1) = 
        p(1) = 1 - q. P(2) = p(1) + p(2) = 1 - q + q - q**2 = 1 - q**2. 
        Induction can be used to show that P(k) = 1 - q**k = 1 - (1-phi)**k 
        """

        assert 0.0 <= phi and phi <= 1.0, \
                      "success frequency must be in [0.0, 1.0] in rgeometric!"

        if phi == 1.0: return 1  # Obvious...

        p = self.runif01()

        q = 1.0 - phi

        if phi < 0.25:  # Use the direct inversion formula
            lnq = -safelog(q)
            ln1mp = -safelog(1.0 - p)
            kg = 1 + int(ln1mp / lnq)

        else:  # Looking for the passing point is more efficient for
            kg = 1  # phi >= 0.25 (it's still inversion)
            u = p
            a = phi
            while True:
                u = u - a
                if u > 0.0:
                    kg += 1
                    a *= q
                else:
                    break

        return kg

    # end of rgeometric

# ------------------------------------------------------------------------------

    def remp_exp(self, values, npexp=0, ordered=False, \
                                        xmax=float('inf'), pmax=1.0):
        """
        The mixed expirical/exponential distribution from Bratley, Fox and 
        Schrage. A polygon (piecewise linearly interpolated cdf) is used 
        together with a (shifted) exponential for the tail. The procedure 
        is designed so as to preserve the mean of the input sample.
        
        The input is a set of observed points (vector) and an integer 
        representing the npexp largest points that will be used to formulate 
        the exponential tail.
        
        NB it is assumed that x is in [0, inf) (with the usual cutoff 
        provisions)  !!!!!
        
        The function may also be used for a piecewise linear cdf without the 
        exponential tail - corrections are made to preserve the mean in this 
        case as well !!! 
        """

        assert xmax >= 0.0, "xmax must be a non-negative float in remp_exp!"
        self._checkpmax(pmax, 'remp_exp')

        pmx = pmax
        #if xmax < float('inf'):
        #pmx = min(pmax, cemp_exp(values, npexp, ordered, xmax))

        p = pmx * self.runif01()
        x = iemp_exp(p, values, npexp, ordered)

        return x

    # end of remp_exp

# ------------------------------------------------------------------------------

    def rexpo_gen(self, a, b, c, xmin=float('-inf'), xmax=float('inf'), \
                                 pmin=0.0, pmax=1.0):
        """
        The generalized continuous double-sided exponential 
        distribution (x in R):
        x <= c: f  =  [a*b/(a+b)] * exp(+a*[x-c])
                F  =   [b/(a+b)]  * exp(+a*[x-c])
        x >= c: f  =  [a*b/(a+b)] * exp(-b*[x-c])
                F  =  1 - [a/(a+b)]*exp(-b*[x-c])
        a > 0, b > 0
        
        NB The symmetrical double-sided exponential sits in rlaplace!
        """

        self._checkminmax(xmin, xmax, pmin, pmax, 'rexpo_gen')

        pmn = pmin
        pmx = pmax
        if xmin > float('-inf'): pmn = max(pmin, cexpo_gen(a, b, c, xmin))
        if xmax < float('inf'): pmx = min(pmax, cexpo_gen(a, b, c, xmax))

        p = pmn + (pmx - pmn) * self.runif01()
        x = iexpo_gen(p, a, b, c)

        return x

    # end of rexpo_gen

# ------------------------------------------------------------------------------

    def rlaplace(self, loc, scale, xmin=float('-inf'), xmax=float('inf'), \
                                   pmin=0.0, pmax=1.0):
        """
        The Laplace aka the symmetrical double-sided exponential distribution 
        f = ((1/2)/s)) * exp(-abs([x-l]/s))
        F = (1/2)*exp([x-l]/s)  {x <= 0},  F = 1 - (1/2)*exp(-[x-l]/s)  {x >= 0}
        s >= 0  
        """

        self._checkminmax(xmin, xmax, pmin, pmax, 'rlaplace')

        pmn = pmin
        pmx = pmax
        if xmin > float('-inf'): pmn = max(pmin, claplace(shift, scale, xmin))
        if xmax < float('inf'): pmx = min(pmax, claplace(shift, scale, xmax))

        p = pmn + (pmx - pmn) * self.runif01()
        x = ilaplace(p, loc, scale)

        return x

    # end of rlaplace

# ------------------------------------------------------------------------------

    def rtukeylambda_gen(self, lam1, lam2, lam3, lam4, pmin=0.0, pmax=1.0):
        """
        The Friemer-Mudholkar-Kollia-Lin generalized Tukey lambda distribution.
        lam1 is a location parameter and lam2 a scale parameter. lam3 and lam4
        are associated with the shape of the distribution. 
        """

        assert lam2 > 0.0, \
          "shape parameter lam2 must be a positive float in rtukeylambda_gen!"
        assert 0.0 <= pmin < pmax, \
                             "pmin must be in [0.0, pmax) in rtukeylambda_gen!"
        assert pmin < pmax <= 1.0, \
                             "pmax must be in (pmin, 1.0] in rtukeylambda_gen!"

        p = pmin + (pmax - pmin) * self.runif01()

        if lam3 == 0.0:
            q3 = safelog(p)
        else:
            q3 = (p**lam3 - 1.0) / lam3

        if lam4 == 0.0:
            q4 = safelog(1.0 - p)
        else:
            q4 = ((1.0 - p)**lam4 - 1.0) / lam4

        x = lam1 + (q3 - q4) / lam2

        return x

    # end of rtukeylambda_gen

# ------------------------------------------------------------------------------

    def rcauchy(self, location, scale, xmin=float('-inf'), xmax=float('inf'), \
                                       pmin=0.0, pmax=1.0):
        """
        Generator of random variates from the Cauchy distribution: 
        f = 1 / [s*pi*(1 + [(x-l)/s]**2)]
        F = (1/pi)*arctan((x-l)/s) + 1/2
        (also known as the Lorentzian or Lorentz distribution)
        
        scale must be >= 0 
        """

        self._checkminmax(xmin, xmax, pmin, pmax, 'rcauchy')

        pmn = pmin
        pmx = pmax
        if xmin > float('-inf'):
            pmn = max(pmin, ccauchy(location, scale, xmin))
        if xmax < float('inf'):
            pmx = min(pmax, ccauchy(location, scale, xmax))

        p = pmn + (pmx - pmn) * self.runif01()
        x = icauchy(p, location, scale)

        return x

    # end of rcauchy

# ------------------------------------------------------------------------------

    def rextreme_I(self, type, mu, scale, \
                                        xmin=float('-inf'), xmax=float('inf'), \
                                        pmin=0.0, pmax=1.0):
        """
        Extreme value distribution type I (aka the Gumbel distribution or 
        Gumbel distribution type I):
        F = exp{-exp[-(x-mu)/scale]}       (max variant)
        f = exp[-(x-mu)/scale] * exp{-exp[-(x-mu)/scale]} / scale
        F = 1 - exp{-exp[+(x-mu)/scale]}   (min variant)
        f = exp[+(x-mu)/scale] * exp{-exp[+(x-mu)/scale]} / scale

        type must be 'max' or 'min'
        scale must be > 0.0
        """

        self._checkminmax(xmin, xmax, pmin, pmax, 'rextreme_I')

        pmn = pmin
        pmx = pmax
        if xmin > float('-inf'):
            pmn = max(pmin, cextreme_I(type, mu, scale, xmin))
        if xmax < float('inf'):
            pmx = min(pmax, cextreme_I(type, mu, scale, xmax))

        p = pmn + (pmx - pmn) * self.runif01()
        x = iextreme_I(p, type, mu, scale)

        return x

    # end of rextreme_I

# ------------------------------------------------------------------------------

    def rextreme_gen(self, type, shape, mu, scale, \
                                      xmin=float('-inf'), xmax=float('inf'), \
                                      pmin=0.0, pmax=1.0):
        """
        Generalized extreme value distribution:
        F = exp{-[1-shape*(x-mu)/scale]**(1/shape)}       (max version)
        f = [1-shape*(x-mu)/scale]**(1/shape-1) * 
                                 exp{-[1-shape*(x-mu)/scale]**(1/shape)} / scale
        F = 1 - exp{-[1+shape*(x-mu)/scale]**(1/shape)}   (min version)
        f = [1+shape*(x-mu)/scale]**(1/shape-1) * 
                                 exp{-[1+shape*(x-mu)/scale]**(1/shape)} / scale
        shape  < 0 => Type II
        shape  > 0 => Type III
        shape -> 0 => Type I - Gumbel

        type must be 'max' or 'min'
        scale must be > 0.0

        A REASONABLE SCHEME SEEMS TO BE mu = scale WHICH SEEMS TO LIMIT THE
        DISTRIBUTION TO EITHER SIDE OF THE Y-AXIS!
        """

        self._checkminmax(xmin, xmax, pmin, pmax, 'rextreme_gen')

        pmn = pmin
        pmx = pmax
        if xmin > float('-inf'):
            pmn = max(pmin, cextreme_gen(type, shape, mu, scale, xmin))
        if xmax < float('inf'):
            pmx = min(pmax, cextreme_gen(type, shape, mu, scale, xmax))

        p = pmn + (pmx - pmn) * self.runif01()
        x = iextreme_gen(p, type, shape, mu, scale)

        return x

    # end of rextreme_gen

# ------------------------------------------------------------------------------

    def rlogistic(self, mu, scale, xmin=float('-inf'), xmax=float('inf'), \
                                   pmin=0.0, pmax=1.0):
        """
        The logistic distribution: F = 1 / {1 + exp[-(x-m)/s]}; x on R
        m is the mean and mode, and s is a scale parameter (s >= 0) 
        """

        self._checkminmax(xmin, xmax, pmin, pmax, 'rlogistic')

        pmn = pmin
        pmx = pmax
        if xmin > float('-inf'): pmn = max(pmin, clogistic(mu, scale, xmin))
        if xmax < float('inf'): pmx = min(pmax, clogistic(mu, scale, xmax))

        p = pmn + (pmx - pmn) * self.runif01()
        x = ilogistic(p, mu, scale)

        return x

    # end of rlogistic

# ------------------------------------------------------------------------------

    def rrayleigh(self, sigma, xmax=float('inf'), pmax=1.0):
        """
        The Rayleigh distribution:
        f = (x/s**2) * exp[-x**2/(2*s**2)]
        F = 1 - exp[-x**2/(2*s**2)]
        x, s >= 0 
        """

        assert xmax >= 0.0, "xmax must be a non-negative float in rrayleigh!"
        self._checkpmax(pmax, 'rrayleigh')

        pmx = pmax
        if xmax < float('inf'): pmx = min(pmax, crayleigh(sigma, xmax))

        p = pmx * self.runif01()
        x = irayleigh(p, sigma)

        return x

    # end of rrayleigh

# ------------------------------------------------------------------------------

    def rpareto_zero(self, lam, xm, xmax=float('inf'), pmax=1.0):
        """
        The Pareto distribution with the support shifted to [0, inf):
        f = lam * xm**lam / (x+xm)**(lam+1)
        F = 1 - [xm/(x+xm)]**lam
        x in [0, inf)
        lam > 0
        For lam < 1 all moments are infinite
        For lam < 2 all moments are infinite except for the mean
        """

        assert xmax >= 0.0, "xmax must be a non-negative float in rpareto_zero!"
        self._checkpmax(pmax, 'rpareto_zeroero')

        pmx = pmax
        if xmax < float('inf'): pmx = min(pmax, cpareto_zero(lam, xm, xmax))

        p = pmx * self.runif01()
        x = ipareto_zero(p, lam, xm)

        return x

    # end of rpareto_zero

# ------------------------------------------------------------------------------

    def rkodlin(self, gam, eta, xmax=float('inf'), pmax=1.0):
        """
        The Kodlin distribution, aka the linear hazard rate distribution:
        f = (gam + eta*x) * exp{-[gam*x + (1/2)*eta*x**2]},
        F = 1 - exp{-[gam*x + (1/2)*eta*x**2]};  x, gam, eta >= 0 
        """

        assert xmax >= 0.0, "xmax must be a non-negative float in rkodlin!"
        self._checkpmax(pmax, 'rkodlin')

        pmx = pmax
        if xmax < float('inf'): pmx = min(pmax, ckodlin(scale, xmax))

        p = pmx * self.runif01()
        x = ikodlin(p, gam, eta)

        return x

    # end of rkodlin

# ------------------------------------------------------------------------------
# Auxiliary functions
# ------------------------------------------------------------------------------

    def _checkpmax(self, pmax, caller='caller'):

        assert 0.0 <= pmax and pmax <= 1.0, \
                      "pmax must be in [0.0, 1.0] in" + caller + "!"

    # end of _checkpmax

# ------------------------------------------------------------------------------

    def _checkminmax(self, xmin, xmax, pmin, pmax, caller='caller'):

        assert xmax >= xmin,        \
                     "xmax must be >= xmin in " + caller + "!"
        assert  0.0 <= pmin <= pmax, \
                     "pmin must be in [0.0, pmax] in " + caller + "!"
        assert pmin <= pmax <= 1.0,\
                     "pmax must be in [pmin, 1.0] in " + caller + "!"

    # end of _checkminmax


# ------------------------------------------------------------------------------

# end of ABCRand

# ------------------------------------------------------------------------------