Для лекции используйте файл data.xlsx.
Постановка задачи
Модель представляет простую цепочку поставок. Имеется несколько дистрибьюторов и ритейлеров, расположенных в разных точках. Ритейлеры время от времени заказывают товар у дистрибьюторов. Доставка осуществляется грузовиками, которые после выгрузки у ритейлера возвращаются не к исходному, а к ближайшему дистрибьютору. Таким образом, грузовики не закреплены жёстко за конкретным дистрибьютором.
Исходные данные
Характеристики — города расположения дистрибьюторов и ритейлеров, а также начальное количество грузовиков у каждого дистрибьютора — хранятся в Excel-файле. Структура файла:
• Лист
Distributors: города (Марсель, Париж, Бордо, Лион, Нант) и количество грузовиков.
• Лист
Retailers: список городов ритейлеров (Тулуза, Лилль, Ницца, Брест, Страсбург и др.).
Модель будет читать эти данные для создания агентов.
Создание модели и GIS-карты
Создаётся новая модель с именем
Supply Chain. Единицы модельного времени — часы. На главное окно презентации добавляется
GIS карта (из палитры «Пути»). Карта растягивается на всё окно, так как основное содержание — расположение объектов на карте.
Импорт данных из Excel
В палитре «Базы данных» используется элемент «База данных». Через контекстное меню выполняется импорт таблиц, указывается Excel-файл. В настройках можно установить флажок «Обновлять данные при запуске модели», чтобы при каждом запуске считывались актуальные значения. Для географических названий эта опция важна для корректного парсинга, поэтому флажок устанавливается для обеих таблиц.
После импорта в разделе «Базы данных» появляются две таблицы:
Distributors и
Retailers, содержимое которых соответствует листам Excel.
Создание агентов-дистрибьюторов
1. В разделе «Агент» выбирается элемент
Популяция агентов и помещается на рабочую область. Задаётся имя
Distributor.
2. В свойствах популяции выбирается «Импортировать таблицу базы данных», указывается таблица
Distributors. Для каждой записи создаётся один агент.
3. Внешний вид агента задаётся в 2D (для снижения нагрузки) — иконка склада.
4. Созданный агент автоматически получает параметры, соответствующие столбцам таблицы:
location (город) и
trucks (количество грузовиков).
5. Для размещения агентов на карте используется свойство
Начальное местоположение. Выбирается размещение по первому результату поиска на карте (
First GIS search result), а в поле адреса указывается
self.location. Это означает, что для каждого агента его позиция определяется значением параметра
location.
Аналогичным образом создаётся популяция
ритейлеров (
Retailer) на основе таблицы
Retailers. В качестве иконки выбирается магазин. Начальное местоположение также задаётся через
self.location.
После запуска модели все дистрибьюторы и ритейлеры отображаются на карте Франции в соответствующих городах.
Создание грузовиков (агентов Truck)
Грузовики не создаются как простая популяция «один агент на запись», потому что их количество для каждого дистрибьютора различно и указано в столбце
trucks таблицы
Distributors.
1. Создаётся новый тип агента
Truck. Иконка — грузовик, размер уменьшается для наглядности.
2. При создании популяции выбирается опция «Я добавлю агентов позже» (популяция остаётся пустой).
3. В свойствах популяции включается загрузка из базы данных, указывается таблица
Distributors.
4. Выбирается вариант «Количество агентов задано в столбце» и указывается столбец
trucks. Местоположение каждого сгенерированного грузовика определяется столбцом
location.
Таким образом, для дистрибьютора в Париже с 4 грузовиками будет создано 4 агента грузовика в точке Парижа. Для проверки модель запускается — грузовики отображаются поверх иконок дистрибьюторов.
Логика движения грузовика: стейтчарт
Для описания поведения грузовика создаётся
стейтчарт (диаграмма состояний) внутри агента
Truck. Используются три состояния и переходы между ними.
Состояния:
• atDistributor — грузовик находится у какого либо дистрибьютора.
• movingToRetailer — движение к ритейлеру для выполнения заказа.
• movingToDistributor — движение к ближайшему дистрибьютору после доставки.
Переходы:
1. Из
atDistributor в
movingToRetailer. Тип перехода — по
получению сообщения order. Действие:
moveTo(msg.retailer). Грузовик получает заказ и направляется к ритейлеру, указанному в этом заказе.
2. Из
movingToRetailer в
movingToDistributor. Тип перехода —
по прибытию агента. Действие:
moveTo(getNearestAgent(main.distributors)). Доехав до ритейлера, грузовик сразу начинает движение к ближайшему дистрибьютору.
3. Из
movingToDistributor в
atDistributor. Тип перехода —
по прибытию агента. Действий нет: грузовик просто меняет состояние на ожидание у дистрибьютора.
Цепочка замыкается: получил заказ → поехал к ритейлеру → доехал → поехал к ближайшему дистрибьютору → прибыл → снова готов принимать заказы.
Агенту
Truck добавляется переменная
order типа
Order. Она хранит текущий заказ, который выполняет грузовик.
Агент заказа (Order) и генерация заказов
Заказ также является агентом (без визуализации). Создаётся тип агента
Order с параметрами:
•
amount — количество товара (целое число),
•
retailer — ритейлер, сделавший заказ (тип
Retailer).
Генерация заказов реализуется внутри агента Retailer с помощью события (Event).
Событие срабатывает с заданной интенсивностью —
5 заказов в день (экспоненциальное распределение интервалов). В действии события пишется Java-код:
java
Order order = new Order(uniform_discr(5, 20), this);
Truck truck = getNearestAgent(
main.trucks.stream()
.filter(t -> t.inState(atDistributor))
.collect(Collectors.toList())
);
if (truck != null) {
truck.order = order;
send(order, truck);
}
Пояснения к коду:
• Создаётся новый заказ
order со случайным целым количеством от 5 до 20 (равномерное дискретное распределение) и указанием текущего ритейлера (
this).
• Ищется ближайший свободный грузовик. Все грузовики модели хранятся в
main.trucks. Их список фильтруется: оставляются только те, которые находятся в состоянии
atDistributor. Это гарантирует, что не будет выбран грузовик, уже выполняющий другой заказ.
• Если такой грузовик найден (
truck != null), его переменной
order присваивается созданный заказ, и ему отправляется сообщение
order. Получив сообщение, грузовик согласно стейтчарту начинает движение к ритейлеру.
Если свободных грузовиков нет, заказ в данной версии модели теряется (может быть доработано очередью ожидания).
Запуск и наблюдение
При запуске модели на карте видны грузовики, которые начинают двигаться по дорогам. Получив заказ, они направляются к указанному ритейлеру, после прибытия сразу же едут к ближайшему дистрибьютору и остаются там ждать нового заказа. Так как грузовики возвращаются к ближайшему дистрибьютору, а не к исходному, распределение заказов становится динамическим.
Используются маршруты, определяемые встроенной картографической подсистемой AnyLogic. При необходимости можно загрузить собственные маршруты через специальные элементы.
Дальнейшие темы
После построения базовой цепочки поставок будут изучены основы Java для AnyLogic, что позволит глубже понимать написанный код. Затем планируется построение модели обслуживания ветряных турбин, где будет рассмотрен эксперимент оптимизации. Далее — пешеходное моделирование на примере упрощённой модели аэропорта для анализа пассажиропотоков и оптимизации логистики.
Краткие итоги
Построение модели цепочки поставок демонстрирует связку трёх ключевых механизмов: работа с внешними данными, агентное моделирование и геопространственная визуализация. Главная идея, заложенная в логику, — отсутствие жёсткой привязки грузовиков к конкретному дистрибьютору. Грузовик после доставки всегда возвращается к ближайшему, что делает систему гибкой и адаптивной: распределение транспортных ресурсов зависит от текущего пространственного положения и состояния агентов.
Технически это реализовано через взаимодействие трёх типов агентов. Ритейлеры генерируют заказы с помощью события с заданной интенсивностью и случайным объёмом. При этом поиск исполнителя включает фильтрацию всей популяции грузовиков по состоянию
atDistributor. Такой подход предотвращает назначение нового заказа грузовику, который уже находится в рейсе или на возврате. Сама логика передвижения инкапсулирована в стейтчарте, где переходы инициируются сообщениями либо фактом прибытия в точку назначения. Это позволяет чётко разделить этапы цикла обслуживания и делает поведение каждого грузовика независимым, но скоординированным через общие данные.
Практическая ценность модели заключается в том, что она служит шаблоном для более сложных транспортно логистических задач. Добавление очереди невыполненных заказов, учёт нескольких типов товаров, введение ограничений по грузоподъёмности или замена стандартных дорог на загружаемые маршруты — все эти расширения естественно ложатся на созданную архитектуру. Продемонстрированный приём генерации популяций агентов напрямую из таблиц Excel с управлением количеством создаваемых экземпляров позволяет быстро масштабировать модель при изменении входных данных.
Кроме того, работа с GIS-картой и встроенными функциями
moveTo() и
getNearestAgent() показывает, как с минимальными усилиями получить пространственно-реалистичную картину без ручного задания маршрутов. Интеграция Java-кода в события даёт полный контроль над бизнес-логикой: здесь и создание объектов, и обращение к окружению через
main, и потоковая фильтрация коллекций. Таким образом, модель не только решает конкретную учебную задачу, но и формирует набор компетенций, необходимых для самостоятельной разработки агентных имитаций с географической привязкой и динамическим распределением ресурсов.
1. Модель состоит из трёх типов агентов: дистрибьюторы, ритейлеры и грузовики, размещаемых на GIS карте.
2. Исходные данные импортируются из Excel файла; каждая запись таблицы может порождать одного или несколько агентов.
3. Популяции дистрибьюторов и ритейлеров создаются с опцией «один агент на запись», а количество грузовиков задаётся отдельным столбцом.
4. Позиции агентов на карте привязываются к значению параметра location через self.location в свойстве начального местоположения.
5. Логика движения грузовика реализована стейтчартом с состояниями: у дистрибьютора, движение к ритейлеру, движение к ближайшему дистрибьютору.
6. Переход к ритейлеру происходит при получении сообщения order, переходы обратно — автоматически по прибытии агента.
7. Метод moveTo() обеспечивает перемещение по дорогам карты, а getNearestAgent() — выбор ближайшего дистрибьютора.
8. Заказы генерируются событием внутри ритейлера с интенсивностью 5 заказов в день и случайным количеством товара от 5 до 20.
9. Для выбора исполнителя используется фильтрация всех грузовиков по состоянию atDistributor, что исключает занятых агентов.
10. Код фильтрации применяет Java Stream API и лямбда выражение, проверяющее состояние через inState().
11. При отсутствии свободных грузовиков заказ в базовой версии теряется — требуется дополнительная логика очереди.
12. Модель демонстрирует динамическое распределение транспорта без жёсткой привязки к дистрибьюторам и может масштабироваться для сложных цепей поставок.
1. Как в AnyLogic создать популяцию агентов на основе данных из Excel файла?
2. Почему для корректного отображения географических названий из Excel на карте необходимо устанавливать флажок «Обновлять данные при запуске модели»?
3. Каким образом задаётся количество создаваемых грузовиков, если оно различается для разных дистрибьюторов?
4. Для чего в свойствах начального местоположения агента используется выражение self.location?
5. Какие три состояния образуют стейтчарт грузовика и какова роль каждого из них?
6. Чем различаются переходы по получению сообщения и по прибытию агента в стейтчарте?
7. Какой метод отвечает за движение агента к заданному объекту на GIS карте?
8. Почему в коде генерации заказа применяется uniform_discr, а не uniform?
9. Зачем при поиске свободного грузовика вводится фильтрация t -> t.inState(atDistributor)?
10. Что произойдёт с заказом, если на момент генерации все грузовики заняты?
11. Каким образом грузовик узнаёт, к какому ритейлеру везти заказ?
12. Как модель обеспечивает, что после разгрузки грузовик направится именно к ближайшему, а не к исходному дистрибьютору?