AnyLogic
Expand
Font size

Saving and restoring model snapshot

In AnyLogic Professional Edition you are able to save the full state of a model (the snapshot) during runtime to a file, restore it at a later time and continue running simulation from the same point.

This feature may be useful in several cases:

  • Resilience: when a simulation takes very long time to complete, it may make sense to save its state periodically so that you do not have to start everything from scratch when e.g. the computer crashes.
  • Skipping warm-up period: if you plan to run several scenarios with a simulation that differ only after the model warms up, you may run the model up to the end of its warm-up period only once, save the state, and then reload it for every scenario.
  • Running distributed simulations: many parallel/distributed simulation frameworks require the ability to roll-back the model to some previous state (checkpoint). This may be needed to synchronize the clocks of concurrent simulations when one of them happens to "run too far".
  • Any other case when you need to refer to a particular state of the model without running the simulation from the initial state.

AnyLogic model snapshot implementation is based on Java serialization mechanism.

Saving a model snapshot

To save a snapshot of a model

  1. First, check that your model is serializable, i.e. states of all model elements can be saved, and therefore all the information about the current state of the model can be saved in a model snapshot file. To check this, select the model item (e.g. Projects view, and then select Tools > Check for Snapshot Compatibility from the main menu. If there are some unserializable elements in your model, fix this, and then continue.

  2. Snapshots can be saved and restored only when the model execution is paused. Run the model and then pause the execution by clicking the  Pause control.
  3. Now you will need the snapshot management commands. The snapshot commands are located in the developer panel. To open the developer panel click the rightmost  Toggle Developer panel control, located in the model window.
  4. By default, the snapshot commands are hidden. To see them, click the Icon: Show/Hide Show/hide snapshot options control in the topmost line of the developer panel.
  5. Click the  Save snapshot toolbar button.
  6. AnyLogic will save the model state to your default downloads location. The snapshot file name will have the following format:
    <model name> - <experiment name> - yyyy-mm-dd hh-mm.als
    You can now continue the simulation, stop, close it, or even shut down your computer.

Restoring a saved model snapshot

To restore a snapshot of a model

  1. To restore the saved model state you first need to run the same experiment (you do not need to actually run the simulation, it is sufficient just to launch the experiment).
  2. Now you will need snapshot management commands. Snapshot commands are located in the developer panel. To open the developer panel, in the model window, click the rightmost control  Toggle Developer panel.
  3. By default, the snapshot commands are hidden. To see them, click the Icon: Show/Hide Show/hide snapshot options control in the topmost line of the developer panel.
  4. Click the  Load snapshot toolbar button.
  5. The Open model snapshot dialog box will open.
  6. Choose the snapshot file you want to restore and click the Open button when finished.
  7. The model state saved in that file will be restored and the model will be put in the paused state.

Saving and restoring model snapshots via API

Saving / restoring model snapshot from all experiments except for the custom experiment

You can save and restore model snapshots using API functions which both take one parameter – the name of the snapshot file. Call them as follows:

Function Description
getExperimentHost().saveSnapshot(String fileName) Pauses experiment if it is currently running, saves snapshot and then resumes experiment if it was running. On any error throws nothing. If you need custom error processing, please use another function notation (see below).

fileName - the name of the snapshot file
getExperimentHost().saveSnapshot(String fileName, java.lang.Runnable successfulCallback, java.util.function.Consumer<java.lang.throwable> errorCallback) Pauses experiment if it is currently running, saves snapshot and then resumes experiment if it was running.

fileName - the name of the snapshot file
successfulCallback - called when the snapshot is successfully saved
errorCallback - called in case of any error

Usage example. If the snapshot was successfully saved, we print the "Saved!" text to the console. In case of an error, we print the "Error!" text and the error stack trace to the console.
getExperimentHost().saveSnapshot(
        "file.als",
        () -> traceln( "Saved!" ),
        e -> { traceln( "Error!" );
        e.printStackTrace();
        }
        );
getExperimentHost().loadSnapshot(String fileName) Stops experiment and loads snapshot (in its "not running" state), doesn’t resume simulation of loaded snapshot. On any error throws nothing, silently rollbacks to the current experiment and resumes it if it was running. If you need custom error processing, please use another function notation (see below). When snapshot is loaded, presentation forgets everything about the model which was running before (including the engine, experiment, and agents), therefore, it is recommended not to keep references to model objects after this function call.

fileName - the name of the snapshot file
getExperimentHost().loadSnapshot(String fileName, java.lang.Runnable successfulCallback, java.util.function.Consumer<java.lang.throwable> errorCallback) Stops experiment and loads snapshot (in its 'not running' state), doesn't resume simulation of loaded snapshot. On any error throws nothing, silently rollbacks to the current experiment and resumes it if it was running. When snapshot is loaded, presentation forgets everything about the model which was running before (including the engine, experiment, and agents), therefore, it is recommended not to keep references to model objects after this function call.

fileName - the name of the snapshot file
successfulCallback - called when the snapshot is successfully loaded
errorCallback - called in case of any error

Usage example. If the snapshot was successfully loaded, we print the "Loaded!" text to the console, set the window to display the top-level agent's presentation and launch the model. In case of an error, we print the "Error!" text and the error stack trace to the console.
getExperimentHost().loadSnapshot(
        "file.als",
        () -> {
        traceln("Loaded!");
        getExperimentHost().setPresentable(
        getExperiment().getEngine().getRoot() );
        getExperiment().run();
        },
        e -> {
        traceln("Error!");
        e.printStackTrace();
        }
        );

Saving and restoring model snapshot from custom experiment

You can also save and restore model snapshots from custom experiment code using the following functions of Engine:

Function Description
saveRootObjectSnapshot(String snapshotFileName) Saves top-level agent and current state of the simulation engine into a specified snapshot file. This function shouldn't be called from the model-execution thread (except calling from custom experiments where the model is executed using runFast() method). Also, this method shouldn't be called when this engine is in RUNNING state.

snapshotFileName - the absolute path to the snapshot file
The created snapshot file may only be used in loadRootObjectFromSnapshot() methods, i.e. it isn't compatible with any experiments with animation (i.e. any other type of AnyLogic experiment).
Agent loadRootObjectSnapshot(String snapshotFileName) Loads and sets top-level agent from the given snapshot with caching contents of snapshot file (for performance speed-up). This function supports loading from snapshots created with any experiment with animation (i.e. not custom) and from snapshots saved using saveRootObjectSnapshot(String) method.

snapshotFileName - the absolute path to the snapshot file
Agent loadRootObjectSnapshot(String snapshotFileName, boolean cacheSnapshot) Loads and sets top-level agent from the given snapshot with optional caching contents of snapshot file (for performance speed-up). This method supports loading from snapshots created with any experiment with animation (i.e. not custom) and from snapshots saved using saveRootObjectSnapshot(String) method.

snapshotFileName - the absolute path to the snapshot file
cacheSnapshot - if true, snapshot file will be cached for performance speed-up

Examples:

When you create a custom experiment, the experiment has default auto-generated code (see the custom experiment's properties, Code section) containing this line of code: Engine engine = createEngine();

This code creates engine and stores it in the local variable engine. Use it to call the functions mentioned above. To call the functions, write the following code:

engine.saveRootObjectSnapshot("C:\Model\Model.als");

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

Or, instead of typing the code, you can automatically insert the load snapshot function call while creating a custom experiment, by selecting the checkbox Load top-level agent from snapshot on the second page of the New Experiment wizard.

Loading a top-level agent from snapshot into an experiment

There is one more use case of model snapshots. You can load a top-level agent from a snapshot into an experiment. It can be any experiment - possibly of even another type than the one that was launched when you have saved the model snapshot. The only self-evident restriction is that the model should be the same.

Loading a top-level agent from a snapshot into an experiment, you load the saved state of a model from the snapshot file. The experiment will be started from the time when the model state was saved.

Unlike restoring snapshots, when loading a top-level agent from a snapshot, experiment settings are not taken from the saved snapshot.

To load a top-level agent from a snapshot into an experiment

  1. In the Projects view, select the required experiment.
  2. Open the Advanced section of the experiment's Properties.
  3. Select the Load root agent from snapshot checkbox.
  4. Choose the snapshot file, which top-level agent's state you want to load into the experiment. Type the full path to the snapshot file in the edit box to the right of the checkbox. You can browse to the required file using the Browse button.

How to ensure your model is serializable

For 99% of models you do not have to do anything to enable their serialization, i.e. saving their state at runtime: all AnyLogic objects are serializable, including all standard library objects. However, there are things you should be aware of.

As you know, AnyLogic allows you to use arbitrary Java classes in your model, namely in:

  • Parameters
  • Variables
  • Collections
  • User's Java classes
  • Additional class code

If you wish all the objects to be saved and restored, you have to either use only serializable classes, or define custom serialization, or exclude them from the snapshot at all.

In the properties of Parameter, Variable and Collection you will find the checkbox Save in Snapshot that is checked by default. If you leave that checkbox checked AnyLogic will take care of serialization, but the type of parameter, variable or a collection element should then be one of:

  • Java primitive type (int, double, etc.)
  • Array of primitive type (int[], double[], boolean[][], etc.)
  • A class implementing java.io.Serializable interface (like Integer, Date, HyperArray, etc.)
  • Array of serializable objects (Integer[], Date[] etc.)

By unchecking that checkbox you tell AnyLogic to exclude the corresponding object from standard serialization. You may then specify custom serialization for it by defining two functions in the agent's Additional class code:

  • The function void writeCustomData(ObjectOutputStream os) throws java.io.IOException. In the body of the function use output stream to write values of java primitive types and objects (which should implement java.io.Serializable).
  • The function void readCustomData(ObjectInputStream is) throws java.io.IOException, ClassNotFoundException. In the body of the function use input stream to read values of java primitive types and objects (which should be serializable) in the same order they are saved in writeCustomData function.
If you are defining custom serialization for a field defined in the Additional class code (i.e. not graphically and for which there is no Save in snapshot checkbox that you can uncheck) you need to declare them as transient by adding this word before the field name.

The wizard for creating a new Java class within AnyLogic IDE now has an option Enable saving this class in model snapshots. If it is checked, the new class will be generated as implementing java.io.Serializable interface and contain the code line.

private static final long serialVersionUID = <some_value> ;

where <some_value> is a kind of class fingerprint which normally should be changed when class structure changes (e.g. when some fields are added to (removed from) the class). Please note that in the models created earlier the Java classes were not implementing that interface by default and you may need to add the corresponding code manually. Same applies to the inner classes if there are any.

The Java classes used as statechart transition messages MUST be serializable.

Also, there is one common recommendation for making advanced structures Serializable. All user-defined cross-object links except links to the embedded objects must have custom serialization (and Save in snapshot checkbox should be unchecked for the corresponding model elements). An example of one-level-links is field "next" of some object, which stores reference to the object of the same class and thus also having field "next" with reference to third object and so on. Link to owner is link from one object located inside another object, to its parent.

Some classes require additional custom deserialization code for their instances, if any are created manually (i.e. in the code) and stored in the serializable fields (e.g. in collections or variables) of model objects. These classes are:

  • Agent, AgentCollection and subclasses
  • Agent, Environment
  • Database, TextFile
  • Statechart, Transition classes, Event classes
  • Port
  • Shapes not present on the presentation or icon of agent
Custom deserialization code is automatically generated for all elements added from the Palette in the AnyLogic graphical editor.

Java classes which need custom serialization, i.e. require special handling during the serialization and deserialization process must implement special functions with these exact signatures:

private void writeObject(java.io.ObjectOutputStream out)

throws IOException

private void readObject(java.io.ObjectInputStream in)

throws IOException, ClassNotFoundException;

The following description is borrowed from API Javadoc of standard Java interface java.io.Serializable:

  • The writeObject function is responsible for writing the state of the object for its particular class so that the corresponding readObject function can restore it. The default mechanism for saving the Object's fields can be invoked by calling out.defaultWriteObject. The function does not need to concern itself with the state belonging to its superclasses or subclasses. State is saved by writing the individual fields to the ObjectOutputStream using the writeObject function or by using the functions for primitive data types supported by DataOutput.
  • The readObject function is responsible for reading from the stream and restoring the classes fields. It may call in.defaultReadObject to invoke the default mechanism for restoring the object's non-static and non-transient fields. The defaultReadObject function uses information in the stream to assign the fields of the object saved in the stream with the correspondingly named fields in the current object. This handles the case when the class has evolved to add new fields.

Restrictions

Static elements

The following AnyLogic elements can be declared as static:

Static elements are not saved into a snapshot file.

If you do not alter the value of a static element in your model, the state of the restored model will be exactly the same as the previously saved state. However, if you somehow change the value of a static element (e.g. in agent's On startup code), the modified value won't be saved into a snapshot, and therefore the restored state of the model will differ from the state you have previously saved.

There is one more case to be mentioned with the static constant variable and some other variable inside a model referring to the same Java object. As a result, when you check the constant and the variable for equality, you will get true. However, if you save the model state into a snapshot, the constant and the variable in the restored model will refer to different objects, and they won't be considered as equal anymore, thus the restored model behavior will differ.

The general conclusion is to avoid using static elements if you plan to save/restore your model state using the AnyLogic snapshot feature.

Non-serializable objects

Some Java objects cannot be serialized, for example objects of class BufferedImage. Variables of such types should be excluded from standard snapshot serialization. If you wish to save and restore these objects anyway, you can write some code in the functions writeCustomData and readCustomData. This code should take care of custom serialization and loading of some base content of a given non-serializable object (of course, it is possible to obtain that base content from the object and setup it back in the future).

For example, for the BufferedImage base content could be: width, height, image type parameters (which are used in the constructor) and int[] array of pixel colors (which can be retrieved by calling getRGB function). The code restoring BufferedImage (in the readCustomData function) should create an instance by invoking constructor with above parameters and then call setRGB function to restore the picture.

Open database connections and open files cannot save and restore their states. Therefore after restoring the snapshot the connections and files will be in closed state. This does not mean you cannot continue accessing them. For example, if the model writes to a kind of a log file, it can continue writing there, provided the file is in "append" mode. Connectivity objects will create their connections on first access, as they usually do. But Statement and ResultSet objects (if any are opened) cannot be included in the snapshot (they are not serializable). It is recommended to open ResultSets, access and close them entirely within the code of some function body (or any code section). Storing references to Statement and ResultSet in the variables of agent is not recommended. Also, note that snapshot cannot affect external data sources and in some cases manual database backup and restore may be required.

The optimization engines used by AnyLogic are unable to save and restore their state, therefore you cannot save snapshots of optimization experiments.

How can we improve this article?