С помощью модели Harvest мы хотим проанализировать сложную логику и динамику взаимодействия нескольких объектов, участвующих в уборке урожая. В модели представлено движение объектов в двумерном пространстве
и их взаимодействие в том смысле, что после заполнения емкости комбайн не может продолжать уборку и вынужден стоять, ожидая трактор для разгрузки, после заполнения кузова трактора тот должен ехать для разгрузки к грузовику и т. п. При построении модели должны быть решены вопросы: насколько детально следует в модели отражать движение объектов и процессы загрузки-выгрузки, будут ли они выполняться с постоянной либо с переменной скоростью, учитывать ли фактор случайности. Все эти вопросы должны решаться в зависимости от той цели, которую ставит перед собой разработчик модели.
Целью данной модели является анализ логики управления взаимодействием объектов системы во времени в процессе их функционирования. Поэтому в данной модели все процессы мы будем считать детерминированными, скорости передвижения и скорости заполнения емкостей собранным урожаем будем считать постоянными, а передвижение — равномерным, временами разворота движущейся техники пренебрегаем — не это важно для нас с точки зрения цели исследования. Именно поэтому мы абстрагируемся от указанных ранее деталей, считая их несущественными. В действительности, все эти детали при необходимости могут быть потом добавлены в модель.
8.2.4. Представление процесса
сбора урожая
Интересным является вопрос о том, как представить в модели сам сбор урожая. На поле анимации в случайном порядке разбросаны зеленые точки, имитирующие урожай. Самым глупым вариантом моделирования сбора урожая была бы реализация анализа того, дошел ли комбайн до очередной точки, и если дошел, то активизировать помещение некоторой части урожая в бункер комбайна и исчезновение этой зеленой точки из окна анимации.
В данной модели принята гипотеза о равномернсти урожая по площади поля, поэтому скорость заполнения бункера комбайна в модели является постоянной при движении комбайна по полю с постоянной скоростью. Что касается анимации, то с положением комбайна на поле связана полоса (прямоугольник), высота которого увеличивается при движении комбайна, накладываясь при этом поверх прямоугольника с зелеными точками урожая. Хотя здесь не учитываются второстепенные детали реального сбора урожая, важно то, что существенные характеристики поведения объектов и их взаимодействия отражены в модели.
По ходу объяснения этой модели далее будут прояснены и другие вопросы абстрагирования.
8.2.5. Выделение общей логики
поведения объектов
При создании каждой модели следует стремиться сделать ее структуру простой и ясной. Структура данной модели очевидна: три объекта представляют
сельскохозяйственную технику (комбайн, трактор и грузовик), один объект — бункер (Bin), и еще один — корневой активный объект Main, представляет всю модель. Очевидно, что бункер также должен быть представлен активным объектом. Хотя он и не движется, бункер тоже имеет активность (он заполняется, когда в него разгружается грузовик).
Несмотря на различные алгоритмы функционирования наших объектов, в них имеется общая логика. Во-первых, комбайн, трактор и грузовик движутся. Во-вторых, у всех активных объектов (кроме корневого) есть логика заполнения емкостей. Один из главных интересных моментов данной модели — то, как эти две общие функции выделены в отдельные объекты, как они реализованы и как используются. Настоящие проблемы также связаны с вопросами абстрагирования.
Понятно, что бессмысленно отражать в модели каждую деталь реального процесса уборки урожая. Для моделирования достаточным является отражение в модели лишь существенных деталей внешнего поведения объектов. Это так называемый операционалистский подход к построению моделей. Опера-ционалист скажет, что модель отражает реальный объект, если она ведет себя так же, как реальный объект. В [РП05] Роджер Пенроуз с этой точки зрения анализирует вопрос "Может ли компьютер мыслить?". Компьютер мыслит, если он ведет себя так же, как и человек в момент раздумий. При этом от компьютера не требуется, чтобы он расхаживал по комнате или морщил лоб подобно тому, как мог бы вести себя размышляющий человек, еще менее важно, чтобы компьютер был внешне похож на человека. Это несущественные детали, от которых в данном случае можно абстрагироваться. То, что нас действительно интересует при моделировании разумного поведения — способность компьютера выдавать схожие с человеческими отве-ты на любой вопрос.
Представление непрерывных процессов дискретными. Активный объект Двигатель (Mover)
В модели Harvest непрерывные процессы движения объектов и изменения емкостей представлены не непрерывными, а чисто дискретными процессами. Однако с точки зрения внешних объектов эти процессы ведут себя как непрерывные, например выдавая по запросу в л'юбой момент времени корректные значения координат, что абсолютно приемлемо для моделирования. Каждая из трех машин (комбайн, трактор и грузовик) движется на плоскости. Движение это происходит по прямой, равномерно с постоянной скоростью. Для движения обычно известны начальная и конечная точка и скорость. Одним из самых простых вариантов моделирования такого движения является моделирование его как непрерывного процесса, а именно, опреде-ление текущих координат, начальной скорости и (дифференциальных) уравнений, задающих этот процесс — движение объекта.
Этот подход, однако, не всегда удобен. Он требует использования численных методов решения уравнений и проверки условия остановки объекта на каждом шаге по времени с маленьким интервалом — фактически, имитацию непрерывного процесса с использованием численных методов. При большом числе движущихся объектов это может существенно замедлить процесс имитационного моделирования.
В данной модели непрерывный процесс движения моделируется как дискретный процесс. Поскольку моделируемое движение — равномерное прямолинейное, то при известной скорости и начальных координатах можнс вычислить время, когда объект прибудет в точку назначения. Иными словами, для дифференциального уравнения движения мы можем найти решение и использовать его. Именно на момент времени прибытия объекта в точку назначения можно взвести таймер. Событие срабатывания таймера сообщит о том, что процесс движения закончился. Если нам нужны промежуточные координаты объекта, то их легко получить в любой момент времени по остатку времени движения, скорости объекта и координате конечной точки его движения. Так непрерывный процесс представляется дискретным.
Рассмотрим, как реализован непрерывный процесс движения в нашей модели. В модели введен новый класс — абстрактный активный объект Mover
ИМеЮЩИЙ ТОЛЬКО СВОЙСТВО "двигаться от точки А к точке В с постоянной
скоростью". От объекта Mover нужно только, чтобы он по запросу выдавал промежуточные координаты объекта и по прибытии в точку в известил окружение, что конечная точка достигнута. В некоторых случаях это движение может быть прервано поступившим извне событием.
Реализация объекта Mover очень проста. Он имеет в качестве параметров скорость velocity и начальные координаты (XInitial и YInitial), а также динамический параметр onArrival — код Java (фактически, набор операторов), который должен выполняться при достижении объектом конечной точки. Сам активный объект содержит только таймер, который запускается и останавливается методами (функциями) этого класса. Если таймер запущен (т. е. он активен), это свидетельствует о том, что объект движется Окончание движения определяется по срабатыванию таймера (тем самым фиксируется прибытие в точку назначения). При этом выполняется код. который является значением параметра onArrival.
Таким образом, непрерывный процесс — движение объекта — представлен виртуально, максимально экономным образом. Никакие переменные при этом не изменяются ни непрерывно во времени, ни даже дискретно с постоянным шагом, что можно часто встретить в попытках реализовать такие процессы с меньшими затратами. Движется только модельное время. В случае, когда нужно моделировать большое число движущихся объектов, такая реализация имеет бесспорные преимущества.
Защищенными переменными класса Mover являются координаты х и у. а также скорости по осям vx и vy. Координаты х и у, при активном таймере
хранят координаты точки назначения, при пассивном таймере в них хранятся текущие координаты объекта. Переменная Angle используется для анимации — объекты при движении должны поворачиваться по направлению движения. Все эти переменные определены в окне Код активного объекта.
Методы этого класса xnow, ynow, stop и другие определены как алгоритмические функции. Эти методы дают возможность работы с движущимся объектом так, чтобы они создавали иллюзию того, что объект действительно движется, хотя он только имитирует движение. Рассмотрим некоторые из этих методов.
Метод xnow выдает текущую координату х объекта:
lf(timer.isActive())
return X - timer.getRest () * Vx; return X;
Если таймер активен, то внутренняя переменная х хранит Х-координату конечной точки движения объекта. С помощью функции getRest о таймера можно получить остаток интервала времени, на которое таймер был запулен. По конечному значению координаты х, остатку времени движения и скорости Vx по этой координате, метод xnow вычисляет текущее значение координаты х. Таким образом, мы можем получить значение текущей координаты объекта, как если бы этот объект реально двигался и координата изменялась бы непрерывно, в то время как никаких изменений переменных нет. Метод ynow построен точно так же.
Метод stop — останов объекта до того, как он достиг конечной точки, если он двигался:
i£(timer.isActive()) { X = xnow(); Y = ynow(); timer.reset();
Если таймер не активен, то это означает, что объект стоит. Если таймер активен, то его нужно сбросить, предварительно подсчитав его текущие (но-зые) координаты.
Метод jumpto(x, у) — перемещение в точку с координатами (х, у):,
stop(); X = х; Y = у;
Объект останавливается, после чего его текущие координаты изменяются. Метод moveto(x, у) — переместить объект из его текущего положения в
точку с координатами (х, у). stop();
double dx = x - X;
double dy = у - Y;
X = x;
Y = y;
double dist = sqrt(dx*dx + dy*dy);
if(dist == 0) {
onArrival();
return; }
Vx = Velocity * dx / dist; Vy = Velocity * dy / dist; Angle = atan2(Vx, -Vy); double dt = dx!= 0? dx/Vx: dy/Vy; timer.restart(dt);
Объект останавливается, если он двигался. Зная его текущие координаты x и y и координаты точки назначения х и у, можно подсчитать расстояние между этими точками и скорости по каждой координате, а также вычислить время движения. Это как раз то время, на которое нужно взвести таймер Если расстояние 0, то точка назначения достигнута, поэтому сразу вызывается метод onArrival. Переменная Angle определяет угол, на который нужно повернуть изображение объекта в анимации по направлению движения.
Заметьте, что в модели для активного объекта Mover определена своя специальная иконка, которая будет появляться в поле того активного объекта, в который будет включен экземпляр активного объекта Mover.
Активный объект Stock (Запас)
Каждая из трех машин может загружаться и разгружаться, может загружаться и бункер. С постоянной скоростью происходит как загрузка, так и разгрузка. Комбайн может одновременно и загружаться, убирая урожай, и разгружаться в кузов трактора. Реализацию непрерывных процессов заполнения и освобождения емкости можно построить так же, как реализацию движения, с помощью дискретных процессов: никакие переменные не меняются непрерывно во времени, изменяться будет только модельное время, а вместе с ним и время до момента окончания процесса разгрузки-загрузки, значение которого хранится в таймере.
В данной модели эта функция тоже реализована как абстракция — отдельный класс активного объекта. Класс stock реализует описанную активность, он несколько проще класса Mover, поскольку здесь только один изменяющийся параметр — объем (в отличие от двух координат при движении на плоскости) и всегда известны границы — максимальное (полный объем, если идет загрузка) и минимальное (пустая емкость, если идет разгрузка) значения
объема материала в конце процесса. Усложняет этот объект только то, что в общем случае (у комбайна) могут одновременно происходить оба процесса, как загрузка, так и разгрузка.
Таймер этого класса моделирует непрерывней процесс. Если он активен, то процесс идет, и значение таймера определяет время окончания процесса. В окне Код класса stock определены защищенные переменные этого класса: in и out — интенсивности загрузки и разгрузки (они обе в общем случае могут быть отличны от нуля), а также вспомогательная переменная v, которая хранит текущее значение заполненного объема при пассивном таймере. Параметры capacity, inFlow, outFlow определяют максимальный объем емкости, скорости загрузки и разгрузки материала в емкость. Если in>out, то происходит загрузка до объема capacity, и таймер хранит время, когда загрузка закончится. Если in<out, то происходит разгрузка емкости до 0, и таймер также хранит время окончания этой операции. Очевидно, что в любой момент текущее значение объема загруженного материала в любом экземпляре этого активного объекта должно находиться в пределах [0, capacity]. Если объем материала выходит из этих границ, то это свиде-тельствует об ошибке в модели. Параметр vinitial определяет начальный объем материала, а динамические параметры onFuii и onEmpty типа code определяют в каждом экземпляре этого активного объекта те действия, которые должны быть выполнены при достижении соответствующих порого-вых объемов.
Методы, определенные для этого активного объекта, также позволяют соз-дать иллюзию того, что течет непрерывный процесс, хотя никакие перемен-ные здесь не изменяются непрерывно. Метод vnow по значениям интенсив-ности загрузки и интенсивности разгрузки и оставшемуся значению таймера выдает текущую величину заполненного объема. Метод setv устанавливает тот объем, до которого должны выполниться загрузка/разгрузка, и запускает процесс. Этот метод в данной модели не используется, он определен в ак-тивном объекте stock для общности.
Запуск процессов загрузки/разгрузки в этом объекте осуществляется так же, если изменения параметров выполняются вызовом методов set_capacity, set_inFlow или set_OutFlow. При вызове этих методов автоматически вызываются соответственно методы onChange_Capacity, onChange_InFlow И onChange_outFlow (см. разд. 7.7). Эти методы определены в активном объек-те stock. Все они вызывают одну и ту же функцию onchange, которая запускает процесс с новыми значениями параметров.
Для активного объекта stock также определена своя специальная иконка.
Активный объект Truck (Грузовик)
Грузовик является самым простым объектом из трех машин в этой модели. Запустите модель и проанализируйте поведение грузовика. Как можно ви-
деть на анимации (рис. 8.4), грузовик вначале стоит на парковке в поле. Если к нему подходит трактор, то грузовик начинает нагружаться. Если он нагружен полностью, он отправляется на разгрузку в бункер. Кроме того, грузовик может направляться к бункеру или направляться от бункера в поле по команде от кнопки.
На рис. 8.5 показана структура и параметры активного объекта Truck. Его параметрами являются его скорость (velocity), емкость кузова (capacity), скорость, с которой он может разгружаться (unloadingRate) и две области, фиксирующие два его возможных положения, — Парковка (parking) и Бункер (Bin). Эти области будут заданы графическими объектами на анимации, потому тип этих параметров выбран shapeBase — базовый класс для всех графических объектов AnyLogic. В описываемой модели будут использованы только методы getx и getY этого класса, которые возвращают соответствующие координаты.
В структуру активного объекта Truck входят стейтчарт movecontrol, управляющий движением объекта, и переменная main, которая установлена как ссылка на объект-владелец экземпляров активного объекта Truck. Поскольку грузовик может и двигаться, и нагружаться/разгружаться, то этот активный объект также включает по одному экземпляру активных объектов Mover (с именем mover) и stock (с именем stock). Для объекта mover установлены следующие параметры — рис. 8.6.
Скорость движения, передаваемая объекту mover, — это скорость самого
грузовика, координаты, определяющие начальное положение грузовика, —
это координаты парковки. По прибытии в конечную точку движения mover
должен выполнить код:
moveControl.fireEvent("ARRIVED");
т. е. известить стейтчарт, управляющий движением грузовика, послав ему
СИГНаЛ "ARRIVED".
На анимации грузовик будет представлен стилизованным изображением (см. объект animation этого активного объекта). В анимации грузовик рас-
положен вертикально, в его начальном положении он развернут на 90 градусов по часовой стрелке. Именно это значение установлено для параметра
Anglelnitial.
Объект stock, включенный в активный объект Truck, моделирует процессы загрузки/разгрузки. Значения параметров объекта stock очевидны. По достижении верхнего и нижнего пределов объема кузова при загрузке и разгрузке этот объект информирует стейтчарт, управляющий движением грузо-зика, посылая ему сигналы "full" и "empty".
Функционированием всех активных объектов модели управляют стейтчарты. Переходы в стейтчартах происходят в основном при приеме событий, которые либо инициируются при нажатии кнопок интерактивного управления на анимации модели, либо возникают во включенных в данный активный объект экземплярах объектов move и stock. События могут возникать и при переходах в стейтчартах тех объектов, которые взаимодействуют в модели.
Стейтчарт, управляющий функциями грузовика, представлен на рис. 8.7. Вначале грузовик находится в поле, где он может либо ждать трактор для загрузки, либо загружать из трактора собранный урожай. Сигналы
" START_LOADING" И "FINISH_LOADING", KOTOрые ПОСЫЛаЮТСЯ TpaKTOpOM.
управляют процессом выгрузки из трактора собранного в нем урожая. Два события могут вывести грузовик из этого состояния: прием сигнала "go_to_bin", возникающего при нажатии соответствующей кнопки пользователем, и сигнала "full", который возникает в объекте stock при заполнении емкости. Оба события заставляют грузовик двигаться к бункеру.
Движение к бункеру моделируется следующим образом. При входе в состояние GoingToBin стейтчарта вызывается метод moveto() объекта mover. включенного в активный объект Truck, с указанием координат точки назначения. Эта точка — координаты графического объекта Bin, являющегося параметром активного объекта Truck. Поскольку движение к бункеру не будет ничем прервано, грузовик перейдет из состояния GoingToBin в следующее состояние unloading в тот момент, когда он достигнет конечной точки, о чем он узнает, получив от своего внутреннего объекта mover сигнал "arrived". После получения этого сигнала грузовик должен развернуться > бункера и начать разгрузку. Поворот моделируется присваиванием параметру Angle объекта mover значения -пи/2. При ВХОДе В состояние Unloading
должны быть запущены два процесса: процесс разгрузки грузовика и процесс загрузки бункера. Для этого просто устанавливается одно и то же значение unloadingRate у параметра интенсивности разгрузки в объекте bin грузовика и параметра интенсивности загрузки в объекте bin корневого объекта модели, включающего грузовик: stock.set_OutFlow(UnioadingRate); main.bin.set_InFlow(UnioadingRate);
При этом вызываются соответствующие функции onChange_outFlow() и onChange_inFlow () в этих объектах, запускающие нужные процессы. Выход из данного состояния происходит либо по сигналу "empty", который порождается при прекращении процесса разгрузки грузовика во включенном в него объекте bin, либо по сигналу "go_to_field", порождающемуся при нажатии кнопки в окне анимации. При выходе из состояния параметры соответствующих емкостей inFlow и outFlow устанавливаются в 0, т. е. оба процесса останавливаются: stock.set_OutFlow(0); main.bin.set_InFlow(0);
Активный объект Cart (Трактор)
Объект cart построен полностью аналогично тому, как и активный объект Truck. Он включает экземпляры объектов Mover и stock, а также переменную main, которая примет значение указателя на объект (Main), включающий экземпляр трактора. Стейтчарт активного объекта cart отражает динамику его "жизни" (рис. 8.8).
Вначале трактор стоит на парковке, ожидая команды. Команда приходит как сигнал "go_to_combain", который порождается при нажатии пользователем
КНОПКИ ПОЛЯ аНИМаЦИИ. При ВХОДе В СОСТОЯНИе GoingToCombaine ВЫЧИСЛЯЮТСЯ текущие координаты комбайна и запускается процесс движения трактора к точке, левее на 40 пикселов места расположения комбайна:
double х = main.combine.mover.xnow() - 40; double у = main.combine.mover.ynow(); mover.moveto(x, у);
Комбайн может при этом двигаться, и его координаты могут меняться. Вследствие этого трактор придет к месту, в котором комбайн был в момент начала движения. Для того чтобы учесть это, из состояния GoingToCombaine в себя проведен переход, срабатывающий с интервалом единица модельного времени. При каждом повторном входе в состояние GoingToCombaine будет производиться определение текущих координат комбайна и перезапускаться процесс движения трактора к новому месту нахождения комбайна. Этим моделируется коррекция направления движения трактора его водителем, периодически поглядывающим из кабины на поле.
Все остальные действия трактора понятны из анализа его стейтчарта. Анализ модели комбайна оставляем читателю в качестве упражнения.
Анимация
Изображение каждой из участвующих в уборке машин построено с помощью графики AnyLogic (тонкие детали строились при увеличенном масштабе окна анимации). Изображения машин расположены в анимационном окне так, чтобы центр координат находился в центре изображения. Именно к данной точке будут привязаны меняющиеся координаты машин в окне анимации корневого объекта при их движении. Координаты машин для их отображения в процессе анимации получаются как значения, возвращаемые функциями xnow и ynow каждый раз, когда необходимо нарисовать объект в окне анимации. Обращение к этим функциям позволяет получить текущие координаты объектов (повторим, что никакие параметры этих объектов непрерывно не изменяются). Аналогично этому, заполненность емкостей отображается в анимации столбцовыми индикаторами с уровнем заполненности, вычисляемым с помощью функции vnow.
На поле анимации в объекте Main введен зеленый прямоугольник размером 3x3. Именно экземпляры данного реплицированного объекта, хаотично разбросанные по полю, изображают выращенный урожай. Координаты X и У этих объектов объявлены в окне Код анимации в поле Дополнительный код класса как два одномерных массива:
double Xunits[] = new double[2000]; double YunitsU = new double[2000];
Значения этим координатам присваиваются в поле Код инициализации того же окна:
for(int i=0; i<Xunits.length; i++) {
Xunits[i] = uniform! animation.field.getWidth() - 2);
Yunitsfi] = uniform(animation.field.getHeight() - 2); }
С помощью графического элемента группа объектов (pivot) на поле анимации зафиксированы точки места парковки машин и разгрузки грузовика
С такой же осью связаны прямоугольники, моделирующие состояние поля, и все координаты движения по полю отсчитываются относительно центра данного объекта. С координатами комбайна связаны высота и координаты прямоугольных полос, моделирующих уборку поля. Наложение этих прямо-угольников поверх прямоугольника, представляющего "неубранное" поле, синхронно с продвижением комбайна по полю визуально представляет уборку урожая изменением цвета части этого прямоугольника.
Заключение
Как показывают два рассмотренных в этой главе примера, возможность включения фрагментов программного кода на языке Java в модели AnyLogic создает поистине неограниченные возможности для моделирования систем со сложными алгоритмами поведения и взаимодействия объектов.
Часть III
Методологические вопросы использования моделей