AnyLogic
Развернуть
Размер шрифта

Сохранение и восстановление состояния модели

AnyLogic Professional предоставляет возможность сохранения в файл всей информации о текущем состоянии модели во время ее выполнения с возможностью последующего ее восстановления и возобновления моделирования с того самого момента времени, когда было сохранено состояние.

Эта часто требуется для достижения следующих целей:

  • Устойчивость: когда один «прогон» модели выполняется в течение длительного периода времени, может иметь смысл периодически сохранять состояние модели, так, чтобы в случае, например, аварийной остановки («зависания») компьютера не приходилось снова запускать модель с самого начала.
  • Пропуск периода «прогрева» модели: если вы планируете запускать несколько различных сценариев, различие в поведениях которых начинает проявляться только по прошествии определенного времени «прогрева» модели, то вы можете один раз промоделировать поведение модели в течение этого периода, сохранить ее состояние, и затем уже начинать выполнение разных сценариев с этого момента, предварительно загружая ранее сохраненное состояние модели.
  • Распределенный запуск нескольких взаимосвязанных моделей: многие параллельные/распределенные системы принятия решений нуждаются в возможности выполнения «отката» модели назад к какому-то определенному моменту времени (к контрольной точке). Это может понадобиться для синхронизации часов нескольких параллельно выполняющихся моделей, когда одна из них «убегает вперед».
  • Если вам нужно восстановить состояние модели в какой-то определенный момент времени, без необходимости предварительного моделирования предшествующего этому моменту периода времени.

Реализация сохранения и восстановления состояния модели AnyLogic основана на механизме сериализации Java.

Сохранение состояния модели

Чтобы сохранить состояние модели

  1. Для начала убедитесь, что ваша модель поддерживает механизм сериализации, т.е. состояния всех элементов модели можно сохранить, и следовательно, всю информацию о текущем состоянии модели можно сохранить в файл состояния модели. Чтобы проверить свою модель, выберите ее в панели Проекты, а затем выберите Инструменты > Проверить возможность сохранения состояния из главного меню. Если в модели обнаружатся элементы, не поддерживающие сериализацию, исправьте их, прежде чем продолжить.
  2. Состояние модели может быть сохранено только в тот момент, когда выполнение модели приостановлено. Запустите модель и щелкните по кнопке панели управления  Пауза в тот момент, когда вы захотите сохранить состояние модели.
  3. Вам понадобятся команды управления файлами состояний модели. Они расположены в панели разработчика. Откройте панель разработчика в окне модели, щелкнув крайний справа элемент управления  Показать/скрыть панель разработчика.
  4. Необходимые нам элементы управления по умолчанию скрыты. Щелкните по элементу управления  Показать/скрыть действия со снэпшотом в верхней строке панели разработчика, чтобы они появились.

  5. Щелкните по кнопке  Сохранить снэпшот.
  6. AnyLogic сохранит файл состояния модели в директорию, используемую операционной системой для загрузок по умолчанию. Имя файла состояния будет иметь следующий формат:
    <имя модели> - <имя эксперимента> - гггг-мм-дд чч-мм.als
    Вы можете продолжить выполнение модели, либо же завершить его (закрыв окно модели, и, возможно, даже выключив компьютер), с тем, чтобы впоследствии загрузить сохраненное состояние и продолжить моделирование с момента сохранения.

Восстановление сохраненного состояния модели

Чтобы восстановить сохраненное состояние модели

  1. Чтобы загрузить ранее сохраненное состояние модели, вначале нужно запустить тот же эксперимент этой модели, который был запущен при ее сохранении (начинать моделирование необязательно, можно просто запустить эксперимент и произвести загрузку прямо со страницы презентации эксперимента). Если же вы начали моделирование, приостановите его, щелкнув по кнопке панели управления  Приостановить выполнение, прежде чем загружать ранее сохраненное состояние модели.
  2. Необходимые нам элементы управления по умолчанию скрыты. Щелкните по элементу управления  Показать/скрыть действия со снэпшотом в верхней строке панели разработчика, чтобы они появились.

  3. Щелкните по кнопке  Загрузить снэпшот. Откроется диалоговое окно, где вы сможете выбрать файл, из которого вы хотите загрузить состояние модели.
  4. Выберите файл и щелкните по кнопке Открыть.
  5. При этом из файла будет загружено ранее сохраненное состояние модели. Сама модель перейдет в режим приостановленного выполнения (паузы). Обратите внимание, что после запуска в окне модели будет отражаться страница презентации эксперимента. Используйте элементы управления навигации по модели, чтобы перейти к презентации агента нужного вам уровня.

Программное сохранение и восстановление состояния модели

Сохранение / восстановление состояния модели из всех экспериментов, кроме нестандартного

Вы можете сохранять и восстанавливать состояние модели программно с помощью функций программного интерфейса (API), каждой из этих функций в качестве аргумента нужно передать имя файла состояния модели:

Функция Описание
getExperimentHost().saveSnapshot(String fileName) Приостанавливает выполнение эксперимента (если он выполняется в текущий момент), сохраняет состояние модели в указанный файл состояния и продолжает выполнение эксперимента (если на момент вызова функции он выполнялся).При возникновении ошибки не выдает об этом никакой информации. Если вам нужна обработка потенциальных ошибок, используйте другую нотацию функции (см. ниже).

fileName — имя файла состояния модели
getExperimentHost().saveSnapshot(String fileName, java.lang.Runnable successfulCallback, java.util.function.Consumer<java.lang.throwable> errorCallback) Приостанавливает выполнение эксперимента (если он выполняется в текущий момент), сохраняет состояние модели в указанный файл состояния и продолжает выполнение эксперимента (если на момент вызова функции он выполнялся).

fileName — имя файла состояния модели
successfulCallback — вызывается, если состояние модели успешно сохранилось
errorCallback — вызывается в случае возникновения какой-либо ошибки
Пример использования. В случае успешного сохранения выводим в консоль текст "Saved!". В случае какой-либо ошибки выводим в консоль текст "Error!", а также список методов, которые были вызваны до момента, когда произошла ошибка:
getExperimentHost().saveSnapshot(
  "file.als",
  () -> traceln("Saved!"),
  e -> {
    traceln("Error!");
    e.printStackTrace();
  }
);
getExperimentHost().loadSnapshot(StringfileName) Останавливает выполнение эксперимента и загружает состояние из файла состояния (в его «незапущенном» состоянии), не продолжает выполнение загруженного из файла эксперимента.
Когда загружается состояние, презентация забывает все о ранее выполнявшейся модели (в том числе, удаляется вся информация об исполняющем модуле, эксперименте и агентах), поэтому рекомендуется не ссылаться на эти объекты после вызова этой функции.
При возникновении какой бы то ни было ошибки отменяет все выполненные операции, производит откат к ранее выполнявшемуся эксперименту и продолжает его выполнение (если на момент вызова функции он выполнялся). При этом не выдает об этом никакой информации. Если вам нужна обработка потенциальных ошибок, используйте другую нотацию функции (см. ниже).

fileName — имя файла состояния модели
getExperimentHost().loadSnapshot(String fileName, java.lang.Runnable successfulCallback, java.util.function.Consumer<java.lang.throwable> errorCallback) Останавливает выполнение эксперимента и загружает состояние из файла состояния (в его «незапущенном» состоянии), не продолжает выполнение загруженного из файла эксперимента. При возникновении какой бы то ни было ошибки отменяет все выполненные операции, производит откат к ранее выполнявшемуся эксперименту и продолжает его выполнение (если на момент вызова функции он выполнялся). Когда загружается состояние, презентация забывает все о ранее выполнявшейся модели (в том числе, удаляется вся информация об исполняющем модуле, эксперименте и агентах), поэтому рекомендуется не ссылаться на эти объекты после вызова этой функции.

fileName — имя файла состояния модели
successfulCallback — вызывается, если состояние модели успешно загрузилось
errorCallback — вызывается в случае возникновения какой-либо ошибки
Пример использования. В случае успешной загрузки выводим в консоль текст "Saved!", задаем презентацию агента верхнего уровня для отображения в окне модели и запускаем модель. В случае какой-либо ошибки выводим в консоль текст "Error!", а также список методов, которые были вызваны до момента, когда произошла ошибка.
getExperimentHost().loadSnapshot(
  "file.als",
  () -> {
    traceln("Loaded!");
    getExperimentHost().setPresentable( getExperiment().getEngine().getRoot() );
    getExperiment().run();
  },
  e -> {
    traceln("Error!");
    e.printStackTrace();
  }
);

Программное сохранение и восстановление состояния модели из нестандартного эксперимента

Сохранить и восстановить состояния модели из нестандартного эксперимента можно с помощью следующих функций исполняющего модуля:

Функция Описание
saveRootObjectSnapshot(String snapshotFileName) Сохраняет агента верхнего уровня и текущее состояние исполняющего модуля в заданный файл состояния. Эта функция не должна вызываться из потока выполнения модели (кроме нестандартного эксперимента, в котором модель выполняется с помощью функции runFast()). Эту функцию также нельзя вызывать, когда исполняющий модуль находится в состоянии RUNNING.

snapshotFileName — абсолютный путь к файлу состояния
Созданный файл состояния можно использовать только с методами loadRootObjectFromSnapshot(), то есть он не совместим с экспериментами, содержащими анимацию (т.е. с любым другим экспериментом AnyLogic).
Agent loadRootObjectSnapshot(String snapshotFileName) Загружает агента верхнего уровня из указанного файла и кэширует содержимое файла (для увеличения производительности). Эта функция допускает загрузку состояния модели из любых экспериментов с анимацией (т.е. все, кроме нестандартного), а также состояний модели, сохраненных с помощью функции saveRootObjectSnapshot(String).

snapshotFileName — абсолютный путь к файлу состояния
Agent loadRootObjectSnapshot(String snapshotFileName, boolean cacheSnapshot) Загружает агента верхнего уровня из указанного файла и опционально кэширует содержимое файла (для увеличения производительности). Эта функция допускает загрузку состояния модели из любых экспериментов с анимацией (т.е. все, кроме нестандартного), а также состояний модели, сохраненных с помощью функции saveRootObjectSnapshot(String).

snapshotFileName — абсолютный путь к файлу состояния
cacheSnapshot — если true, файл состояния будет кэшироваться для увеличения производительности

Примеры:

После создания, нестандартный эксперимент содержит код, сгенерированный по умолчанию (см. секцию Код свойств нестандартного эксперимента) со следующей строчкой: Engine engine = createEngine();

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

engine.saveRootObjectSnapshot("C:\Model\Model.als");
Main root = (Main)engine.loadRootObjectSnapshot("C:\Model\Model.als");

Или, чтобы не печатать код, вы можете автоматически вставить вызов функции загрузки состояния модели во время создания нестандартного эксперимента, установив флажок напротив Загрузить агента верхнего уровня на второй странице мастера создания Новый эксперимент.

Загрузка агента верхнего уровня эксперимента из файла сохраненного состояния модели

Помимо сохранения и восстановления полного состояния модели, файлы сохраненного состояния могут использоваться и для загрузки в эксперимент агента верхнего уровня.

Загружая агента верхнего уровня эксперимента, вы загружаете сохраненное состояние модели из файла состояния. Эксперимент начнет свою работу с того момента времени, когда было сохранено состояние модели.

Вы можете загружать агента верхнего уровня любого эксперимента (возможна даже загрузка агента не того типа эксперимента, который был запущен во время сохранения состояния). Единственное (и само собой разумеющееся) ограничение - это должен быть файл состояния именно этой модели.

В отличие от восстановления состояния модели, при загрузке агента верхнего уровня не используются сохраненные в файле состояния модели установки эксперимента.

Чтобы загрузить агента верхнего уровня эксперимента из файла сохраненного состояния модели

  1. Выберите требуемый эксперимент в панели Проекты.
  2. Откройте секцию Специфические панели Свойства.
  3. Установите флажок Загрузить агента верхнего уровня из файла сохраненного состояния.
  4. Выберите файл состояния модели, из которого вы хотите загрузить тип агента. Введите полный путь к файлу состояния модели в поле редактирования справа от флажка. Вы можете перейти к требуемому файлу с помощью кнопки Выбрать.

Нестандартные механизмы сериализации

В 99% случаев ваши модели будут поддерживать механизм сериализации (поскольку этот механизм поддерживают все объекты AnyLogic, в том числе и библиотечные объекты), а значит, вы сможете сохранять и восстанавливать состояния таких моделей во время моделирования, не выполняя никаких дополнительных действий. Однако же не исключаются те редкие ситуации, когда вам нужно будет самостоятельно внести в модель небольшие изменения для того, чтобы эта модель полностью поддерживала сериализацию.

Как вы знаете, AnyLogic позволяет использовать в моделях объекты любых Java классов. На такие объекты могут ссылаться:

  • Параметры
  • Простые переменные
  • Коллекции
  • Java классы пользователя
  • Дополнительный код класса

Если вы хотите, чтобы все объекты модели сохраняли и восстанавливали свое состояние, вам нужно либо использовать только сериализуемые классы, либо реализовать свою собственную сериализацию, либо просто исключить эти объекты из сохраняемого состояния.

У параметра, переменной и коллекции есть свойство Сохранять при сохранении состояния модели (этот флажок установлен по умолчанию). Если вы оставите этот флажок установленным, то AnyLogic будет сериализовывать (то есть, сохранять в файле состояния) эти элементы, если тип этих элементов будет одним из следующих:

  • Примитивный тип Java (int, double и т.д.)
  • Массив значений примитивных типов (int[], double[], boolean[] и т.д.)
  • Класс, реализующий интерфейс java.io.Serializable (такой, как Integer, Date, HyperArray и т.д.)
  • Массив сериализуемых объектов (Integer[], Date[] и т.д.)

Сбросив этот флажок, вы говорите AnyLogic, что соответствующий элемент модели не должен сохраняться в файле состояния посредством стандартного механизма сериализации. Вы можете затем задать свой механизм сериализации для таких элементов, объявив две функции в Дополнительном коде типа агентов — владельца этих элементов:

  • Функция void writeCustomData( ObjectOutputStream os ) throws java.io.IOException. Используйте в теле этой функции поток вывода для записи значений примитивных типов и объектов (которые должны реализовывать интерфейс java.io.Serializable).
  • Функция void readCustomData( ObjectInputStream is ) throws java.io.IOException, ClassNotFoundException. Используйте в теле этой функции поток ввода для чтения значений примитивных типов и объектов (которые должны реализовывать интерфейс java.io.Serializable) в том же самом порядке, в котором они были записаны функцией writeCustomData.

Обратите внимание, что если вы используете для какого-то поля нестандартный механизм сериализации, реализованный в поле Дополнительный код класса (т.e. не графически, и у которого нет флажка Сохранять при сохранении состояния модели, который вы можете сбросить), то вам нужно добавить к объявлению этих полей слово transient (перед именем поля).

У мастера создания нового Java класса теперь есть опция Добавить возможность сохранения в файле состояния - реализовать java.io.Serializable. Если этот флажок будет установлен, то новый класс будет реализовывать интерфейс java.io.Serializable и содержать следующую строку кода:

private static final long serialVersionUID = <некоторое значение>;

где значение играет роль идентификатора класса, который нужно изменять при изменении структуры класса (например, при добавлении (или удалении) каких-то полей класса). Обратите внимание, что в созданных ранее моделях Java классы не реализовывали по умолчанию этот интерфейс и вам может понадобиться добавить это поле самостоятельно. То же самое касается и вложенных классов, если таковые имеются в ваших моделях.

Java классы, используемые в качестве классов сообщений в диаграммах состояний, должны быть сериализуемыми.

Все ссылки на других агентов (за исключением ссылок на вложенных агентов) должны иметь нестандартную сериализацию и десериализацию (и у соответствующих элементов модели должны быть сброшены флажки Сохранять при сохранении состояния модели).

Некоторые классы требуют дополнительной нестандартной десериализации для своих объектов, если они были созданы вручную (т.e. в коде) и сохранены в сериализуемых полях (например, в простых переменных или в коллекциях). Такими классами являются:

  • Agent, AgentCollection и подклассы
  • Agent, Environment
  • Database, TextFile
  • Statechart, классы различных переходов Transition, классы различных событий Event
  • Port
  • Фигуры, не присутствующие ни на презентации, ни в значке агента

Обратите внимание, что для всех элементов, добавляемых из палитры, код нестандартной десериализации генерируется автоматически.

Java классы, требующие нестандартной сериализации, т.e. требующие специальной обработки во время процесса сериализации и десериализации должны реализовывать специальные методы со следующими объявлениями (они должны быть точно такими):

private void writeObject(java.io.ObjectOutputStream out)
throws IOException;
private void readObject(java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException;

Следующее описание заимствовано из документации стандартного Java интерфейса java.io.Serializable:

  • Метод writeObject отвечает за запись состояния объекта (такую, при которой соответствующий метод readObject смог бы восстановить записанное этим методом состояние). Механизм, используемый по умолчанию для сохранения полей объекта Object, может быть вызван путем вызова метода out.defaultWriteObject. Этот метод не занимается сохранением информации о полях подклассов и суперклассов. Состояние сохраняется путем записи отдельных полей в поток ObjectOutputStream с помощью метода writeObject или путем использования методов примитивных типов данных, поддерживаемых DataOutput.
  • Метод readObject отвечает зачтение из потока и восстановление значений полей класса. Он может вызывать метод in.defaultReadObject, чтобы с его помощью восстановить все нестатические и не объявленные как transient поля класса. Метод defaultReadObject использует считываемую из потока информацию для присвоения значений полей сохраненного в потоке объекта одноименным полям текущего объекта.

Ограничения

Некоторые объекты Java, например, объекты класса BufferedImage, не могут быть сериализованы. Переменные таких типов должны быть исключены из стандартного механизма сериализации, используемого при сохранении состояния модели. Если же вы все-таки хотите сохранять и восстанавливать состояние таких объектов, то вы можете написать свой собственный код в методах writeCustomData и readCustomData. Этот код должен выполнять нестандартную сериализацию и восстановление основного содержимого несериализуемого объекта.

Например, в случае класса BufferedImage, основным его содержимым являются: высота, ширина, параметры типа изображения (которые используются в конструкторе) и массив int[] цветов пикселей (который может быть получен путем вызова метода getRGB). Код, восстанавливающий объект BufferedImage (в методе readCustomData), должен создавать экземпляр этого класса посредством вызова конструктора с вышеперечисленными параметрами и последующего вызова метода setRGB для восстановления изображения.

Открытые соединения с базами данных и открытые файлы не могут сохранять и восстанавливать свои состояния. Поэтому после восстановления состояния модели файлы и соединения с базами данных будут в закрытом состоянии. Это не означает, что вы не можете продолжать работать с ними. Например, если модель записывает информацию в файл журнала, то вы можете продолжать записывать туда нужные вам данные при условии, что соответствующий текстовый файл находится в режиме «запись с добавлением в конец». Объекты связи с базами данных установят свои соединения при первом доступе к базе данных (как и обычно). Но объекты Statement и ResultSet (если есть таковые) не могут быть включены в сохраняемое состояние модели (они несериализуемы). Рекомендуется открывать объекты ResultSet, работать с ними и закрывать их в теле одной и той же функции (или в коде одного и того же кодового свойства элемента модели). Не рекомендуется хранить ссылки на объекты типа Statement и ResultSet в переменных агента. Также обратите внимание, что файлы состояния не могут влиять на внешние источники данных и в некоторых случаях может потребоваться ручное сохранение и восстановление баз данных.

Используемые AnyLogic оптимизаторы не поддерживают сериализацию, поэтому вы не можете сохранять и восстанавливать состояние оптимизационных экспериментов.

Как мы можем улучшить эту статью?