Skip to content

Repository files navigation

📊 QUICK

Python Streamlit FastAPI NumPy SciPy Plotly DuckDB uv Ruff Conventional Commits

QUICK (Queue with Impatient Customers) — это программный комплекс для моделирования систем массового обслуживания с ограниченной очередью и нетерпеливыми заявками, то есть заявками, которые покидают очередь, не дождавшись обслуживания. Комплекс рассчитан на исследователей и инженеров, которым нужно не одно число на выходе, а поведение системы во времени: как меняются вероятности состояний, потери и пропускная способность при заданных интенсивностях. Поддерживаются многолинейные СМО с пуассоновским входным потоком и системы с марковским входным потоком (MAP) от нескольких датчиков разного типа.

Устройство проекта продиктовано обусловленностью задачи. Переходный режим — это система дифференциальных уравнений Колмогорова размерности n · m, у матрицы генератора MAP-системы собственные значения часто оказываются комплексными, а реальный разброс интенсивностей (от сотен до десятков тысяч заявок в единицу времени) делает решение в двойной точности неустойчивым. Поэтому расчёт вынесен за отдельный интерфейс решателей: NumPy для быстрых прикидок, mpmath для произвольной точности, объединённый режим — чтобы сверить одно с другим. По той же причине результаты кешируются в DuckDB: при подборе графиков один и тот же набор параметров пересчитывается многократно.

📦 Зависимости

  • Python 3.13+
  • uv
  • Task — опционально, для сокращённых команд
  • Docker — опционально, для запуска в контейнере

Установка зависимостей всех групп (dev, web, api):

task sync

То же самое без Task:

uv sync --group dev --group web --group api

🚀 Запуск

Веб-интерфейс на Streamlit — основной способ работы, открывается на http://localhost:8501:

task run

REST API на FastAPI, документация Swagger — на http://localhost:8000/docs:

task run-api

Запуск в контейнере (сборка и старт веб-интерфейса, кеш расчётов пробрасывается наружу):

task docker

Без Task те же команды выглядят так:

uv run streamlit run web/app.py
uv run uvicorn api.main:app --host 0.0.0.0 --port 8000

🔧 Конфигурация

Параметры читаются из переменных окружения или файла .env через Pydantic Settings. Файл .env не обязателен: без него используются значения по умолчанию.

Переменная Назначение По умолчанию
DISABLE_CACHE Отключает кеширование результатов расчётов в DuckDB True
DUCKDB_PATH Путь к файлу базы данных с кешем cache_data.duckdb
DISABLE_LOGGING Полностью отключает логирование False
LOG_LEVEL Уровень логирования loguru INFO
LOG_PATH Файл для записи логов; пустая строка — только вывод в консоль ""

По умолчанию кеш выключен: он ускоряет повторные расчёты с теми же параметрами, но хранит результаты в pickle-формате и растёт вместе с числом экспериментов. Для перебора параметров и построения развёрток его стоит включить, задав DISABLE_CACHE=False.

📐 Модель

Задача описывается тремя независимыми осями выбора.

Ось Значения Чем определяется
Тип системы Многолинейная, MAP с несколькими датчиками Структурой входного потока и числом приборов
Режим Переходный, стационарный Нужна ли динамика во времени или установившееся решение
Метод расчёта Аналитический, численный, имитационный Требуемой точностью и допустимым временем счёта

Аналитический метод раскладывает матрицу коэффициентов по собственным значениям и дополнительно выбирает движок вычислений: NumPy, mpmath или объединённый режим со сверкой результатов по заданному допуску. Численный метод интегрирует уравнения Колмогорова методом Рунге—Кутты (RK45) и устойчив к комплексным собственным значениям, на которых аналитический движок NumPy отказывается считать. Имитационный метод — это моделирование Монте-Карло, единственный режим, поддерживающий интенсивности, заданные временными рядами: λ(t), μ(t) и ν(t) с интерполяцией по схеме LOCF.

При запуске переходного расчёта в лог дополнительно выводится средняя интенсивность входного потока λ̄. Для MAP-модели это эффективная стационарная интенсивность λ̄ = θD₁e, где θ — стационарное распределение фаз MAP; она не равна простому арифметическому среднему введённых λᵢ. Для численного метода в лог также выводится полный спектр матрицы коэффициентов, включая комплексные собственные значения, а затем краткая спектральная сводка (max Re(eig) и минимальный по модулю ненулевой корень). Эти значения являются диагностическими и не участвуют в интегрировании RK45.

📊 Характеристики

Для каждой точки времени рассчитываются вероятности состояний и производные от них характеристики.

Обозначение Характеристика Смысл
P(k, i, t) Вероятности состояний Вероятность иметь k заявок в системе при фазе MAP i
λ(t) Интенсивность входного потока Нестационарная величина: меняется вслед за распределением фаз MAP
N_b(t) Среднее число заявок в буфере Взвешенная сумма вероятностей уровней с непустым буфером
P_fail(t) Вероятность отказа Система заполнена, заявка не принимается
P_uns(t) Вероятность ухода Накопленная доля уходов: ∫v_loss(u)du / (E N(0) + ∫λ(u)du)
P_loss(t) Вероятность потерь Сумма отказов и уходов
P_serv(t) Вероятность обслуживания Дополнение вероятности потерь до единицы
A(t) Пропускная способность Поток фактически обслуженных заявок
v_serv(t) Интенсивность потока обслуживания μ, взвешенная на вероятность занятости прибора
v_loss(t) Интенсивность потока ухода νN_b(t) = ν∑ₖ k∑ᵢP(k+1,i,t)
α(t) Распределение фаз MAP Вклад каждого датчика во входной поток в данный момент
K_уст(t) Коэффициент устойчивости Отношение качества обслуживания к минимально допустимому уровню

Величины v_serv(t), v_loss(t) и α(t) показывают систему изнутри: в отличие от параметров μ и ν, которые постоянны, фактические потоки обслуживания и ухода меняются во времени вместе с заполнением системы. Для переходного режима P_uns(t) вычисляется не как мгновенное отношение v_loss(t)/λ(t), а как отношение накопленных интегралов с учётом E N(0), как в формуле (16) статьи.

Для MAP-систем каждая характеристика считается в одном из двух режимов. В агрегированном режиме вероятности состояний суммируются по всем датчикам, ∑ᵢP(k, i, t). В индивидуальном — в тех же формулах суммирование заменяется единственным слагаемым P(k, j, t), то есть система рассматривается только через состояния выбранного датчика. Подстановка сквозная: λ_j(t), P_fail,j(t), N_b,j(t) и всё, что из них следует, считаются по одному датчику, поэтому вклады датчиков в аддитивные величины в сумме дают агрегированное значение. Режим выбирается в поле «Режим расчёта по датчикам»; отдельный пункт «Все датчики (по отдельности)» раскладывает характеристики на вклады всех датчиков сразу и выводит их набором кривых.

🧊 Трёхмерная визуализация

Отдельная страница строит характеристики в трёх измерениях, где по оси X всегда отложено время, а смысл третьей оси выбирается для каждого графика отдельно.

Третья ось Что получается Стоимость
Отношение ν/μ Поверхность «время × ν/μ × характеристика» на отрезке [0, 1] Один полный расчёт на точку сетки
Развёртка по параметру Поверхность «время × параметр × характеристика» для сетки значений μ, ν или λ Один полный расчёт на точку сетки
Вторая метрика Траектория системы в координатах двух характеристик, окрашенная по времени Бесплатно, из готового расчёта
Номер датчика Поверхность «время × датчик × характеристика» Один покомпонентный расчёт

Отношение ν/μ — основной вид третьей оси: интенсивность обслуживания μ при развёртке не меняется, а интенсивность ухода задаётся как ν = (ν/μ)·μ, поэтому поверхность показывает вклад именно нетерпеливости заявок. Отрезок замкнут с обеих сторон и обе границы имеют физический смысл: при ν/μ = 0 ухода нет и очередь дожидается обслуживания целиком, при ν/μ = 1 заявка покидает очередь в среднем за то же время, за которое обслуживается. Величина безразмерна, поэтому такие поверхности сравнимы между системами с разными абсолютными интенсивностями.

Развёртки — единственный вид оси, требующий пересчётов, поэтому размер сетки вынесен в настройку, а одинаковые развёртки разных графиков считаются один раз. Значение по умолчанию — 12 точек: для системы из 15 состояний численным методом такая развёртка занимает около 0,4 с, то есть строится без заметной паузы.

Оформление поверхностей подчинено читаемости двух независимых параметров: сетка включена по всем трём осям и оформлена одинаково, на поверхность наносятся линии уровня, а цветовая шкала вынесена за пределы сцены и уменьшена, чтобы не отбирать ширину у самого графика.

🛡️ Устойчивость

Устойчивость в комплексе понимается не как классическое условие существования стационарного режима ρ < 1. СМО с конечным буфером и нетерпеливыми заявками стационарна при любых интенсивностях, поэтому такое условие о качестве работы системы ничего не говорит. Вместо него используется динамическое определение: система устойчива, если базовая характеристика качества обслуживания a(t) на всём переходном интервале не опускается ниже критического уровня a_кр. Это позволяет определять не отдельные значения характеристик, а область параметров, в которой система сохраняет устойчивый режим работы.

Величина Формула Смысл
a(t) 1 − P_loss(t) либо A(t) Базовая характеристика качества обслуживания
a_кр 1 − P_доп либо A_crit Минимально допустимый уровень
K_уст(t) a(t) / a_кр Мгновенный коэффициент: ниже единицы — требование нарушено
K_уст ∫a(t)dt / (a_кр·t_пер) Интегральный коэффициент за переходный процесс
R (∫a(t)dt − a_кр·t_пер) / ((a(t₀) − a_кр)·t_пер) · 100% Запас: доля от максимально возможного избытка
S₊ ∫max(a(t) − a_кр, 0)dt Площадь закрашенного участка характеристики выше критического уровня
t_пер Длительность переходного процесса

Мгновенный коэффициент K_уст(t) рассчитывается как обычная характеристика системы и выводится отдельным графиком. Дополнительно строится специальный 2D-график запаса устойчивости в стиле рисунков из Приложения А: на нём показаны a(t), горизонталь a_кр и полупрозрачная закрашенная область выше критического уровня. Под графиком выводится её численная площадь S₊. Интегральные K_уст, R и S₊ считаются по переходному участку [t₀, t₀ + t_пер] — именно на нём система способна нарушить требования, тогда как на установившемся участке значение уже постоянно и лишь размывало бы оценку. Длительность t_пер определяется по последнему выходу a(t) за коридор вокруг установившегося значения, а не по первому входу в него: колебательный процесс с затухающими выбросами иначе засчитывался бы установившимся слишком рано.

Диапазон Режим
K_уст ≥ 1,2 Устойчивый: запас не меньше 20%
1,0 ≤ K_уст < 1,2 Пограничный: требование выполняется, запаса нет
K_уст < 1,0 Неустойчивый

Показатель R воспроизведён по ВКР без изменений, вместе с его областью применимости: знаменатель считает наибольшим значением характеристики начальное. Для спадающего переходного процесса, который рассматривается в ВКР, это так. Расчёт же с пустой системы даёт растущую a(t), и тогда a(t₀) максимумом не является — R выходит за 100%, а при a(t₀), близком к a_кр, теряет смысл. Поэтому R сравним между вариантами системы только для процессов, начинающихся с наилучшего значения; в остальных случаях следует опираться на K_уст, у которого такого ограничения нет.

Проверка минимума первична по отношению к интегральному коэффициенту: короткий, но недопустимый провал ниже a_кр делает систему неустойчивой даже при высоком среднем уровне. В этом случае запас обнуляется, R = 0.

Значения по умолчанию — a_кр = 0,95 (допустимая доля потерь 5%) и порог 1,2; и уровень, и границы задаются в интерфейсе. Базовая характеристика переключается между вероятностью обслуживания и пропускной способностью: первая безразмерна и сравнима между системами, вторая измеряется в заявках за единицу времени и сравнима с требованиями к каналу.

🌐 API

Метод Путь Назначение
GET /health Проверка состояния сервиса
GET /api/v1/meta Справочник допустимых значений: типы систем, режимы, методы, движки
POST /api/v1/simulate Расчёт системы и возврат вероятностей вместе с характеристиками

Поле sensor_index в теле запроса переключает расчёт в индивидуальный режим по указанному датчику, блок stability задаёт критический уровень и границы областей устойчивости.

Пример запроса к справочнику:

curl http://localhost:8000/api/v1/meta

🧰 Разработка

Форматирование и автоисправление замечаний линтера:

task fmt

Полный набор проверок — Ruff, mypy, аудит уязвимостей и поиск неиспользуемых зависимостей:

task check

Коммиты подчиняются соглашению Conventional Commits; Commitizen помогает составить сообщение, а при выпуске версии формирует changelog:

task cz-commit

📁 Структура исходников

src/quick/
├── domain/        # Параметры моделей, перечисления, конфигурации расчёта
├── services/
│   ├── matrix_builders/   # Построение матриц коэффициентов по типу системы
│   ├── solvers/           # Аналитические, численные и имитационные решатели
│   ├── systems/           # Модели СМО в стационарном и переходном режимах
│   ├── systems_behavior/  # Формулы характеристик, зависящие от типа системы
│   └── stability.py       # Определение устойчивости и коэффициент запаса
├── db/            # Клиент DuckDB и декоратор кеширования
├── settings/      # Загрузка конфигурации из окружения
└── utils/         # Логирование, прогресс, сериализация, генераторы матриц

web/               # Streamlit: страницы моделирования и 3D-визуализации
api/               # FastAPI: схемы, конвертеры и маршруты

Разделение на matrix_builders, solvers и systems держит математику независимой от способа её решения: тип системы определяет матрицу коэффициентов, метод расчёта — способ получить из неё вероятности, а формулы характеристик не зависят ни от того, ни от другого.

📜 Лицензия

Проект распространяется под лицензией MIT. Подробности — в файле LICENSE.md.

About

Моделирование и анализ СМО с ограниченной очередью и нетерпеливыми заявками: переходный и стационарный режимы, MAP-потоки от нескольких датчиков, аналитические, численные и имитационные расчёты.

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Contributors

Languages