The aimmspy Python Module

The main features of the aimmspy Python module are:

  • a seamless integration with existing AIMMS models from within a Python script

  • assign and retrieve data using Python dictionaries, Pandas, Polars or Arrow date frames

  • execute AIMMS procedures and retrieve results programmatically

The aimmspy library has been build with pybind11 for high-performance C++ integration.

Installation instructions

To install the aimmspy module, you can use pip:

pip install aimmspy

However, in case you execute your Python script from within an AIMMS procedure (i.e. using pyaimms), you do not need to install the aimmspy module. It is sufficient to add the aimmspy reference in your project’s .toml file. This will make sure that aimmspy is installed in the virtual Python environment that will be set up.

Connecting to an AIMMS project

To connect your Python script to an (already existing) AIMMS model, you will need to initialize an instance of the Project class by providing:

  • the path of the AIMMS executable

  • the path to the project file

  • the license url

You can download a pre-made example from the link below:

transport_optimization.7z

Instead of the path to the AIMMS executable you can also provide just the AIMMS version number and use the function find_aimms_path (available in the aimms.utils module) to figure out the location of the path to the AIMMS executable.

If you have already a working, licensed AIMMS setup on your computer, there is no need to specify the license_url in the constructor of the Project instance.

After having created a project instance, you can retrieve the actual model by calling get_model. The returned model instance provides access to the identifiers in the model.

  import os
  from aimmspy.project.project import Project, Model
  from aimmspy.utils import find_aimms_path

  # Initialize the AIMMS project
  project = Project(
      # path to the AIMMS Bin folder (on linux the Lib folder)
      aimms_path=find_aimms_path("25.4.3.3"),

      # path to the AIMMS project
      aimms_project_file=R"C:\AimmsProjects\MyAimmsProject\MyAimmsProject.aimms",

      # url
      license_url=R"wss://licensing.aimms.cloud/license-ws/license?profile=community&license=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
  )

aimms_model: Model = project.get_model(__file__)

Of course, in case you are using a free AIMMS community license, you will need to replace the license code in the above snippet, by the license code of your community license.

Important

You can only connect to a single AIMMS project from within a Python script.

Identifier references

Identifier references (as an attribute of an instance of the project class) are case-sensitive. For example, if your AIMMS project contains an identifier Pi and, in your Python script, you created a reference to the associated model project with name aimms_model, you will need to write aimms_model.Pi.data() to retrieve the data for the identifier Pi. Writing aimms_model.pi.data() will show a Python _AttributeError_ with message

'Model' object has no attribute 'pi'. Did you mean: 'Pi'?.

Namespace prefixes (e.g. for AIMMS identifiers inside a library) need to be separated by a dot character character.

Note that you will need to declare the library identifiers that you want to access in your Python script as public identifiers (on the attribute form of the library node in the AIMMS model tree).

Return data types

In this section we will focus on the data types that are used in Python to represent AIMMS data. For simple or medium sized AIMMS data you can use Python dictionaries. For large sized AIMMS data you might be better of using Python data frames.

Indexed data can be represented in

Python dictionaries are a simple and straight-forward way to contain data for a single identifier. However, when the data size gets really big and/or you want to store the data of multiple (similar) identifiers in the same data structure, it is recommended to use a data frame to store your data.

The default data return type for multi-dimensional data can be specified during project construction. It is possible to override the default return type on a per-statement level. More details about this in the section on [Retrieving data](#retrieving-data)

import os
from aimmspy.project.project import Project, Model
from aimmspy.utils import find_aimms_path

# Initialize the AIMMS project
project = Project(
    # path to the AIMMS Bin folder (on linux the Lib folder)
    aimms_path=find_aimms_path("25.4.3.3"),

    # path to the AIMMS project
    aimms_project_file=R"C:\AimmsProjects\MyAimmsProject\MyAimmsProject.aimms",

    # default data type when retrieving multi-dimensional data
    data_type_preference= DataReturnTypes.PANDAS
)

To communicate with scalar identifiers, plain Python string and float values are used. Not surprisingly, values of scalar AIMMS string parameters and scalar element parameters are represented as a string in Python. A scalar element parameter in a calendar however, will return a Python datetime value. Values of scalar numerical identifiers are represented as a float in Python.

Python dictionaries

When representing AIMMS data in a Python dictionary, we assume the following:

  • the keys in the dictionary are the tuples for which the identifier has non-default data

  • the value type of the values in the dictionary depends on the identifier type in the AIMMS model

Python data frames

When working with AIMMS data that is large or complex, it is often more efficient to use data frames.

For data transfer of large sized data, the aimmspy Python module has support for two popular (and comparable) data frame libraries, being

Note

Representation of special values

Special values (in AIMMS) are currently represented in Python in the following way

  • the AIMMS value inf is translated into a Python float with value 1e+150

  • the empty label (in AIMMS) is translated into an empty Python string

  • the AIMMS values zero, undf and na are translated to a Python float with value 0.0

Dealing with default values

The translation of AIMMS data to Python data will be executed sparse, meaning that only non-default values will be retrieved from AIMMS. Currently you can only retrieve data for a single identifier at a time. For multi-dimensional data this means that no default values will be communicated to Python. Scalar data with a default value are communicated to Python as values of type float or string, even in case these scalar represent a default value.

When retrieving data from multi-dimensional AIMMS data identifiers with a non-default default value, you will have to deal with that in your Python script yourself (to ensure that all calculations in your Python script are correct).

Dealing with units

Numerical values are communicated (both-ways) in the unit of the corresponding AIMMS identifier. As in any AIMMS model, you can use conventions to change the identifier unit. This would allow you to input data in, say, ‘kilometers’ while, after having changed the (model) convention, retrieve the same value in ‘miles’.

Reference manual

Project

Project(aimms_path, aimms_project_file, exposed_identifier_set_name=None, license_url=None, data_type_preference=None)

Project class constructor

Arguments
  • aimms_path – the path to the AIMMS executable. You can use the find_aimms_path utility function to find the path for a specific AIMMS version

  • aimms_project_file – the path the the AIMMS project file

  • exposed_identifier_set_name – optional, a model set (subset of AllIdentifiers) that contains all model identifiers that are available in your Python script. Defaults to AllIdentifiers if not specified

  • license_url – optional, a reference to an AIMMS community cloud license url of the form wss://licensing.aimms.cloud/license-ws/license?profile=community&license=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, whereas the xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx part needs to be replaced by the code of your AIMMS community license.

  • data_type_preference – optional, the default return type when retrieving data from non-scalar data identifiers (by calling .data() on the associated Identifier instance). Should be one of DataReturnTypes.PANDAS, DataReturnTypes.POLARS, or DataReturnTypes.ARROW. Defaults to DataReturnTypes.DICT if not specified

A Project instance provides access to the AIMMS model through

get_model()

returns an instance of the Model class

Important

In model with a lot of identifiers, you are advised to use the exposed_identifier_set_name parameter to restrict the accessible identifiers to those that are needed in your Python script.

Model

The Model instance is retrieved through the get_model method of the Project instance. During construction of the Model instance, the aimmspy library will automatically create a stub file that is can be to support you while editing your Python script using Visual Studio Code.

To do so, when you are creating a Python script with name my_script.py, the aimmspy library will create a stub file with name my_script.pyi in the same directory as your Python script. This stub file contains all identifiers that are available in the AIMMS model. To actually use the stub file, you should extend your script with something like

from typing import TYPE_CHECKING
if TYPE_CHECKING:
    from my_script import Model

The stub file will ensure that you can refer to any of the exposed identifiers as if it was an attribute of the Model instance.

DataIdentifier

Assigning data

To assign data from Python objects to AIMMS identifiers two methods are available on instances of type DataIdentifier

assign(data, options=None)

Assigns data to the identifiers

Arguments
  • datadata should be an int, float, a string, a datetime object, a dictionary, a Pandas or a Polars data frame, or an Arrow table

  • options – a dictionary with options that can be used to control the behavior of the assignment

Available options

  • update: If set to False (default), the identifier will be emptied before the assignment is executed. If set to True, the behavior is identical to that of the update method

  • extendType: Should be one of the following (string) values:

    • extend (default): Elements that are not in a domain set yet, will be automatically added (assumed the set is updatable)

    • error: An AimmsPyException will be thrown when encountering an element that is outside of one of the domain sets

    • filter: Tuples with an element that is outside the domain will be silently ignored

  • mapping: A string-to-string map in which the key should correspond to a column name in the date frame and the corresponding values should refer to the associated identifier name in the AIMMS model.

    The options can be combined in a dictionary, for example:

    { "update": True, "extendType": "filter", {"mapping": { "MyColumnName": "MyIdentifierName"} }
    

For example:

  num_inhabitants_data_df = pd.DataFrame({
    "c": [ 'Amsterdam', 'Haarlem' ],
    "Number of Inhabitants": [ 741636, 147590 ]
})

aimms_model.NumInhabitants.assign(num_inhabitants_data_df, { "update": True, "mapping": { "Number of Inhabitants": "NumInhabitants"} })
update(data, options=None)

Assigns data to the identifiers. Same as data with update option set to True

Retrieving data

data(options=None)

To retrieve data from AIMMS identifiers, you can use the .data() method on an instance of type DataIdentifier. The return value will be a float, string or a datetime object for scalar identifiers, and a dictionary, a Pandas or Polars data frame, or an Arrow table for multi-dimensional identifiers.

Arguments
  • options – optional, a dictionary with options that can be used to specify the return type of the data

For example, to enforce the return type to be a Pandas DataFrame, you can use the following for the options argument

{ "return_type": DataReturnTypes.PANDAS }

Using this for the NumInhabitants identifier that we used in the previous session would for example look like

num_inhabitants = aimms_model.NumInhabitants.data({ "return_type": DataReturnTypes.PANDAS })
print(f"num_inhabitants:\n{num_inhabitants}")

which would yield the following output

num_inhabitants:
           c  NumInhabitants
0  Amsterdam        741636.0
1    Haarlem        147590.0

The default return type for multi-dimensional identifiers can be set during the construction of the Project instance.

Running procedures

Procedure can be executed directly using the Model instance. Just create a reference to the procedure and add to brackets (in case of a procedure without arguments) to trigger execution.

So far, we only support scalar input arguments for procedures. The order of the arguments needs to match the order in which they are declared in the model. The return value will be the return value of the AIMMS procedure.

For example:

retval = aimms_model.SetInterestRate(rate=1.06)

Exceptions

The aimmspy module introduces two custom Exceptions

  • AimmsException: Exceptions that are generated in AIMMS itself, either raised from model or due to some other internal issue.

  • AimmsPyException: Exceptions that are raised in the aimmspy Python module, i.e. the part of the code that is responsible for the communication between Python and AIMMS.

    For example,

    additional_num_inhabitants_data = pd.DataFrame({
        "c": [ 'Invalid City' ],
        "NumInhabitants": [ 123456 ]
    })
    
    try:
        aimms_model.NumInhabitants.assign(additional_num_inhabitants_data, {"update": True, "extendType": "error"})
    except AimmsPyException as e:
        print(f"Invalid set element raised an AimmsPyException (as expected): {e}")
    

    would yield the following output:

    Invalid set element raised an AimmsPyException (as expected): Invalid element 'Invalid City