Ejemplo n.º 1
0
def mss(channels_count, in_stream, out_stream, cost, total_time, queue_size=None, queue_time=None, fault_stream=None, repair_stream=None, destructive=None, repair_cost=None, times=1):
    u'Система массового обслуживания'
    
    Infinity = float('inf')
    
    if queue_size is None:
        queue_size = Infinity
    
    if queue_time is None:
        queue_time = Infinity
    
    if fault_stream is None:
        fault_stream = Infinity
    
    if repair_stream is None:
        repair_stream = 0
    
    if destructive is None:
        destructive = 0
    
    if repair_cost is None:
        repair_cost = 0
    
    stream = distributions.filter(lambda x: x > 0, distributions.exponential)
    
    in_stream = stream(1.0 / in_stream)
    out_stream = stream(1.0 / out_stream)
    
    fault_stream = stream(fault_stream)
    repair_stream = stream(repair_stream)
    
    # Действия
    mss.actions = {}

    increment, decrement = 1, -1

    def action(state=0):
        def mix(f):
            key = f.__name__
            mss.actions[key] = 0

            def spice(*args):
                result = f(*args)

                mss.actions[key] += 1 # Статистика

                # Состояние
                duration = (args[0] - mss.prevTime)
                try:
                    mss.states[mss.state] += duration
                except:
                    mss.states.append(duration)

                mss.prevTime = args[0]
                mss.state += state

                return result

            return spice

        return mix

    @action(state=increment)
    def accept(time):
        'Принять заявку в обработку'

        # Поиск свободного канала
        free = [key for key, value in enumerate(orders) if value == Infinity and 1 <= key <= channels_count]

        # Выбор случайного канала
        channel = random.choice(free)

        # Время обработки заявки
        orders[channel] = time + out_stream.next()

    @action(state=increment)
    def sit(time):
        'Отправка заявки в очередь'
        orders.append(time + queue_time)

    @action()
    def sizeout(time):
        'Отклонение заявки по размеру очереди'

    @action(state=decrement)
    def leave(time, channel):
        'Окончание обслуживания заявки'
        orders[channel] = Infinity

    @action(state=decrement)
    def stand(time):
        'Удаление первого элемента из очереди'
        orders.pop(channels_count + 1)

    @action(state=decrement)
    def timeout(time):
        'Уход элемента из очереди по таймауту'
        orders.pop(channels_count + 1)

    @action()
    def fault(time, destructive):
        'Неисправность'

        if not mss.state:
            return # Авария проходит без последствий, если нет заявок в обработке

        # Занятые каналы
        busy = [index for index, value in enumerate(orders) if 1 <= index <= channels_count and value < Infinity]

        # Выбор канала, на котором происходит авария
        channel = random.choice(busy)

        # Заявка ожидает ремонта оборудования
        orders[channel] += repair_stream.next()

        if destructive: # Если случилась авария,
            orders[channel] += out_stream.next() # Заявка ещё и обрабатывается заново
            mss.actions['destructive_fault'] = mss.actions.get('destructive_fault', 0) + 1

    # События

    def onNew(time):
        'Появление новой заявки'

        if mss.state < channels_count: # Если есть пустые каналы,
            accept(time) # то принимаем заявку;
        elif mss.state < channels_count + queue_size: # если их нет, но есть места в очереди -
            sit(time) # направляем её в очередь.
        else: # если же и очередь забита -
            sizeout(time) # отвергаем заявку.

    def onLeave(time, channel):
        'Канал channel освобождается заявкой'
        leave(time, channel) # Заявка уходит из канала
        if mss.state + 1 > channels_count: # Если очередь непуста...
            stand(time) # то из очереди вызывается первая заявка
            accept(time) # и уходит на выполнение

    def onTimeout(time):
        'Уход заявки из очереди по таймауту'
        timeout(time)

    def onDispatch(*args):
        'Редирект на нужный обработчик события'
        onDispatch.route(*args)
        orders[0] = eventStream.next()

    def onFault(time):
        'Неисправность'
        fault(time, random.random() <= destructive)

    def inputCombinator():
        'Комбинация генераторов'

        # Время последней заявки и последней неисправности
        new, fault = in_stream.next(), fault_stream.next()

        while True:
        # Время следующей заявки и следующей неисправности
            if new < fault:
                onDispatch.route = onNew
                yield new
                new += in_stream.next()
            else:
                onDispatch.route = onFault
                yield fault
                fault += fault_stream.next()

            # Бесконечность

    Infinity = float('Infinity')

    # Поток событий - заявок и аварий
    eventStream = inputCombinator()

    # Время последнего изменения состояния
    mss.prevTime = 0

    # Структура, хранящая местоположение заявок и операции над ними.
    orders = [eventStream.next()] + [Infinity] * channels_count

    # Состояние
    mss.state = 0
    mss.states = []

    while True:
    # Тип и значение следующего события
        event = min(range(len(orders)), key=orders.__getitem__)
        time = orders[event]

        # Ограничение времени жизни модели
        if time > total_time:
            break

        if event:
            if event <= channels_count: # Завершена обработка заявки
                onLeave(time, event)
            else: # Заявка уходит из очереди по таймауту
                onTimeout(time)
        else: # Независимое событие (новая заявка или неисправность)
            onDispatch(time) # уходит на диспетчеризацию.

    # Время истекло, но в обработке и в очереди ещё могли остаться заявки.
    mss.actions['shutdown'] = mss.state

    # Сбор статистики.
    # Сначала собирается баланс доходов и расходов. Он имеет следующую
    # структуру:
    # - Все прошедшие через систему заявки
    #   - Принятые
    #   - Не принятые
    #       - По размеру очереди
    #       - По времени ожидания
    #       - По рабочему времени
    
    denied = dict((field, mss.actions[field]) for field in ('sizeout', 'timeout', 'shutdown'))
    denied['total'] = sum(denied.values())
    
    requests = {
        'denied': denied,
        'accepted': mss.actions['accept'],
        'total': mss.actions['accept'] + denied['total'],
    }
    
    # Теперь следующий шаг - статистика по балансу. Она имеет точно такую же
    # структуру, но добавляется пункт costs -- затраты на обслуживание системы,
    # заданные функцией от количества каналов.
    balance = tree.recursive_map(lambda x: x * cost, requests)
    
    balance['repairs'] = mss.actions['fault'] * repair_cost
    
    costs_function  = lambda n: 1 - 0.5 * n + 0.5 * n * n
    costs = costs_function(channels_count)
    
    balance['costs'] = costs
    balance['income'] = balance['accepted'] - costs - balance['repairs']
    
    balance['relative_income'] = requests['accepted'] - costs / cost
    
    # Теперь статистика по загрузке каналов и очереди -- распределение
    # вероятностей состояний системы. Для каждого из состояний указывается
    # вероятность пребывания системы в нём.
    workload = [time / total_time * 100 for time in mss.states]
    
    workload_mean = sum(n * p / total_time for n, p in enumerate(mss.states))
    
    output = {
        'requests': requests,
        'balance': balance,
        'workload': workload,
        'workload_mean': workload_mean,
        'workload_mean_probability': workload[int(round(workload_mean))],
    }
    
    if mss.actions['fault']:
        output['faults'] = {
            'total': mss.actions['fault'],
            'fatal': mss.actions.get('destructive_fault', 0),
        }
    
    return output
Ejemplo n.º 2
0
def warehouse(lot_size, limit, amount, total_time, demand_mu, demand_sigma, supply_mu, supply_sigma, demand_price, supply_price, storage_price, fine, times=1):
    u'Склад'
    
    # Потоки
    stream = distributions.filter(lambda x: x > 0, distributions.normal)
    
    demand = distributions.inverse(stream(demand_mu, demand_sigma))
    supply = stream(supply_mu, supply_sigma)
    
    # Цены
    price = {
        'supplies': supply_price,
        'sales': demand_price,
        'storage': storage_price,
        'fines': fine,
    }
    
    # Статистика
    fines    = 0 # Количество выплат неустойки
    sales    = 0 # Количество проданных единиц продукции
    supplies = 0 # Количество полученных партий
    storage  = 0 # Среднее количество продукции на складе
    
    # Лог
    history = [(0, amount)]
    
    # Событие за границами total_time никогда не произойдёт
    delivery = never = total_time + 1

    time = 0 # Время модели
    for delta in demand:
        time += delta
        
        # Выход за границы. Завершаем работу.
        if time > total_time:
            time -= delta
            break
        
        storage += amount * delta
        
        # Приехал новый заказ
        if delivery <= time:
            history.append((delivery, amount))
            amount += lot_size # Принимаем его
            supplies += 1
            storage += (time - delivery) * lot_size # Учитываем хранение партии
            history.append((delivery, amount))
            delivery = never
        
        # На складе есть продукция
        if amount: # Продаём одну единицу
            amount -= 1
            sales  += 1
            if not amount:
                history.append((time, amount))
        else: # Продукции нет
            fines  += 1 # Выплачиваем неустойку
        
        # Если нужно - заказываем новую партию
        if amount <= limit and delivery == never:
            delivery = time + supply.next()
    
    # Учитываем в storage хранение товара после последней покупки
    storage += (total_time - time) * amount
    
    # Обрабатываем запоздавший заказ, если он есть
    if delivery < total_time:
        history.append((delivery, amount))
        amount += lot_size
        supplies += 1
        history.append((delivery, amount))
        
        storage += (total_time - delivery) * lot_size
        delivery = never
    
    history.append((total_time, amount))
    
    # Окончательная статистика
    
    units = {
        'sales': sales,
        'supplies': supplies * lot_size,
        'fines': fines,
        'storage': storage / total_time,
    }
    
    balance = {}
    for item, value in units.items():
        balance[item] = value * price[item]
    
    balance['storage'] *= total_time
    
    cost_fields = ['supplies', 'fines', 'storage']
    
    balance['costs'] = 0
    for field in cost_fields:
        balance['costs'] += balance[field]
    
    balance['profit'] = balance['sales'] - balance['costs']
    
    return {
        'units': units,
        'balance': balance,
        'history': history if times == 1 else [],
    }