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 runREST 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; и уровень, и границы задаются в интерфейсе. Базовая характеристика переключается между вероятностью обслуживания и пропускной способностью: первая безразмерна и сравнима между системами, вторая измеряется в заявках за единицу времени и сравнима с требованиями к каналу.
| Метод | Путь | Назначение |
|---|---|---|
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-commitsrc/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.