AnyLogic Cloud API 8.5.0 Documentation

1 Overview

In addition to the standard web UI, Anylogic Cloud offers multiple APIs that you can use to configure and run simulations programmatically within your analytical workflows, query experiment results, build fully customized web interfaces to your models, and do a lot of other things. There are four APIs:

Useful resources:

1.1 Simple YouTube-style embedding of simulation animations

Before we dive into AnyLogic Cloud API, we would like to make sure you know that there is a simple way of embedding Cloud-based animations into any web pages without using any API/coding whatsoever.

Embedding AnyLogic simulation animation

If all you need is just have the AnyLogic Cloud model animation on your HTML page you can embed it just like a YouTube video:

  • Display the model tile (either in the tiles view or in the model
  • Click the <> icon at the bottom right corner of the tile
  • Copy the HTML fragment displayed in the dialog box
  • Paste the fragment into your HTML page where you want the animation to appear:

<head>
    …
</head>
<body>
    …
    <iframe width="1000" height="650" ></iframe>
    …
</body>

That simple embedding is subject to the following restrictions:

  • Only animations of public models can be embedded that way
  • The animation always refers to the same simulation as the public page of the model, i.e.
    • to a simulation experiment of the latest model version will run
    • If the latest version has no simulation experiments, an error will occur
  • Please keep in mind:
    • You will only be able to control the model execution from the default animation toolbar
    • You will not be able to change parameters prior to simulation or save outputs afterwards
    • When the Stop button is pressed, the animation returns to the start page

1.2 Authentication with API key and security considerations

Any usage of Anylogic Cloud API is done on behalf of a particular user. Authentication is don with the help of the API Key a randomly generated personal identifier. This is an example API Key:

"e05a6efa-ea5f-4adf-b090-ae0ca7d16c20"

The API key is available for subscribers and Private Cloud users (in a Private Cloud, every user has his own API key). To obtain the API Key:

  1. Login to AnyLogic Cloud with web UI
  2. Go to your Profile page
  3. Expand the API section

The key is there:

Example AnyLogic API key

Keep the key safe! If the key gets exposed unintentionally, generate a new key and do not forget to change the key in your code!

It is important to know that, once you have used the API key in the JavaScript code of your custom web page and made that page public or opened it to an end user or a client, the key gets exposed to whoever can access the web page (because the JavaScript code can be viewed in the browser console). So potentially, that person would also be able to use the AnyLogic Cloud API under the same account!

Therefore, if that can be an issue, you should do the following:

Create a separate AnyLogic Cloud account for the user or users. (If you are using the Public Cloud, that account should also be a subscriber account.)

  • Generate the API key from that user s account
  • Use that key in the JavaScript code
  • Share the model or models that you want the users to access between your (developer s) account and user s account

That way you have full control of the models that are accessible to particular users and ability to delete the user s account at any time without affecting the models.

2 RESTful HTTP API

The AnyLogic Cloud API is versioned, and the root address contains the version number, for example this is the root address for AnyLogic Cloud subscribers, API version 8.5.0: https://cloud.anylogic.com/api/open/8.5.0. The same applies to the Private Cloud, then the address would be <your private cloud host address>/api/open/8.5.0.

2.1 End points

Please note that you should provide Authorization: <API key> header in every HTTP request, see API Key. In AnyLogic HTTP API, requests have no parameters, some requests should provide data objects in the request body. The data objects are described in the next section.

GET /models - returns the array of all Models of the user (the API key owner), i.e. the models where the user has either developer or user access rights.

GET /models/<model id> - returns the Model with a given id.

GET /models/name/<model name> - returns the Model with a given name.

GET /models/<model id>/versions/number/<version number> - returns the Version with a given number (version numbering starts with 1).

GET /models/<model id>/versions/<version id> - returns the Version with a given id.

GET /versions/<version id>/experiments returns the array of Experiments created for a given model version.

POST /versions/<version id>/runs, body: Run Request requests to perform a simulation run with the inputs contained in the Run Request. This will result in a real new simulation only in case such run has not yet been performed. Returns Experiment Run, but does not wait for simulation to complete and therefore does not return the outputs.

POST /versions/<version id>/runs/stop, body: Run Request stops execution of a model run provided in the request body. Does not return anything.

POST /versions/<version id>/run, body: Run Request returns Experiment Run containing the status of the run. The intended use is to query the run state.

POST /versions/<version id>/results/<run id>, body: blank Outputs returns the Outputs of a completed run with a given id according to the outputs template provided (so that you can control which data you will receive). If the run has not been completed, returns 404.

POST /versions/<version id>/results, body: Experiment Result Request the Outputs of a completed run with a given id according to the outputs template provided. In contrast with the pervious end point, this one does not require <run id>.

GET /versions/<version id>/runs/<run id>/progress returns Experiment Run State containing the status and details of the run (which may be already completed or not).

POST /versions/<version id>/runs/animation, body: Run Request starts animated run of the model. Waits for the animation to start, and returns Animation SVG Run Info containing, among other things, the host address of the node where the animated run has been created.

There are more end points for communication with the animated run (like changing the speed, pausing, etc.) but we are not describing them here because they only make sense in the browser environment and there is the corresponding JavaScript API.

2.2 Data objects

The data structure of AnyLogic Cloud can roughly be represented by the picture below. Using the API Key obtained from the page of a user, you can access the models listed in the user s My Models page. Each Model can have multiple versions, and the Version is what you can perform experiments with. AnyLogic Cloud keeps the inputs and outputs of all experiments ever performed with each model version.

Data objects of AnyLogic Cloud

Model

Model is a simple object with a unique id, name, description, array of version ids, and a public/not public flag:

{
    id: "f7771cd8-8d55-486a-b255-10459a349093",
    name: "Flocks of Boids",
    description: "Original Boids, developed by Craig Reynolds, is an artificial life program ",
    modelVersions: [
        "7c702819-b413-417b-aa61-60becd029e3c",
        "de699d41-1f0c-497d-b544-413685f82d2b",
        "b845d4f9-972c-45c7-ae6a-7a762ab81c03"
    ],
    published: false
}

Version

Version contains full information about the model inputs, outputs, and dashboard configuration in its experimentTemplate field, for example:

{
    id: "def089c4-96a3-40f2-aefb-de1af471fe2f",
    version: 6,
    experimentTemplate: {
        inputs: [
            {name: "{STOP_MODE}", type: "STRING", units: null, value: "STOP_MODE_AT_TIME"},
            {name: "{START_TIME}", type: "DOUBLE", units: "SECOND", value: "0"},
            {name: "{STOP_TIME}", type: "DOUBLE", units: "SECOND", value: "1000000"},
            {name: "{START_DATE}", type: "DATE_TIME", units: null, value: "2019-05-15T00:00"},
            {name: "{STOP_DATE}", type: "DATE_TIME", units: null, value: "2019-05-26T13:46:40"},
            {name: "{MAX_MEMORY_MB}", type: "INTEGER", units: null, value: "512"},
            {name: "Arrival rate", type: "DOUBLE", units: null, value: "1"},
            {name: "Mean service time", type: "DOUBLE", units: null, value: "2"},
            {name: "Server capacity", type: "INTEGER", units: null, value: "3"},
            {name: "Check failure probability", type: "DOUBLE", units: null, value: "0.2"}
        ],
        outputs: [
            {name: "Queue size stats", type: "STATISTICS_CONTINUOUS", units: null, value: null},
            {name: "Total time in system|Total time in system", type: "HISTOGRAM_DATA", units: null, value: null},
            {name: "Utilization|Server utilization", type: "DOUBLE", units: null, value: null},
            {name: "Mean queue size|Mean queue size", type: "DOUBLE", units: null, value: null}
        ],
        dashboard: { }

    }
}

Please note that the inputs array contains the system inputs of the model (the ones in curly braces) such as the {STOP_TIME} or {MAX_MEMORY_MB} as well the user-defined inputs. The value field contains the default value of the input and is always a string regardless of the input type. The outputs part of the object is not the actual outputs of some simulation run (those are described in the Outputs section), but a list of outputs that are available for that particular model version, therefore the value field is always null. The dashboard configuration is irrelevant from the API user viewpoint, so we will skip its structure.

Experiment

Experiment contains the input settings of an experiment saved in the Anylogic Cloud UI. It can be used, for example, to set the inputs of a run invoked via the AnyLogic Cloud API by calling the function createInputsFromExperiment() of the CloudClient object. It has the following structure:

{
    uuid: "f3f441f3-8117-4423-9b1c-75b4680c715b",
    modelVersion: "def089c4-96a3-40f2-aefb-de1af471fe2f",
    name: "Server Capacity Variation",
    type: "PARAMETER_VARIATION",
    inputs: [
        {name: "Arrival rate", type: "DOUBLE", units: null, value: "1"},
        {name: "Check failure probability", type: "DOUBLE", units: null, value: "0.2"},
        {name: "Mean service time", type: "DOUBLE", units: null, value: "2"},
        {name: "Server capacity", type: "FIXED_RANGE_INTEGER", units: null, value: "{"min":3,"max":7,"step":1}"},
        {name: "{MAX_MEMORY_MB}", type: "INTEGER", units: null, value: "512"},
        {name: "{RANDOM_SEED}", type: "LONG", units: null, value: "1"},
        {name: "{START_DATE}", type: "DATE_TIME", units: null, value: "2019-05-15T00:00"},
        {name: "{START_TIME}", type: "DOUBLE", units: "SECOND", value: "0"},
        {name: "{STOP_DATE}", type: "DATE_TIME", units: null, value: "2019-05-26T13:46:40"},
        {name: "{STOP_MODE}", type: "STRING", units: null, value: "STOP_MODE_AT_TIME"},
        {name: "{STOP_TIME}", type: "DOUBLE", units: "SECOND", value: "1000000"}
    ]
}

Note that the value field of an input item always contains a string, for example, for the input of range type ("FIXED_RANGE_INTEGER") it is a string with unparsed JSON with min, max, and step fields.

Possible values of the type field are listed in Input types section. The unit field can contain null or one of the values listed in the Units section.

Outputs

Outputs is a fairly complex object and we strongly recommend using the Cloud clients provided by AnyLogic because they contain methods that simplify navigation through the Outputs, see for example SingleRunOutputs and MultiRunOutputs JavaScript objects. It is an array, each element represents a single output of a run or a series of runs, but also, in case of multi-run experiments, an element can represent an input being varied across the runs. The aggregationType field is a system field that tells how the results of individual runs are aggregated. Consider an example of a single run outputs:

[
    {
        aggregationType: "IDENTITY",
        inputs: [],
        outputs: [{name: "Queue size stats", type: "STATISTICS_CONTINUOUS", units: null, value: null}],
        value: "{"type":"CONTINUOUS","count":1375730,"min":0.0,"max":10.0,"mean":0.9915478405530092, … }"
    },
    {
        aggregationType: "IDENTITY"
        inputs: []
        outputs: [{name: "Total time in system|Total time in system", type: "HISTOGRAM_DATA", units: null, value: null}]
        value: "{"statistics":{"type":"DISCRETE","count":1000897,"min":1.6001570096705109, … }"
    },
    {
        aggregationType: "IDENTITY"
        inputs: []
        outputs: [{name: "Utilization|Server utilization", type: "DOUBLE", units: null, value: null}]
        value: "0.500358755897554"
    },
    {
        aggregationType: "IDENTITY"
        inputs: []
        outputs: [{name: "Mean queue size|Mean queue size", type: "DOUBLE", units: null, value: null}]
        value: "0.9915478375701102"
    }
]

In case of a single simulation run, there is obviously no aggregation, and the value of the aggregationType field is IDENTITY . Besides the aggregationType, each element of a top-level array always contains inputs, outputs, and value fields. As there are no varied parameters in a single run, there are no input descriptors and the inputs field is an empty array in all items. outputs field is an array with a single element describing the output: its name, type, units, and value, but the value is always null in the descriptor.

Possible values of the type field of an array element are listed in Input types section.

The actual value of the output is contained in the value field of the top-level array element as an unparsed JSON object. You can find more information on the value formats in the section about data conversion.

Please keep in mind that, although an AnyLogic chart (possibly, containing multiple data items) is a single item in AnyLogic Run configuration, its data items become separate items in AnyLogic Cloud API. Therefore, the name of such item what the API would return is constructed as <the name of the chart in Run Configuration outputs>|<the title of the data item in the chart>. For other AnyLogic outputs, such as (explicitly defined) datasets, statistics objects, output objects, the name is AnyLogic API outputs is the same as in AnyLogic. Compare the JS object above with the Run configuration screenshot:

Outputs in AnyLogic Run Configuration

In case of multi-run experiments, such as Variation or Monte Carlo, the outputs format becomes a bit more complicated. To give you an idea of the format, let us consider the outputs of the parameter variation experiment of the same model that produced single-run outputs described above:

[
    {
        aggregationType: "ARRAY",
        inputs: [],
        outputs: [{name: "Queue size stats", type: "STATISTICS_CONTINUOUS", units: null, value: null}],
        value: "[{"count":1335978,"min":0.0,"max":10.0,"mean":0.9860041083277306,"variance":0.07920717342721317, … }]"
    },
    {
        aggregationType: "ARRAY",
        inputs: [],
        outputs: [{name: "Total time in system|Total time in system", type: "HISTOGRAM_DATA", units: null, value: null}],
        value: "[{"statistics":{"type":"DISCRETE","count":999206,"min":1.4401413087034598, … }]"
    },
    {
        aggregationType: "ARRAY",
        inputs: [],
        outputs: [{name: "Utilization|Server utilization", type: "DOUBLE", units: null, value: null}],
        value: "[0.4497779714401299,0.4750782325123417,0.500358755897554]"
    },
    {
        aggregationType: "ARRAY",
        inputs: [],
        outputs: [{name: "Mean queue size|Mean queue size", type: "DOUBLE", units: null, value: null}],
        value: "[0.9860040848956356,0.9877473693031751,0.9915478375701102]"
    },
    {
        aggregationType: "ARRAY",
        inputs: [{name: "Mean service time",type: "FIXED_RANGE_DOUBLE",units: null,value: "{"min":"1.8","max":"2","step":"0.1"}"}],
        outputs: [],
        value: "[[1.8],[1.9],[2.0]]"
    }
]

The aggregationType is now ARRAY. As you can see, now one of the items in the top-level array (the last one) contains a non-empty input field for the input that is being varied. The (unparsed) value fields of the output items are now arrays with multiple elements, for example, the Queue size stats has an array of three statistics objects in its value field. The sequence of the output values in the arrays corresponds to the sequence of the varied input parameter values. Once again: the cloud clients provided by AnyLogic enable easy iterations though the inputs and outputs, see for example the JS MultiRunOutputs class.

Run Request

Run Request contains the type of experiment and the full list of inputs. It does not contain a reference to the model version, which is passed as a part of the request URL. For example, for a simulation run it will look like this:

{
    experimentType: "SIMULATION",
    inputs: [
        {name: "{STOP_MODE}", type: "STRING", units: null, value: "STOP_MODE_AT_TIME"},
        {name: "{START_TIME}", type: "DOUBLE", units: "SECOND", value: "0"},
        {name: "{STOP_TIME}", type: "DOUBLE", units: "SECOND", value: "1000000"},
        {name: "{START_DATE}", type: "DATE_TIME", units: null, value: "2019-05-15T00:00"},
        {name: "{STOP_DATE}", type: "DATE_TIME", units: null, value: "2019-05-26T13:46:40"},
        {name: "{MAX_MEMORY_MB}", type: "INTEGER", units: null, value: "512"},
        {name: "Arrival rate", type: "DOUBLE", units: null, value: "1"},
        {name: "Mean service time", type: "DOUBLE", units: null, value: "2"},
        {name: "Server capacity", type: "INTEGER", units: null, value:  5 },
        {name: "Check failure probability", type: "DOUBLE", units: null, value: "0.2"},
        {name: "{RANDOM_SEED}", type: "LONG", units: null, value: "1"}
    ]
}

And for a parameter variation experiment with one parameter varied in a range, like this:

{
    experimentType: "PARAMETER_VARIATION",
    inputs: [
        {name: "{STOP_MODE}", type: "STRING", units: null, value: "STOP_MODE_AT_TIME"},
        {name: "{START_TIME}", type: "DOUBLE", units: "SECOND", value: "0"},
        {name: "{STOP_TIME}", type: "DOUBLE", units: "SECOND", value: "1000000"},
        {name: "{START_DATE}", type: "DATE_TIME", units: null, value: "2019-05-15T00:00"},
        {name: "{STOP_DATE}", type: "DATE_TIME", units: null, value: "2019-05-26T13:46:40"},
        {name: "{MAX_MEMORY_MB}", type: "INTEGER", units: null, value: "512"},
        {name: "Arrival rate", type: "DOUBLE", units: null, value: "1"},
        {name: "Mean service time", type: "FIXED_RANGE_DOUBLE", units: null, value:  { min : 1.8 , max : 2 , step : 0.1 } },
        {name: "Server capacity", type: "INTEGER", units: null, value:  5 },
        {name: "Check failure probability", type: "DOUBLE", units: null, value: "0.2"},
        {name: "{RANDOM_SEED}", type: "LONG", units: null, value: "1"}
    ]
}

Please note that the value of an input is always a string, which may contain an unparsed JSON like in the case of a range.

Experiment Results Request

Experiment Results Request contains the type of experiment, the full list of inputs, and the list of required outputs. The latter is a string form of the Outputs data object with blank values. For example:

{
    experimentType: "SIMULATION",
    inputs: [
        {name: "{STOP_MODE}", type: "STRING", units: null, value: "STOP_MODE_AT_TIME"},
        {name: "{START_TIME}", type: "DOUBLE", units: "SECOND", value: "0"},
        {name: "{STOP_TIME}", type: "DOUBLE", units: "SECOND", value: "1000000"},
        {name: "{START_DATE}", type: "DATE_TIME", units: null, value: "2019-05-15T00:00"},
        {name: "{STOP_DATE}", type: "DATE_TIME", units: null, value: "2019-05-26T13:46:40"},
        {name: "{MAX_MEMORY_MB}", type: "INTEGER", units: null, value: "512"},
        {name: "Arrival rate", type: "DOUBLE", units: null, value: "1"},
        {name: "Mean service time", type: "DOUBLE", units: null, value: "2"},
        {name: "Server capacity", type: "INTEGER", units: null, value:  5 },
        {name: "Check failure probability", type: "DOUBLE", units: null, value: "0.2"},
        {name: "{RANDOM_SEED}", type: "LONG", units: null, value: "1"}
    ]
    outputs: "[{"aggregationType":"IDENTITY","inputs":[],"outputs":[{"name":"Queue size "
}

Experiment Run

Experiment run contains the unique run id, the model version id, the inputs, the run status, and the message containing some unparsed JSON with information about the run (see Experiment Run State) and can be empty, for example:

{
    id: "Ao7KEYv76pyqZFf6QetmAg6InFuJoJMj1WbYaSm17wo",
    inputs: [
        {name: "{STOP_MODE}", type: "STRING", units: null, value: "STOP_MODE_AT_TIME"},
        {name: "{START_TIME}", type: "DOUBLE", units: "SECOND", value: "0"},
        {name: "{STOP_TIME}", type: "DOUBLE", units: "SECOND", value: "1000000"},
        {name: "{START_DATE}", type: "DATE_TIME", units: null, value: "2019-05-15T00:00"},
        {name: "{STOP_DATE}", type: "DATE_TIME", units: null, value: "2019-05-26T13:46:40"},
        {name: "{MAX_MEMORY_MB}", type: "INTEGER", units: null, value: "512"},
        {name: "Arrival rate", type: "DOUBLE", units: null, value: "1"},
        {name: "Mean service time", type: "DOUBLE", units: null, value: "2"},
        {name: "Server capacity", type: "INTEGER", units: null, value: "5"},
        {name: "Check failure probability", type: "DOUBLE", units: null, value: "0.2"},
        {name: "{RANDOM_SEED}", type: "LONG", units: null, value: "1"}
    ],
    message: "{"title":null,"total":100,"finishedTasks":1,"totalTasks":1,"subRuns":[]}",
    modelVersionId: "def089c4-96a3-40f2-aefb-de1af471fe2f",
    outputs: null,
    status: "COMPLETED"
}

The outputs field of the Experiment Run is always empty.

Experiment Run State

Experiment Run State comes in response to a progress request and contains the status of the run and the message field with the unparsed JSON structure with the run progress details:

{
    message: "{"title":null,"total":52,"finishedTasks":0,"totalTasks":1,"subRuns":[]}",
    status: "RUNNING"
}

To obtain the completed fraction of the run, you can call JSON.parse( <experiment run state>.message ).total;

The status of a run can be one of:

  • "FRESH" - created, not yet started
  • "RUNNING" - currently being executed
  • "COMPLETED" - successfully completed, results are available
  • "FAILED" - execution failed
  • "STOPPED" - cancelled before completion by the user (via GUI or API).

For a completed run, the message field may be an empty string:

{
    message: "",
    status: "COMPLETED"
}

For a multiple run experiment, the Experiment Run State will look like this:

{
    message: "{"title":null,"total":47,"finishedTasks":0,"totalTasks":11," … }",
    status: "RUNNING"
}

And if you parse the message field, the structure will be:

{
    finishedTasks: 0
    subRuns: [
        {title: null, total: 42, finishedTasks: 2, totalTasks: 5, subRuns: []},
        {title: null, total: 42, finishedTasks: 2, totalTasks: 5, subRuns: []}
    ],
    title: null,
    total: 47,
    totalTasks: 11
}

The subRuns field contains information about the batches of individual runs comprising the experiment.

Animation SVG Run Info

Animation SVG Run Info comes in response to start an animated run of the model. It contains the native dimensions of the animation window, the simulation speed, the URL of the node where the simulation is running, and other information:

{
    animationHeight: 600,
    animationSpeed: 10,
    animationWidth: 1000,
    experimentRunId: "fbc6a1d8-fa08-4a5c-a171-4631657c1aa7",
    modelUuid: "493e6789-acf7-4dac-971d-325cb508ea39",
    restUrl: "b0cc221b-8217-474b-af18-c2d07753187b/",
    sessionUuid: "b0cc221b-8217-474b-af18-c2d07753187b",
    version: "8.5.0"
}

Input types

Input type is a string constant, possible values are:

  • "STRING"
  • "DOUBLE"
  • "INTEGER"
  • "LONG"
  • "DATE_TIME"
  • "BOOLEAN"
  • "RANDOM_DOUBLE"
  • "RANDOM_INTEGER"
  • "RANDOM_BOOLEAN"
  • "FIXED_RANGE_DOUBLE"
  • "FIXED_RANGE_INTEGER"
  • "FIXED_RANGE_LONG"
  • "FULL_RANGE_BOOLEAN"
  • "INPUT_FILE"

Units

Unit is a string constant, it can be null, or one of:

  • "MILLISECOND"
  • "SECOND"
  • "MINUTE"
  • "HOUR"
  • "DAY"
  • "WEEK"
  • "MONTH"
  • "YEAR"
  • "MILLIMETER"
  • "CENTIMETER"
  • "METER"
  • "KILOMETER"
  • "INCH"
  • "FOOT"
  • "YARD"
  • "MILE"
  • "NAUTICAL_MILE"
  • "SQ_MILLIMETER"
  • "SQ_CENTIMETER"
  • "SQ_METER"
  • "SQ_KILOMETER"
  • "SQ_INCH"
  • "SQ_FOOT"
  • "SQ_YARD"
  • "SQ_MILE"
  • "SQ_NAUTICAL_MILE"
  • "PER_MILLISECOND"
  • "PER_SECOND"
  • "PER_MINUTE"
  • "PER_HOUR"
  • "PER_DAY"
  • "PER_WEEK"
  • "PER_MONTH"
  • "PER_YEAR"
  • "MPS"
  • "KPH"
  • "FPS"
  • "FPM"
  • "MPH"
  • "KN"
  • "MPS_SQ"
  • "FPS_SQ"
  • "LITER"
  • "OIL_BARREL"
  • "CUBIC_METER"
  • "KILOGRAM"
  • "TON"
  • "LITER_PER_SECOND"
  • "OIL_BARREL_PER_SECOND"
  • "CUBIC_METER_PER_SECOND"
  • "KILOGRAM_PER_SECOND"
  • "TON_PER_SECOND"
  • "TURN"
  • "RADIAN"
  • "DEGREE"
  • "RPM"
  • "RAD_PER_SECOND"
  • "DEG_PER_SECOND"

Output types

Output type is a string constant, possible values are:

  • "STRING"
  • "DOUBLE"
  • "INTEGER"
  • "LONG"
  • "DATE_TIME"
  • "BOOLEAN"
  • "DATA_SET"
  • "STATISTICS_DISCRETE"
  • "STATISTICS_CONTINUOUS"
  • "HISTOGRAM_DATA"
  • "HISTOGRAM_2D_DATA"
  • "MODEL_OUTPUT_NAME"

3 JavaScript API

3.1 Installing the JavaScript client

JavaScript API is used to build custom web interfaces for cloud based AnyLogic models with or without animation. To start using the API, follow these steps:

  1. Download the required version of the cloud client from https://cloud.anylogic.com/files/api-8.5.0/clients. For example, for API version 8.5.0 download https://cloud.anylogic.com/files/api-8.5.0/clients/js-client-8.5.0.zip.
  2. Unpack the .zip file. You will see this directory structure:
    anylogic-cloud-client-8.5.0
       js-client-8.5.0
          assets
          index.html
          main.js
  3. Copy the content of the assets folder into your website's assets directory.
  4. Load the cloud_client library by adding the <script> tag to the head of your webpage*:
    <script src="assets/cloud_client.js"></script>
  5. Obtain the API key in the AnyLogic Cloud web UI.
  6. Create a new instance of the CloudClient object
    cloudClient = CloudClient.create( <apiKey> ) if you are using the public AnyLogic Cloud, or
    cloudClient = CloudClient.create( <apiKey>, <URL of the cloud> );
  7. Use the API to work with the cloud-based models.

The index.html and main.js files contain a simple example of the API usage and may be useful to look at.

Please keep in mind that:

  • AnyLogic Cloud JavaScript API is fully asynchronous and extensively uses the Promise technology
  • Your IDE compiler settings should allow ECMAScript 6 JavaScript version.

3.2 API usage examples

Simulation run without animation (minimalistic)

In this example we run a simulation experiment without animation. First, we find the model and its latest version. Next, we specify the input parameters of the model. If simulation with such inputs has already been completed (and thus, the outputs are stored in the Cloud), the outputs are simply fetched to the web frontend. Otherwise, the simulation run is performed (in fast animation-less mode), the outputs are generated and then delivered to the frontend. This is done with the API function ModelRun.getOutputsAndRunIfAbsent().

HTML:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>AnyLogic Cloud API Example - Run simulation. Minimalistic</title>
    <script src="assets/cloud-client.js"></script>
    <script src="js/main.js"></script>
</head>
<body>
    <button id="run-button" onclick="runSimulation()">Run simulation</button>
    <div id="info">The simulation results will be displayed here</div>
</body>
</html>

JavaScript:

let cloudClient = create("e05a6efa-ea5f-4adf-b090-ae0ca7d16c20");

let runButton;
let info;
let inputs;

window.onload = () => {
    runButton = document.getElementById( "run-button" );
    info = document.getElementById( "info" );
};

function runSimulation() {
    runButton.disabled = true;
    cloudClient.getLatestModelVersion( "Service System Demo" )
        .then( version => {
            inputs = cloudClient.createDefaultInputs( version );
            inputs.setInput( "Server capacity", 8 );
            let simulation = cloudClient.createSimulation(inputs);
            info.innerHTML = "Getting outputs, running simulation if absent...";
            return simulation.getOutputsAndRunIfAbsent();
        })
        .then( outputs => {
            let html = "For Server Capacity = " + inputs.getInput( "Server capacity" ) + ":<br>";
            html += "Mean queue size = " + outputs.value( "Mean queue size|Mean queue size" ) + "<br>";
            html += "Server utilization = " + outputs.value( "Utilization|Server utilization" ) + "<br>";
            info.innerHTML = html;
        })
        .catch( error => {
            info.innerHTML = error.status + "<br>" + error.message;
            console.error( error );
        })
        .finally( () => {
            runButton.disabled = false;
        });
}

Try now

In the HTML, we include the AnyLogic Cloud Client JavaScript located in the assets folder as advised in the installation guide; there is also a button and a div element to display the simulation results.

As the web page and the scripts get loaded, a CloudClient object is created, given the API key. The second optional parameter of the static function CloudClient.create() is the URL of the cloud, here it is omitted, so AnyLogic public cloud is assumed. We also remember the HTML elements we will use.

When the user presses the Run button, the function runSimulation() is called. It disables the button and initiates a chain of client-server communication using the JavaScript Promise mechanism:

  1. The server is asked to find the latest version of the model with the name “Service System Demo”.
  2. When (and if) such model and version is found (the first then), we create the Inputs object with default input values. And in those inputs, we change the value of “Server capacity” parameter to 8. We ask the CloudClient to create a simulation object with the inputs. This is a purely fronted operation, no communication with the server is done. This code block finishes with the call of getOutputsAndRunIfAbsent(), which does the following: checks if such simulation has been completed, if yes, gets the outputs, if not, runs it and waits for the outputs. The SingleRunOutputs object is returned by Promise.
  3. When the outputs are delivered, we display some output values and the corresponding input using the API of SimgleRunOutputs and Inputs. That’s it. If no error occurs during that sequence of operations, we proceed directly to the finally block, where we re-enable the Run button.
  4. If during any step an error occurs, the catch block is invoked. Here we display the error message and copy it to console. Then finally block is executed.
  5. Please note that, to get the value of a particular output, we need to specify its name exactly as it is constructed when the model is uploaded to the Cloud, see information in the Output data object section. The only thing that is relaxed is the case: you can use either lower or upper case.

It is also worth reminding that the function runSimulation() completes immediately, and the code fragments written in the then, catch, and finally blocks are invoked later when the corresponding events occur.

Querying simulation results of a completed run

As you know, AnyLogic Cloud stores the input/output pairs of all ever-completed simulation runs. In this example, we will show how to query the outputs of a completed model run (which is identified by the inputs). If the results exist, they are displayed, if not, we do not run the simulation (in contrast with the previous example) and display the corresponding message. The key function used here is ModelRun.getOutputs().

HTML:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>AnyLogic Cloud API Example - Query outputs of a completed run</title>
    <script src="assets/cloud-client.js"></script>
    <script src="js/main.js"></script>
</head>
<body>
    Server capacity: <span id="parameter-value">5</span>
    <input id="parameter-range" type="range" min="2" max="10" step="1" value="5" onchange="changeValueText()">
    <button id="query-button" onclick="queryOutputs()">Query outputs</button>
    <div id="info">The simulation results will be displayed here</div>
</body>
</html>

JavaScript:

let cloudClient = create("e05a6efa-ea5f-4adf-b090-ae0ca7d16c20");

let parameterValue;
let parameterRange;
let queryButton;
let info;

window.onload = () => {
    parameterValue = document.getElementById( "parameter-value" );
    parameterRange = document.getElementById( "parameter-range" );
    queryButton = document.getElementById( "query-button" );
    info = document.getElementById( "info" );
};

function queryOutputs() {
    parameterRange.disabled = true;
    queryButton.disabled = true;
    cloudClient.getModelByName( "Service System Demo" )
        .then( model => cloudClient.getLatestModelVersion( model ) )
        .then( version => {
            let inputs = cloudClient.createDefaultInputs( version );
            inputs.setInput( "Server capacity", parameterRange.value);
            let simulation = cloudClient.createSimulation(inputs);
            info.innerHTML = "Getting outputs...";
            return simulation.getOutputs();
        })
        .then( outputs => {
            let html = "Mean queue size = " + outputs.value( outputs.findNameIncluding("Mean Queue Size") ) + "<br>";
            html += "Server utilization = " + outputs.value( outputs.findNameIncluding("Server utilization") ) + "<br>";
            info.innerHTML = html;
        })
        .catch( error => {
            info.innerHTML = error.status + "<br>" + error.message;
            console.error( error );
        })
        .finally( () => {
            parameterRange.disabled = false;
            queryButton.disabled = false;
        });
}

function changeValueText() {
    parameterValue.innerText = parameterRange.value;
}

Try now

In the HTML code there is a range input element that we use to set the value of the Server capacity. Having constructed the inputs and the simulation objects, we call simulation.getOutputs(). If the outputs exist, they are returned by Promise and we get into the then block that follows. If they are absent, an error is thrown and we process to the catch block and display the error. However, the same catch block is executed if any other type of error occurs. In case you want a specific handler to be invoked in case of absent results, you can modify the code in the following way:

JavaScript:

…
return simulation.getOutputs()
    .catch( error => {
        if( error.status == 404 ) {
            //custom handler code
            console.error( "Simulation results not found for these inputs" );
        }
        throw new Error( "Outputs not found for Server Capacity = " + parameterRange.value );
    });
…

Simulation run with progress polling

In case of an animation-less simulation run it makes sense to have some progress indication. In this example we use the function ModelRun.getProgress() to obtain the progress of a running simulation. Also, we show a different way of running the simulation and getting results: the sequence of run(), waitForCompletion(), and getOutputs() function calls.

HTML:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>AnyLogic Cloud API Example - Simulation with Progress indication</title>
    <script src="assets/cloud-client.js"></script>
    <script src="js/main.js"></script>
</head>
<body>
    <button id="run-button" onclick="runSimulation()">Run simulation</button>
    <progress id="progress" value="0" max="100"></progress>
    <div id="info">The simulation results will be displayed here</div>
</body>
</html>    

JavaScript:

let cloudClient = create("e05a6efa-ea5f-4adf-b090-ae0ca7d16c20");

let runButton;
let progress;
let info;
let simulation;

window.onload = () => {
    runButton = document.getElementById( "run-button" );
    progress = document.getElementById( "progress" );
    info = document.getElementById( "info" );
};

function runSimulation() {
    runButton.disabled = true;
    cloudClient.getLatestModelVersion( "Service System Demo" )
        .then( version => {
            let inputs = cloudClient.createDefaultInputs( version );
            inputs.setInput( "Server capacity", 21 );
            inputs.setInput( "{STOP_TIME}", 10000000 );
            simulation = cloudClient.createSimulation(inputs);
            startPolling();
            return simulation.run();
        })
        .then( simulation => simulation.waitForCompletion() )
        .then( simulation => simulation.getOutputs() )
        .then( outputs => {
            let html = "Mean queue size = " + outputs.value( "Mean queue size|Mean queue size" ) + "<br>";
            html += "Server utilization = " + outputs.value( "Utilization|Server utilization" ) + "<br>";
            info.innerHTML = html;
        })
        .catch( error => {
            info.innerHTML = error.status + "<br>" + error.message;
            console.error( error );
        })
        .finally( () => {
            stopPolling();
            runButton.disabled = false;
        });
}

let pollingInterval;

function startPolling() {
    pollingInterval = setInterval(
        () => {
            simulation.getProgress()
                .then( progressinfo => {
                    if( progressinfo ) { //can be undefined in the beginning
                        progress.value = progressinfo.total;
                    }
                });
        },
        1000
    );
}

function stopPolling() {
    setTimeout( () => clearInterval( pollingInterval ), 2000 );
}

Try now

During the standard construction of the inputs, we change the “system” input {STOP_TIME} to a bigger value to make the simulation run longer, so we can really see the progress bar moving. Such input exists in every model uploaded to the Cloud. However, if a run with the same stop time and other inputs has been performed already, the simulation won’t run, and the outputs will be delivered almost instantly. So, to watch the progress moving, consider setting other inputs to new values.

The function startPolling() is called just before simulation.run() and initiates a repeated call of simulation.getProgress() with one second interval. getProgress() returns information about the progress by Promise. For a simple simulation run, we are only interested in the total field; for multi-run experiments it contains more details. Please keep in mind that the result may also be an empty object in case we poll too soon, while the run has not yet been created on the server; therefore, we need to check the existence of the polling result. The function stopPolling() is called once the simulation results have been delivered (or an error occurs). It clears the one second interval but waits for additional two seconds to make sure the final progress value has been received.

The call of simulation.run() initiates the simulation run (of course, only if such run has not been done before) are returns the same simulation object by Promise – it does not wait for completion of the simulation. To wait, we use the function simulation.waitForCompletion(), which also returns the same object. And only then we query the simulation results by calling simulation.getOutputs(). This sequence is here solely for demo purposes; it could be replaced by a single call of simulation.getOutputsAndRunIfAbsent().

Running a custom workflow – a mix of parallel and sequential simulations

Now, let’s implement a custom workflow where some simulations are run in parallel and some – sequentially, based on the results of other simulations. This scenario may be useful for those who want to set up e.g. a custom optimization algorithm in Anylogic Cloud.

HTML:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>AnyLogic Cloud API Example - Custom workflow</title>
    <script src="assets/cloud-client.js"></script>
    <script src="js/main.js"></script>
</head>
<body>
    <button id="run-button"
onclick="runSimulations()">Run 2+1 simulations</button>
    <div id="info">The simulation results will be displayed here</div>
</body>
</html>

JavaScript:

let cloudClient = create("e05a6efa-ea5f-4adf-b090-ae0ca7d16c20");

let version;
let runButton;
let info;

window.onload = () => {
    runButton = document.getElementById( "run-button" );
    info = document.getElementById( "info" );
};

let sc1 = 4;
let sc2 = 9;
let sc3;

function runSimulations() {
    runButton.disabled = true;
    cloudClient.getLatestModelVersion( "Service System Demo" )
        .then( v => {
            version = v;
            //create two inputs instances with different parameter values
            let inputs1 = cloudClient.createDefaultInputs( version );
            let inputs2 = cloudClient.createDefaultInputs( version );
            inputs1.setInput( "Server capacity", sc1 );
            inputs2.setInput( "Server capacity", sc2 );
            //create two simulation objects with different inputs
            let simulation1 = cloudClient.createSimulation( inputs1 );
            let simulation2 = cloudClient.createSimulation( inputs2 );
            info.innerHTML = "Running two parallel simulations...<br>";
            //run the two simulations in parallel, wait for both results
            return Promise.all( [simulation1.getOutputsAndRunIfAbsent(), simulation2.getOutputsAndRunIfAbsent()] );
        })
        .then( outputarray => {
            //both outputs are here
            let su1 = outputarray[0].value( "Utilization|Server utilization" );
            let su2 = outputarray[1].value( "Utilization|Server utilization" );
            let html = "Two parallel simulations completed.<br>";
            html += "With " + sc1 + " servers, Server utilization = " + su1 + "<br>";
            html += "With " + sc2 + " servers, Server utilization = " + su2 + "<br>";
            //based on the results, set up the inputs for the third simulation
            sc3 = (su1 + su2) > 0.4 ? sc2-1 : sc1+1;
            let inputs3 = cloudClient.createDefaultInputs( version );
            inputs3.setInput( "Server capacity", sc3 );
            let simulation3 = cloudClient.createSimulation( inputs3 );
            html += "Running third simulation with " + sc3 + " servers...<br>";
            info.innerHTML += html
            return simulation3.getOutputsAndRunIfAbsent();
        })
        .then( outputs3 => {
            let su3 = outputs3.value( "Utilization|Server utilization" );
            let html = "Third simulation completed.<br>";
            html += "With " + sc3 + " servers, Server utilization = " + su3 + "<br>";
            info.innerHTML += html
        })
        .catch( error => {
            info.innerHTML = error.status + "<br>" + error.message;
            console.error( error );
        })
        .finally( () => {
            runButton.disabled = false;
        });
}

Try now

In this scenario, we first perform two simulations runs in parallel with the Server Capacity parameter set to 4 in one run and 9 in another. The two runs are initiated by calling simulation1.getOutputsAndRunIfAbsent() and simulation2.getOutputsAndRunIfAbsent(). The function getOutputsAndRunIfAbsent(), as actually all functions in AnyLogic Cloud JavaScript API that involve communication with the server, are asynchronous – they immediately return a Promise, and then we need to provide the handler in a then block. In this case we have two Promise objects from the two runs – and we need to wait for both to complete. This is accomplished by using the Promise.all() function that returns a single Promise that resolves when all promises passed as an iterable (array in our case) have resolved. The outputs of simulation runs performed in parallel are returned also as an array in the same order. In the then block that follows we analyze the values of Server Utilization in both runs and set up the input of the third simulation run based on those values.

Please note that, when you run multiple simulations in parallel manually, like in this example, each call of ModelRun.getOutputsAndRunIfAbsent() or ModelRun.waitForCompletion() performs its own independent polling, therefore many parallel runs may result in high HTTP traffic. If that is suspected, consider changing the optional parameter pollingPeriod of these functions.

Running parameter variation

In this example, we will run a parameter variation experiment. One of the input parameters will be varied in range. To demonstrate one more feature of the AnyLogic Cloud API, we will take the input values from an existing simulation experiment defined in the standard AnyLogic Cloud web interface and change a parameter value from a scalar to a range.

HTML:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>AnyLogic Cloud API Example - Parallel Simulations</title>
    <script src="assets/cloud-client.js"></script>
    <script src="js/main.js"></script>
</head>
<body>
    <button id="run-button" onclick="runVariation()">Run variation</button>
    <div id="info">The parameter variation results will be displayed here</div>
</body>
</html>

JavaScript:

let cloudClient = create("e05a6efa-ea5f-4adf-b090-ae0ca7d16c20");

let runButton;
let info;

window.onload = () => {
    runButton = document.getElementById( "run-button" );
    info = document.getElementById( "info" );
};

function runVariation() {
    runButton.disabled = true;
    cloudClient.getLatestModelVersion( "Service System Demo" )
        .then( version => {
            return cloudClient.createInputsFromExperiment( version, "Baseline" );
        })
        .then( inputs => {
            inputs.setRangeInput( "Mean service time", 1.8, 2, 0.1 );
            let variation = cloudClient.createParameterVariation( inputs );
            info.innerHTML = "Running parameter variation...<br>";
            return variation.getOutputsAndRunIfAbsent( ["Total time in system|Total time in system"] );
        })
        .then( outputs => {
            let html = "Parameter variation completed.<br>";
            let invalues = outputs.getValuesOfInput("Mean service time");
            let outvalues = outputs.getValuesOfOutput("Total time in system|Total time in system");
            for( let i=0; i<invalues.length; i++ ) {
                html += "When Mean service time = " + invalues[i] +
                        ", mean Total time in system = " + outvalues[i].statistics.mean + "<br>"
            }
            info.innerHTML += html
        })
        .catch( error => {
            info.innerHTML = error.status + "<br>" + error.message;
            console.error( error );
        })
        .finally( () => {
            runButton.disabled = false;
        });
}

Try now

To reuse the inputs set from an experiment defined in the AnyLogic Cloud web interface we use the function CloudClient.createInputsFromExperiment() instead of createDefaultInputs(). Having copied the inputs of the "Baseline" experiment,  we change the input "Mean service time" to a range from 1.8 to 2.0 with step 0.1 type by calling Inputs.setRangeInput(). Therefore three simulation runs are to be performed. Then we need to create a ModelRun object of parameter variation type, this is what the function CloudClient.createParameterVariation() does.

The run of a parameter variation is invoked by calling getOutputsAndRunIfAbsent() just like in our previous examples, but there is one important difference. Full outputs of a multiple run experiment may be a very large piece of data, so the API user has to explicitly specify which outputs need to be delivered. This is done by listing the output names in the array passed to getOutputsAndRunIfAbsent() or getOutputs() as a parameter. In this example, we are interested in the "Total time in system|Total time in system" output. If the parameter is omitted for a multi run experiment, only scalar outputs will be returned, if any.

Outputs of a multi run experiment are returned as a MultiRunOutputs object, which has a number of functions simplifying navigation.  In this simple example we get array of the input values and the corresponding array of the output values, and display them.

Simulation run with animation (minimalistic)

The minimalistic example of embedding the animation of acloud-based model into a web page would look like the following.

HTML:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>AnyLogic Cloud API Example - Run animation. Minimalistic</title>
    <script src="assets/cloud-client.js"></script>
    <script src="js/main.js"></script>
</head>
<body>
    <button id="run-button" onclick="runAnimation()">Run animation</button>
    <div id="animation-container" style="width: 1200px; height: 700px; border: 1px solid blue;">
    </div>
</body>
</html>

JavaScript:

let cloudClient = create("e05a6efa-ea5f-4adf-b090-ae0ca7d16c20");

function runAnimation() {
    runButton = document.getElementById( "run-button" );
    runButton.disabled = true;
    cloudClient.getLatestModelVersion( "Bass Diffusion Demo 8.5.0" )
        .then( version => {
            let inputs = cloudClient.createDefaultInputs( version );
            inputs.setInput( "Contact Rate", 30 );
            return cloudClient.startAnimation( inputs, "animation-container" );
        })
        .then( animation => {
            return animation.waitForCompletion();
        })
        .catch( error => {
            console.error( error );
        })
        .finally( () => {
            runButton.disabled = false;
        });
}

Try now

Within the HTML code you should create the animation placeholder. In this example that is a 1200x700 <div> element, with id "animation-container", initially empty. Then, when the user presses the Run animation button, the function runAnimation() is called. It disables the button, searches for the latest version of the model, creates a default input set, and changes one of the input values. The call of cloudClient.startAnimation() launches the model, connects the model to the frontend, and starts streaming animation into the container provided. That call completes as the HTTP request completes and returns the Animation object by Promise. Then, we use animation.waitForCompletion() to wait for the user to press Stop. Please note that even if the animated simulation finishes (e.g. reaches its stop time), it is not considered as stopped until the user presses the Stop button on the animation control panel, or the server decides to stop it for any reason. The finally block is then executed and re-enables the Run button.

Animated run with external pause and resume buttons and state polling

This example is a bit more sophisticated than the previous one: we will add buttons to pause and resume the simulation. The key functions of the Animation object used here are pause(), resume(), and getState()

HTML:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>AnyLogic Cloud API Example - Animation. Pause Resume</title>
    <script src="assets/cloud-client.js"></script>
    <script src="js/main.js"></script>
</head>
<body>
    <button id="run-button" onclick="runAnimation()">Run animation</button>
    <button id="pause-button" onclick="pauseAnimation()" disabled>Pause</button>
    <button id="resume-button" onclick="resumeAnimation()" disabled>Resume</button>
    <div id="animation-container" style="width: 1200px; height: 700px; border: 1px solid blue;">
    </div>
</body>
</html>

JavaScript:

let cloudClient = create("e05a6efa-ea5f-4adf-b090-ae0ca7d16c20");

let runButton;
let pauseButton;
let resumeButton;
let animation;

window.onload = () => {
    runButton = document.getElementById( "run-button" );
    pauseButton = document.getElementById( "pause-button" );
    resumeButton = document.getElementById( "resume-button" );
};

function runAnimation() {
    runButton.disabled = true;
    cloudClient.getModelByName( "Bass Diffusion Demo 8.5.0" )
        .then( model => cloudClient.getLatestModelVersion( model ) )
        .then( version => {
            let inputs = cloudClient.createDefaultInputs( version );
            inputs.setInput( "Contact Rate", 40 );
            return cloudClient.startAnimation( inputs, "animation-container" );
        })
        .then( a => {
            animation = a;
            startPolling();
            return animation.waitForCompletion();
        })
        .catch( error => {
            console.error( error );
        })
        .finally( () => {
            stopPolling();
            runButton.disabled = false;
            pauseButton.disabled = true;
            resumeButton.disabled = true;
        });
}

function pauseAnimation() {
    pauseButton.disabled = true;
    animation.pause();
}

function resumeAnimation() {
    resumeButton.disabled = true;
    animation.resume();
}

let pollingInterval;

function startPolling() {
    pollingInterval = setInterval(
        () => {
            animation.getState()
                .then( state => {
                    pauseButton.disabled = state != "RUNNING";
                    resumeButton.disabled = state != "PAUSED";
                });
        },
        500
    );
}

function stopPolling() {
    clearInterval( pollingInterval );
}

Try now

In the HTML code, there are two new buttons above the animation container that are initially disabled. In JavaScript, we remember these elements as the page gets loaded. We also remember the Animation object returned by cloudClient.startAnimation() by Promise. The actions assigned to the buttons simply call pause() and resume() of that object. What is more interesting, is the animation state polling. Once the animation starts, we begin to poll its state every 0.5 second, see the function startPolling(). A call of getState() returns the animation state by Promise, and we use it to enable or disable the buttons. The polling is stopped once the model is stopped or an error occurs, see the finally block.

Please note that, in animated run, the inputs are not validated until they are passed to the model. Therefore, if there is an error in the input values (such as a type error), the model will fail to start and you will see an error message within the animation window, but the catch block won’t be reached.

Also, in this example we show a different, more general, way of getting to the latest version of the model: we first get the Model object by its name, and then ask for the latest version of the model.

Animated run with external control of a model parameter

In this example we will show how to control a model input parameter while the model is running using the AnyLogic Cloud API and how to call a function defined in the model. We will run the model with the default input parameter value and provide external HTML controls to change it. The key functions used here are setValue() and callFunction().

HTML:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>AnyLogic Cloud API Example - Animation. Parameter Control</title>
    <script src="assets/cloud-client.js"></script>
    <script src="js/main.js"></script>
</head>
<body>
    <button id="run-button" onclick="runAnimation()">Run animation</button>
    <input id="parameter-range" onchange="changeParameter()" type="range" min="0" max="100" disabled>
    <button id="reset-button" onclick="resetParameter()" disabled>Reset parameter</button>
    <div id="animation-container" style="width: 1200px; height: 700px; border: 1px solid blue;">
    </div>
</body>
</html>

JavaScript:

let cloudClient = create("e05a6efa-ea5f-4adf-b090-ae0ca7d16c20");

let runButton;
let parameterRange;
let resetButton;
let animation;

window.onload = () => {
    runButton = document.getElementById( "run-button" );
    parameterRange = document.getElementById( "parameter-range" );
    resetButton = document.getElementById( "reset-button" );
};

function runAnimation() {
    runButton.disabled = true;
    cloudClient.getLatestModelVersion( "Bass Diffusion Demo 8.5.0" )
        .then( version => {
            let inputs = cloudClient.createDefaultInputs( version );
            return cloudClient.startAnimation( inputs, "animation-container" );
        })
        .then( a => {
            animation = a;
            parameterRange.disabled = false;
            resetButton.disabled = false;
            return animation.waitForCompletion();
        })
        .catch( error => {
            console.error( error );
        })
        .finally( () => {
            runButton.disabled = false;
            parameterRange.disabled = true;
            resetButton.disabled = true;
        });
}

function changeParameter() {
    animation.setValue( "experiment.root.ContactRate", parseFloat( parameterRange.value ) );
}

function resetParameter() {
    parameterRange.value = 30;
    animation.callFunction( "experiment.root.set_ContactRate", [30] );
}

Try now

In the HTML code, there is a slider and a button; initially these controls are disabled.

The function changeParameter() uses the AnyLogic Cloud API function setValue(), the first parameter of which is the path to the object whose value we wish to set. The path should always start with the word "experiment" in most cases followed by "root": " experiment.root" brings you to the root agent of the model, typically this is the (only) instance of the class Main. As our Contact Rate parameter is located in Main, the full path to it is "experiment.root.ContactRate". Please note that here we are using the “original” Java name of the parameter and not its UI label “Contact Rate”! Also, as the parameter is numeric, we need to convert parameterRange.value, which is a string, to a float number.

Similarly, the resetParameter() function uses the API function callFunction() to force the value of Contact Rate to 30. It is done by invoking the function set_ContactRate() defined in the model class Main (in this case, the function was auto-generated by AnyLogic, but it could be a user-defined function). The path to the function is obviously "experiment.root.set_ContactRate" and the arguments of the function call must be passed as a JavaScript array. In our case there is only one parameter – the new value 30. The functions defined in the model by the user are called in exactly same way.

Retrieving information from a running animated model

In this example, we will retrieve information from a running model using the getValue()< function of the Animation object.

HTML:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>AnyLogic Cloud API Example - Animation. Get data</title>
    <script src="assets/cloud-client.js"></script>
    <script src="js/main.get_data.js"></script>
</head>
<body>
    <button id="run-button" onclick="runAnimation()">Run animation</button>
    <button id="get-scalar-button" onclick="getScalarValue()" disabled>Get scalar value</button>
    <button id="get-dataset-button" onclick="getDataSet()" disabled>Get dataset</button>
    <div id="info">The data retrieved from the model will be displayed here</div>
    <div id="animation-container" style="width: 1200px; height: 700px; border: 1px solid blue;">
    </div>
</body>
</html>

JavaScript:

let cloudClient = create("e05a6efa-ea5f-4adf-b090-ae0ca7d16c20");

let runButton;
let getScalarButton;
let getDataSetButton;
let info;
let animation;

window.onload = () => {
    runButton = document.getElementById( "run-button" );
    getScalarButton = document.getElementById( "get-scalar-button" );
    getDataSetButton = document.getElementById( "get-dataset-button" );
    info = document.getElementById( "info" );
};

function runAnimation() {
    runButton.disabled = true;
    cloudClient.getLatestModelVersion( "Bass Diffusion Demo 8.5.0" )
        .then( version => {
            let inputs = cloudClient.createDefaultInputs( version );
            return cloudClient.startAnimation( inputs, "animation-container" );
        })
        .then( a => {
            animation = a;
            getScalarButton.disabled = false;
            getDataSetButton.disabled = false;
            return animation.waitForCompletion();
        })
        .catch( error => {
            info.innerText = error.message;
            console.error( error );
        })
        .finally( () => {
            runButton.disabled = false;
            getScalarButton.disabled = true;
            getDataSetButton.disabled = true;
        });
}

function getScalarValue() {
    let value = animation.getValue( "experiment.root.Adopters" )
        .then( value => {
            info.innerText = "Adopters = " + value;
        });
}

function getDataSet() {
    let value = animation.getValue( "experiment.root._ds_Adopters" )
        .then( value => {
            info.innerText = "Adopters over time: time=[" + value.dataX + "] Adopters=[" + value.dataY + "]";
        });
}

Try now

In the HTML code there are two buttons: one for querying a scalar value, and another for querying a dataset (both buttons are initially disabled), and a <div> element to display the obtained information.

The first parameter of getValue() is the full path to the object being accessed, which starts with “experiment.root” (just like in setValue() and callFunction(), see the previous example). The scalar value is the System Dynamics variable Adopters, and the dataset is the one automatically created for the same variable (in AnyLogic such datasets are auto-generated and have prefix “_ds_”<; in case you have a user-defined dataset, you should obviously use that dataset’s name).

The structure of the object returned by getValue() depends on its type. For primitive types it is quite straightforward, for complex types please consult the Data conversion section to understand the Java -> JSON conversion rules. In this example, the AnyLogic DataSet Java object is converted into a JSON object with dataX and dataY fields. Should we have a custom chart (like Plotly or Tableau chart) as a part of our frontend, we could feed that data into the chart to visualize it, possibly with live updates of certain frequency.

3.3 JavaScript API reference

AnyLogic JavaScript API is purely asynchronous (see Synchronous and asynchronous API), therefore it makes sense to familiarize yourself with JavaScript Promise technology.

Another thing to keep in mind is the API treats names (e.g. model, experiment, input, and output names) as case-insensitive.

CloudClient class

The CloudClient class is responsible for authentication and communication with the AnyLogic Cloud. Typically, there is only one object of class CloudClient in your JavaScript code.

static create(apiKey, host) a static function that creates the API client, given the API key and, optionally, the host name. If the host is omitted, the public cloud host name https://cloud.anylogic.com is assumed.

getModels() returns available models as an array of Model objects by Promise.

getModelById(id) returns the Model object with a given id by Promise.

getModelByName(name) returns the Model object with a given name by Promise.

getModelVersionById(model, versionId) returns the Version object of a given Model with a given id by Promise.

getModelVersionByNumber(model, versionNumber) returns the Version object of a given Model with a given number by Promise (version numbering starts with 1).

getLatestModelVersion(modelorname) returns the latest Version object of a given model (which can be either a Model, or a model name) by Promise.

createDefaultInputs(version) creates and returns an Inputs object for a given model Version with default input values.

createInputsFromExperiment(version, experimentName) creates and returns by Promise the Inputs object for a given model Version object copied from the experiment with the given name.

createSimulation(inputs) creates and returns a ModelRun object of type SIMULATION with the given Inputs (the model and the version are identified by the inputs).

createParameterVariation(inputs) creates and returns a ModelRun object of type PARAMETER_VARIATION with the given Inputs (the model and the version are identified by the inputs).

startAnimation(inputs, divId) starts an animated model run with the given Inputs (which fully identify the model and the model version), and embeds the animation into the HTML element with the given id. Constructs and returns the corresponding Animation object by Promise.

Inputs class

An object of Inputs class is constructed in preparation of a model run (of any kind) by calling the CloudClient functions createDefaultInputs() or createInputsFromExperiment() and contains full information about the model, model version, and the input values. It should not be confused with the inputs field in the Version object.

getInput(name) returns the value (an object) of the input with a given name. See Data conversion section for possible types.

setInput(name, value) sets the value of the input with a given name.

setRangeInput(name, min, max, step) - sets a range for the input with a given name (in a parameter variation experiment).

Inputs of distribution type (for Monte Carlo 2nd order experiments) are coming in future releases of Anylogic Cloud API.

SingleRunOutputs class

An object of this class is returned after a call of getOutputs() or getOutputsAndRunIfAbsent() of a ModelRun constructed for a single run simulation experiment.

names() returns the array with all output names.

findNameIncluding( namePart ) searches for an output name that has namePart as a substring and returns it. If there is no such name or more than one such name is found, throws Error. This function is useful because full names of the outputs may be complex, see Outputs data object.

value( name ) returns the value of the output with a given name. The type of value depends on the output, see Data conversion section.

getRawOutputs() returns an array of all output items, each item has fields name, type, units, and value. For possible values of type and units field see Output types and Units sections correspondingly. The value field contains an object constructed as described in Data conversion section. This is an example of raw outputs:

[
    {
        name: "Queue size stats",
        type: "STATISTICS_CONTINUOUS",
        units: null,
        value: {
            count: 1255584,
            max: 7,
            mean: 0.9988466028875719,
            min: 0,
            totalTime: 999999.2699032243,
            type: "CONTINUOUS",
            variance: 0.0027334062484944965
        }
    },
    {
        name: "Total time in system|Total time in system",
        type: "HISTOGRAM_DATA",
        units: null,
        value: {
            hits: (20) [800912, 159870, 29594, 5804, 3399, 1073, 257, 56, 12, 9, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
            hitsOutHigh: 0,
            hitsOutLow: 0,
            intervalWidth: 1.6,
            lowerBound: 1.6,
            statistics:{
            count: 1000987,
                max: 18.533258249757637,
                mean: 2.5012383408878067,
                min: 1.6001570096705109,
                type: "DISCRETE",
                variance: 1.2835661096259896
            }
        }
    },
    {
        name: "Utilization|Server utilization",
        type: "DOUBLE",
        units: null,
        value: 0.31275860811685163,
    },
    {
        name: "Mean queue size|Mean queue size",
        type: "DOUBLE",
        units: null,
        value: 0.9988466025848514
    }
]

MultiRunOutputs class

An object of this class is returned after a call of getOutputs() or getOutputsAndRunIfAbsent() of a ModelRun constructed for a parameter variation or other multi run experiment. It simplifies navigation within the complex outputs structure. Keep in mind that you need to explicitly specify the required outputs when calling getOutputs() functions.

getInputNames() returns the array of names of inputs that are varied across runs.

getOutputNames() returns the array of names of requested outputs.

getValuesOfInput( name ) returns the array of values of the input with a given name across all runs in some fixed sequence. Note that you can only query values of those inputs that have been varied, fixed inputs are not stored in MultiRunOutputs.

getValuesOfOutput( name ) returns the array of values of the output with a given name across all runs in some fixed sequence. This function can be used together with getValuesOfInput().

getRawData() returns a table (a two-dimensional array) with values of all variable inputs and all outputs with a header row. For example, here is the raw data of a parameter variation experiment with one variable parameter Mean service time and one scalar output Utilization|Server utilization :

[
    ["Mean service time", "Utilization|Server utilization"],
    [1.8, 0.5621987153519676],
    [1.9, 0.5939408971748594],
    [2, 0.6253419155200399]
]

ModelRun class

The ModelRun class is responsible for communication with and control of a (animation-less) model run executed in the Cloud. It can be considered as a frontend mirror of a backend experiment run. Objects of class ModelRun are created and returned by calling the functions createSimulation() or createParameterVariation() of the CloudClient. A ModelRun object contains full information about the model, version, inputs, and experiment type.

run() requests to run the experiment. Whether or not the simulation will actually be executed depends on the availability of the outputs. The function returns the same ModelRun object by Promise once the HTTP request completes; it does not wait for simulation completion or outputs availability and does no polling.

stop() requests to stop the model execution. Returns the same ModelRun object by Promise once the HTTP request completes.

waitForCompletion( pollingPeriod ) waits for the experiment to complete and returns the same ModelRun object by Promise. The pollingPeriod parameter is optional, the default value is 5000ms.

getStatus() returns the status of the model execution as last updated by polling (does not initiate any extra communication with the server). Possible values are the same as described in the Experiment Run State section.

getProgress() returns the fully parsed message field of the Experiment Run State object by Promise. To find out total experiment progress use getProgress().then( progress => progress.total).

getOutputs( requiredOutputNames ) if the run has already been completed, returns the run outputs (either SingleRunOutputs or MultiRunOuptputs object) by Promise, otherwise throws 404 Error. requiredOutputNames is the array of output names that are to be returned. If requiredOutputNames is omitted, the behavior is different for single and multi-run experiments: for a single run, all outputs are returned, for a multi run only outputs of scalar types are returned.

getOutputsAndRunIfAbsent( requiredOutputNames, pollingPeriod ) if the run has already been completed, returns the run outputs (either SingleRunOutputs or MultiRunOuptputs object) by Promise, otherwise requests to run the experiment, waits for completion by polling, and then returns the outputs by Promise. requiredOutputNames has the same meaning as in getOutputs(). The pollingPeriod parameter is optional, the default value is 5000ms.

Animation class

The Animation class is responsible for communication with and control of an animated simulation run. Object of class Animation is created and returned by calling the functions startAnimation() of the CloudClient.

stop() requests to stop the model execution. Does not return anything.

pause() requests to pause the model execution. Returns the same Animation object by Promise.

resume() requests to resume the (paused) model execution. Returns the same Animation object by Promise.

setSpeed(speed) requests to set the execution speed of the model to a given value (model time units per real second). Returns the same Animation object by Promise.

setVirtualTime() requests to switch to the virtual time (fastest possible) execution mode. Returns the same Animation object by Promise.

navigateTo(viewArea) requests to navigate to a view area with the given name. Returns the same Animation object by Promise.

setPresentable(pathToPresentable) requests to navigate to a given agent. The path should start with experiment.root followed by Java path to the agent (if that is not root). Returns the same Animation object by Promise.

setValue(pathToField, value) requests to set the value of a given object in the model (e.g. a parameter or a variable). The path to the object should look like experiment.root.agent1.myParameter. Returns the same Animation object by Promise.

getValue(pathToField) requests the value of a given object in the model and returns it by Promise. The path to the object should look like experiment.root.agent1.myParameter . The returned value is a JavaScript object, please see Data conversion section to learn how Java objects are mapped to JSON objects.

getState() requests the state of the animated model being executed and returns it by Promise. Please do not confuse the state of animated model with the status of the non-animated run that is returned by ModelRun.getStatus(). The returned value will be one of:

  • "IDLE"
  • "RUNNING"
  • "PAUSED"
  • "FINISHED"
  • "ERROR"
  • "PLEASE_WAIT"
  • "INITIALIZE"
  • "STOPPED"
  • "TIME_LIMIT"
  • "TERMINATED"

callFunction(pathToFunction, args) requests to call a function in the model, possibly providing arguments. Returns what the function returns, by Promise. The path to the function should look like experiment.root.agent1.myFunction. args is a JavaScript array of the function argument objects, see Data conversion section to learn how Java objects are mapped to JSON objects.

waitForCompletion() waits for animated simulation to complete and returns the same Animation object by Promise. Note that even if the simulation run has finished (has reached the stop time), it will not be considered as completed until the use presses Stop, or stop() method has been called, or the server terminates the simulation due to an error.

3.4 Writing HTML and CSS for AnyLogic Cloud things to keep in mind

If you are going to embed model animation in your web page, please make sure there is no CSS style and HTML id intersection between your part of the page and embedded AnyLogic animation part. The CSS styles used for model animation can be found in the presentation-html.css and presentation-svg.css files located in the assets/svg/css directory, and the HTML ids in the svg-template.html file located in assets/svg. Please note that 2D animation of uses numeric ids for SVG elements, therefore you should not use numeric ids in the rest of the page.

3.5 AnyLogic engine API related to Cloud

You can, to a certain extent, adjust the look and feel of AnyLogic model animation embedded in custom web pages by using AnyLogic Java API.

These methods of the ExperimentHost class enable and disable controls on the Control panel:

void setRunControlEnabled( boolean runControlEnabled ) enables or disables Run, Pause, and Stop buttons on the Control panel (but not on the Developer panel).

void setSpeedControlEnabled( boolean speedControlEnabled ) enables or disables all controls related to simulation speed on the Control panel (but not on the Developer panel).

4 Java API

Documentation on AnyLogic Cloud Java API is coming soon.

5 Python API

Documentation on AnyLogic Cloud Python API is coming soon.

6 Data conversion between the model and the API client

One of the advantages of AnyLogic Cloud API is that it abstracts away from the language that was used to develop the model (Java in case of Anylogic) and allows you to interact with the model using multiple different languages on the client side. With the API, you can set up the model inputs, read outputs, and pass and retrieve data to and from the running model. Therefore, data conversion is a part of the API. In general, it works this way:

  • JSON is used as a universal data format throughout AnyLogic Cloud API; JSON is the format directly used in REST API
  • Jackson is used to convert data between Java and JSON. Conversion examples are given in the table below. To ensure safe reliable conversion for your own Java classes you should follow some simple rules also described below. You should keep in mind that while converting from JSON to Java, Jackson knows the target data type and will try to match the incoming string accordingly; whereas when converting from Java to JSON the target type is not known.
  • There are a few exceptions when conversion from Java to JSON is done in a special way (not with the help of Jackson), namely, these are the data objects from the Analysis palette in AnyLogic: DataSet, HistogramData, Histogram2DData, StatisticsContinuous, StatisticsDiscrete. Objects of those classes are considered as model output only and cannot be passed in the reverse direction (from the API client to the model).
JSON Java
Primitive types
Number double, float, int, long, etc. (any numeric type)
Boolean boolean
String String

Exception: AnyLogic classes with specific Java to JSON conversion, reverse conversion is NOT possible

{
    dataX:[1,2,3,4],
    dataY:[10,20,30,40]
}
DataSet
{
    hits: [0,0,0,1,5,67,335,441,138,13],
    hitsOutHigh: 0,
    hitsOutLow: 0,
    intervalWidth: 1.2,
    lowerBound: -8.6,
    statistics:{
        count: 1000,
        max: 2.83,
        mean: 0.0005,
        min: -3.91,
        type: "DISCRETE",
        variance: 0.959
    }
}
HistogramData
{
    hits: [
        [8, 4, 4, 4, 9],
        [5, 6, 8, 2, 1],
        [3, 8, 7, 6, 8],
        [7, 8, 5, 7, 9],
        [3, 4, 3, 9, 6],
    ],
    hitsOutHigh: [0,0,0,0,0],
    hitsOutLow: [0,0,0,0,0],
    xMax: 10,
    xMin: 0,
    yMax: 1,
    yMin: 0
}
Histogram2DData
{
    type: "CONTINUOUS",
    count:1375730,
    min:0.0,
    max:10.0,
    mean:0.9915478405530092,
    variance:0.14075328464389159,
    totalTime:999999.2699032243
}
StatisticsContinuous
{
    type: "DISCRETE",
    count: 1000,
    max: 2.83,
    min: -3.91,
    mean: 0.0005,
    variance: 0.959
}
StatisticsDiscrete
Standard complex types Jackson rules apply, for example:
Formatted String,e.g. "2019-05-13T15:34:03.976" Date
Date Date
[12.5, 34,156.9] array of numbers double[]
["red", "white","blue"] array of Strings ArrayList<String>
Just convert it to JSON using the API and explore the result to find out the structure Any complex class
User-defined classes: Jackson rules apply for example:
{ name:"John",age:33 }
public class Person {
    private String name;
    private int age;

    public String getName() { return name; }

    public int getAge() {return age; }

    public void setName(String name ) {
    this.name = name;
    }

    public void setAge(int age ) {
    this.age = age;
}
}
{ name:"John", age:33 }
public class Person {
    public String name;
    public int age;
}

To make sure you have safe bidirectional JSON Java conversion for your custom objects, you should follow simple rules:

  • Class fields that you wish to pass should either be declared public in Java or, if they are private, must have public setters and getters
  • There should be a default public constructor available in the Java class

7 Synchronous vs asynchronous API

There are two types of API for working with AnyLogic Cloud: synchronous and asynchronous.

  • In synchronous API, methods wait for underlying operations to complete, e.g. for HTTP requests or for server-based simulation runs. The advantage of synchronous API is ease of use: as the methods complete the required action and return the actual results, you can use straightforward control flow. The disadvantage is that such methods block the thread where they are called. Therefore, it is not good to use synchronous API where responsiveness is required or where multithreading is not available.
  • In asynchronous API, methods do not wait for completion of time-consuming operations (and thus do not block the thread) and complete immediately. They return objects where you can provide callback code to be executed when the response comes and results are available, such as Promise in JavaScript or CompletableFuture in Java.

AnyLogic Cloud JavaScript API is purely asynchronous, and Java API has both synchronous and asynchronous methods for your convenience.