Deploying Your Algorithm

This section describes how to use Aion generated object files and dynamic libraries in your application or product.

API Namespace

All user accessable classes and functions are either defined in the namespace Model or, in a very few cases, defined outside of any namespace. Defines will be prepended with the prefix MODEL_ to help avoid collisions with other user defined functions, defines, etc.

You will occasionally see references to the namespace ‘’M’’ as well as classes defined within the namespace M. You are strongly urged not to use any of these classes or functions as their API and associated ABI are undocumented and will be unstable.

Inesonic, LLC will make a best effort to maintain a consistent API and ABI for all members of the namespace Model.

An Overview Of Your Algorithm or Model’s API

All algorithms or models that are exported by Aion will use the same API. You can use the headers provided with Aion to access and use your work.

Table 22 lists where you can find the key headers for your installation. Note that the paths listed are relative to the Aion installation directory.

Table 22 API Header Locations

Platform

Location

Windows

%AION_ROOT%\include

Linux

${AION_ROOT}/include

MacOS

${AION_ROOT}/Contents/Resources/include

All headers begin with the prefix model_ and, as much as possible, are purpose specific. The contents of the headers can be broken down into three broad categories, documented in the following sections.

For detailed API reference documentation, please refer to API Reference.

Core Algorithm/Model API

You can use the classes, enumerations, types, and static variables defined in this section to:

  • instantiate instances of your algorithm or model,

  • query and set variables,

  • locate and call functions you’ve created as part of your model,

  • and run your algorithm or model.

Algorithm Model API Classes briefly describes the key classes you can use as well as the files where those classes are defined.

Table 23 Algorithm/Model API Classes

Class (In Namespace Model)

Defined In

Provides

Api

model_api.h

A common base class used by Aion to control your algorithm or model.

IdentifierData

model_identifier_data.h

Class that encapsulates data about a single identifier used by your model.

IdentifierDatabase

model_identifier_database.h

A database containing information about all the identifiers in your algorithm or model.

IdentifierDatabaseIterator

model_identifier_database_iterator.h

An iterator you can use to traverse IdentifierData entries in an IdentifierDatabase.

Rng

model_rng.h

A friendly interface to the Aion random number generator.

Status

model_status.h

A class you can overload to receive callbacks related to state changes in your model or algorithm.

Table 25 briefly lists key non-class types you may need to use as well as the files where each type is defined.

Table 25 Algorithm/Model API Types

Type (In Namespace Model)

Defined In

Provides

AbortReason

model_api_types.h

An enumeration indicating the reason your algorithm or model aborted.

Device

model_api_types.h

An enumeration of supported input/output devices.

IdentifierHandle

model_api_types.h

Type used to reference an identifier by numeric value.

OperationHandle

model_api_types.h

Type used to reference a code breakpoint by numeric value. value. Values are used to map mathematical expressions to instruction addresses.

ValueType

model_api_types.h

Enumeration of supported data types.

State

model_api_types.h

Enumeration of current algorithm/model operating states.

Table 26 briefly lists key namespace scope and global functions that you may need to use.

Table 26 Algorithm/Model API Functions

Function

Defined In

Provides

allocator

A function that will allocate a new model/algorithm instance.

deallocator

A function that will deallocate a model/algorithm instance, freeing memory.

Table 27 briefly lists namespace scope constants you may need to use.

Table 27 Algorithm/Model API Constants

Constant (In Namespace Model)

Defined In

Provides

invalidOperationHandle

model_api_types.h

A value you can use to indicate or identify an invalid operation handle.

invalidIdentifierHandle

model_api_types.h

A value you can use to indicate or identify an invalid identifier handle.

Table 28 briefly lists defines that you may need to be aware of.

Table 28 Algorithm/Model API Defines

Define

Defined In

Provides

MODEL_PUBLIC_API

model_common.h

Support for declspec(dllimport) and declspec(dllexport on Windows.

MODEL_PUBLIC_TEMPLATE_METHOD

model_common.h

A work-around to allow for template API functions tied to Windows DLLs.

Error Handling API

You can use the classes, enumerations, types, and static variables defined in this section to detect and respond to various run-time errors that may be reported by your algorithm or model.

The sections that follow are organized by header file. Table 29 briefly describes the key classes you can use. Table 30 briefly lists key namespace scope functions you may need to use to enable and disable generation of certain types of exceptions.

Also note the enumeration Model::ExceptionClass defined in model_api_types.h and used to help identify exception types.

Table 29 Exception Classes

Class (In Namespace Model)

Defined In

Provides

CanNotConvertToString

model_exceptions.h

Exception raised when a tuple is expected to contain a string but either contains non-integer entries or integers that do not map to valid code points.

FileCloseError

model_exceptions.h

Exception raised when a file can-not be closed.

FileError

model_exceptions.h

Base class for all file errors.

FileOpenError

model_exceptions.h

Exception raised when a file can-not be opened.

FileReadError

model_exceptions.h

Exception raised when a file can-not be read.

FileSeekError

model_exceptions.h

Exception raised when a file seek operation fails.

FileWriteError

model_exceptions.h

Exception raised when a file can-not be written

InesonicException

model_exceptions.h

Base class for all Aion exceptions.

InsufficientMemory

model_exceptions.h

Exception that is raised if there is insufficient memory available for a data structure.

InvalidFileNumber

model_exceptions.h

Exception raised when an invalid file number is provided to one of the Aion file functions.

InvalidRuntimeConversion

model_exceptions.h

Exception that is raised when a run-time type conversion fails.

MalformedString

model_exceptions.h

Exception raised when a provided string is not UTF-8 encoded and can not be converted to Unicode.

UnknownFileType

model_exceptions.h

Exception raised by high-level file read/write functions when a file type is unrecognized or can-not be determined.

UserAbortRequested

model_exceptions.h

Exception that is raised on a user requested abort.

Table 30 Exception Functions

Function (In Namespace Model)

Defined In

Provides

disableExceptionClass

model_exceptions.h

Disables a specific exception type.

enableExceptionClass

model_exceptions.h

Enables a specific exception type.

Aion Data Types API

You can use the classes, enumerations, types, and static variables defined in this section to inject data into your algorithm or model and to extract and examine the output.

Table 31 briefly describes the key classes you can use to manipulate data.

Table 31 Classes To Manage Aion Data Types

Class (In Namespace Model)

Defined In

Provides

Complex

model_complex.h

The Aion complex type.

Matrix

model_matrix.h

Base class for all matrix types.

MatrixBoolean

model_matrix_boolean.h

Boolean matrix type.

MatrixComplex

model_matrix_complex.h

Complex matrix type.

MatrixInteger

model_matrix_integer.h

Integer matrix type.

MatrixReal

model_matrix_real.h

Real matrix type.

Range

model_range.h

Range pseudo-type.

RangeIterator

model_range_iterator.h

Range iterator.

Set

model_set.h

Set type.

SetIterator

model_set_iterator.h

Set iterator used to traverse sets.

Tuple

model_tuple.h

Tuple type.

TupleConstIterator

model_tuple_const_iterator.h

Tuple constant iterator.

TupleIterator

model_tuple_iterator.h

Tuple iterator.

TupleIteratorBase

model_tuple_iterator_base.h

Base class for all tuple iterators.

Variant

model_variant.h

A variant type used by the Set, Tuple, and IdentifierData classes.

Table 32 briefly lists key non-class types you can use to manipulate simple data types. Using these types will help guarantee portability between platforms and compatibility with future versions of Aion.

Table 32 Intrinsic Non-Class Types:

Type (In Namespace Model)

Defined In

Provides

Boolean

model_intrinsic_types.h

A boolean type you can use.

Integer

model_intrinsic_types.h

An integer type you can use.

Real

model_intrinsic_types.h

A real type you can use.

Exporting Your Algorithm

Aion gives you several options you can use to export your algorithm or model.

Exporting Your Algorithm As An Object File

You can use the File | Export | Object… menu option to export your algorithm or model as an object file. Selecting this menu option will bring up the Export Binary Object File Dialog shown in Figure 104.

../_images/export_binary_object_file_dialog.png

Figure 104 The Export Binary Object File Dialog

Simply select the location and name for the object file and click ‘’Export’’.

You can then link the exported object file against your application.

Exporting Your Algorithm As A Dynamic Library

On Windows, you can use the File | Export | DLL… menu option to export your algorithm or model as a dynamic library. The menu option will be named Dynamic Library… on MacOS and Shared Library… on Linux. Selecting this menu option will bring up the Export Dynamic Library Dialog shown in Figure 105.

../_images/export_dynamic_library_dialog.png

Figure 105 The Export Dynamic Library Dialog

Simply select the location and name for the object file and click ‘’Export’’.

On Windows, you may want to build the dynamic library against the correct Microsoft runtime libraries. You can use the C Runtime Import Library to select the correct import library for your application. Alternately, you can click ‘’Add’’ to select a runtime that is not already listed.

Linking Your Algorithm To Your Own Program

You will need to link your algorithm against your own program. How you do this will depend on how you exported your algorithm and how you intend to use it in your own application.

Linking Your Algorithm As An Object File Or Dynamic Library

If you exported your algorithm as an object file, as described in Exporting Your Algorithm As An Object File, you will simply need to include it on the list of objects you link into your application when you perform the link phase of your build. The exact process for doing this will depend on your choice of build tools.

Note that you must also link against the inem dynamic or static library provided with Aion.

Note

On Windows, you will also need to link against either the Microsoft MT or MTd runtimes.

Dynamically Loading Your Algorithm At Runtime

You can also dynamically load and unload your algorithm at runtime. You must first export your algorithm as a dynamic library as described in Exporting Your Algorithm As A Dynamic Library.

Once you do this, you can use the dlopen function on MacOS and Linux or the LoadLibraryA or LoadLibraryEx functions on Windows to load your algorithm into memory. Alternately, you can use a higher level APIs provided by libraries such as Boost or Qt.

You will also need to resolve the addresses of the allocator and deallocator functions. On MacOS and Linux, you can use the dlsym function to determine the function addresses. On Windows, you can use the GetProcAddress function.

The example C++ code fragment below demonstrates how to load your algorithm at run time and locate the addresses of the allocator and deallocator functions on Windows:

// Includes necessary headers

#include <Windows.h>
#include <string>
#include <model_api.h>

...

Model::AllocatorFunction allocatorFunction = nullptr;
Model::DeallocatorFunction deallocatorFunction = nullptr;

HMODULE moduleHandle = LoadLibraryA(filename.c_str());
if (moduleHandle != nullptr) {
    allocatorFunction = reinterpret_cast<Model::AllocatorFunction>(
        GetProcAddress(moduleHandle, Model::allocatorFunctionName)
    );

    deallocatorFunction = reinterpret_cast<Model::DeallocatorFunction>(
        GetProcAddress(moduleHandle, Model::deallocatorFunctionName)
    );

    if (allocatorFunction == nullptr || deallocatorFunction == nullptr) {
        FreeLibrary(moduleHandle);
        // *** Handle failure to locate function addresses ***
    }
} else {
    // *** Handle failure to load DLL ***
}

On MacOS and Linux, you can reference the example C++ code below:

// Include necessary headers

#include <dlfcn.h>
#include <string>
#include <model_api.h>

...

Model::AllocatorFunction allocatorFunction = nullptr;
Model::DeallocatorFunction deallocatorFunction = nullptr;

void* moduleHandle = dlopen(filename.c_str(), RTLD_NOW);
if (moduleHandle != nullptr) {
    allocatorFunction = reinterpret_cast<Model::AllocatorFunction>(
        dlsym(moduleHandle, Model::allocatorFunctionName)
    );

    deallocatorFunction = reinterpret_cast<Model::DeallocatorFunction>(
        dlsym(moduleHandle, Model::deallocatorFunctionName)
    );

    if (allocatorFunction == nullptr || deallocatorFunction == nullptr) {
        dlclose(moduleHandle);
        // *** Handle failure to locate function addresses ***
    }
} else {
    // *** Handle failure to load dynamic library ***
}

Instantiating And Destroying Your Algorithm

After linking your algorithm against your application as described in Linking Your Algorithm As An Object File Or Dynamic Library or dynamically loading your algorithm at run-time, you will need to instantiate an instance of your algorithm. You can do this by simply calling the allocator function. The allocator function will return a pointer to an instance of your algorithm.

If needed, you can instantiate multiple instances of your algorithm by calling the allocator function multiple times.

If you linked your algorithm against your program, you can instantiate an instance using the C++ sample code shown below:

extern "C" Model::Api* allocator();
Model::Api* algorithmInstance = allocator();

If you dynamically loaded your algorithm against your program, you can instantiate an instance of your algorithm using the C++ sample code shown below:

Model::Api* algorithmInstance = (*allocatorFunction)();

When you are finished using your algorithm, be sure to destroy your algorithm to release resources that it’s currently using. You can do this using the deallocator function.

If you linked your algorithm against your program, you can can destroy your algorithm using the C++ sample code shown below:

extern "C" void deallocator(Model::Api* algorithm);
deallocator(algorithmInstance);

If you dynamically loaded your algorithm against your program, you can destroy an instance of your algorithm using the C++ sample code shown below:

(*deallocatorFunction)(algorithmInstance);

Note

We discourge the use of the C++ delete operator to destroy your algorithm, especially on Windows, as doing so may cause issues due to differing heap management implementations in different Visual C runtimes. This is especially true between debug and release versions of the Microsoft runtime libraries.

Running Your Algorithm

You can run your algorithmInstance using one of several available methods.

Running Your Algorithm Using The Run Method

The simplest way to run your algorithm is to simply call the Model::Api::run method on your algorithm. The Model::Api::run method will block until the algorithm completes, returning true if the algorithm runs or false if, for some reason, the algorithm could not be run.

You can use the Model::Api::state method to determine if your algorithm was successful or if an error occured during execution. Return values will normally be either Model::State::STOPPED or Model::State::ABORTED if an error was detected.

The sample C++ code below demonstrates these calls:

// Include necessary headers

#include <model_api.h>
#include <model_rng.h>

...

// *** Change the random number generator settings as appropriate. ***
Model::Rng::RngType rngType = Model::Rng::MT19937;
Model::Rng::RngSeed rngSeed = { 1, 2, 3, 4 };

bool started = algorithmInstance->run(rngType, rngSeed);
if (started) {
    Model::Status algorithmStatus = algorithmInstance->state();
    if (algorithmStatus == Model::State:STOPPED) {
        // *** Algorithm finished ***
    } else if (algorithmStatus == Model::State::ABORTED) {
        // *** Algorithm failed ***
    } else {
        // *** This scenario indicates either an unexpected state or a state ***
        // *** triggered by a debugging function.                            ***
    }
} else {
    // The algorithm never started -- Was it already running ?
}

Running Your Algorithm In A Separate Thread

You can start your algorithm in a separate thread by calling the Model::Api::start method. This method will return true on success or false on error. Your code will continue to run while your algorithm runs in the background.

You can monitor the status of your algorithm using one of several different methods:

  • The simplest approach is to simply call the Model::Api::state method to determine the current running state of your algorithm. If the algorithm is active, the method will return Model::State::ACTIVE. If an error occurs, the method will return Model::State::ABORTED. If the algorithm has completed or was never started, the method will return Model::State::STOPPED. If an algorithm is in the process of shutting down in response to an error, the value Model::State::ABORTING will be reported.

  • You can create a C++ class that overloads the desired methods in the Model::Status class whenever a change in state occurs. Note that the methods in your derived class may be called from different threads so be sure to write your code in a thread-safe manner.

If, after some time, you wish to simply wait for your algorithm to complete, you can call the Model::Api::waitComplete method.

The sample C++ code below shows how you can use the Model::Api::state method to query the status of your algorithm.

// Include necessary headers

#include <model_api.h>
#include <model_rng.h>

...

// *** Change the random number generator settings as appropriate. ***
Model::Rng::RngType rngType = Model::Rng::MT19937;
Model::Rng::RngSeed rngSeed = { 1, 2, 3, 4 };

bool started = algorithmInstance->start(rngType, rngSeed);
if (started) {
    // Your algorithm is now running.

    while (algorithmInstance->state() == Model::State::ACTIVE) {
        // Continue with other activity...
    }

    Model::Status algorithmStatus = algorithmInstance->state();
    if (algorithmStatus == Model::State:STOPPED) {
        // Algorithm finished
    } else if (algorithmStatus == Model::State::ABORTED) {
        // Algorithm failed
    } else {
        // This scenario indicates either an unexpected state or a state
        // triggered by a debugging function.
    }
} else {
    // The algorithm never started -- Was it already running ?
}

The sample C++ code below shows how you can use the Model::Status class to receive callbacks.

// Include necessary headers

#include <model_api.h>
#include <model_status.h>
#include <model_rng.h>

...

class MyStatus:public Model::Status {
    public:
        MyStatus() {}

        ~MyStatus() override {}

        void started(Model::Api* algorithmInstance) override {
            // Method is triggered when the algorithm actually starts
            // execution.  You can use this to trigger events.  Note
            // that this method is triggered from the first thread
            // in your algorithm, not from the thread that called
            // Model::Api::start.
        }

        void finished(Model::Api* algorithmInstance) override {
            // Method is triggered when the algorithm actually finishes
            // execution.  You can use this to trigger events.  Note
            // that this method is triggered from the last thread in your
            // in your algorithm to terminate, not from the thread that
            // called Model::Api::start.
        }

        void aborted(
                Model::Api*            algorithmInstance,
                Model::AbortReason     abortReason,
                Model::OperationHandle operationHandle
            ) {
            // Method is triggered when the algorithm terminates due to an
            // error of some kind.  You can use this to trigger events.
            // Note that this method is triggered from an arbitrary thread
            // in your in your algorithm to terminate, not from the thread
            // that called Model::Api::start.
        }
}

...

// *** Change the random number generator settings as appropriate. ***
Model::Rng::RngType rngType = Model::Rng::MT19937;
Model::Rng::RngSeed rngSeed = { 1, 2, 3, 4 };
MyStatus            statusInstance;

bool started = algorithmInstance->start(rngType, rngSeed, &statusInstance);
if (started) {
    // Your algorithm is now running.

    ...
} else {
    // The algorithm never started -- Was it already running ?
}

The Aion Random Number Generator

Aion include support for generate of random deviates using one of five (5) available random number generation algorithms listed in Table 33.

Table 33 Supported Random Number Generation Algorithms

Algorithm

Model::Rng::RngType Enumeration

Mercenne Twister MT19937-64 PRNG

Model::Rng::RngType::MT19937

Mercenne Twiser SFMT216091 PRNG

Model::Rng::RngType::MT216091

XOR-Shift-Rotate 256++ PRNG

Model::Rng::RngType::XORSHIRO256_PLUS

XOR-Shift-Rotate 256** PRNG

Model::Rng::RngType::XORSHIRO256_STARS

OS Provided Cryptographic TRNG

Model::Rng::RngTYpe::TRNG

Note that the cryptographic TRNG is substantially slower than the other random number generation algorithms and should only be used if you truely need a true random number generator tied to functions used to generate random variates. Also note that Aion provides a separate API for the TRNG via the method Model::Rng::trng() that you can use in conjunction with other random number generators, if desired.

All pseudo-random number generators (PRNGs) accept a seed containing four (4) 64-bit integer values.

You can use the Model::Api::createRng method to create instances of the random number generator that you can use. Similarly, use the Model::Api::deleteRng method to deallocate a Model::Rng instance.

Note

Always use the Model::Api::createRng class to instantiate instances of Model::Rng. Never instantiate a new instance of Model::Rng on the stack or via the C++ new operator. Doing so will cause unexpected results.

Once you create an instance of a random number generator, you can freely use the supplied methods to generate random variates in different distributions.

Note

The Model::Rng class provides only a subset of the random number generation capabilities used internally. Aion leverages a number of additional capabilities using the same underlying engines.

The code fragment below shows how you can instantiate and use the Model::Rng class within your own code.

// Include necessary headers

#include <model_api.h>
#include <model_rng.h>

...

// *** Change the random number generator settings as appropriate. ***
Model::Rng::RngType rngType = Model::Rng::MT19937;
Model::Rng::RngSeed rngSeed = { 1, 2, 3, 4 };

// Instantiate an RNG.
Model::Rng* rng = algorithmInstance->createRng(rngType, rngSeed);

// Calculate some pseudo-random numbers
Model::Real randomUniform1 = rng->randomInclusive(); // Over the range [0, 1]
Model::Real randomUniform2 = rng->randomExclusiveInclusive(); // Over the range (0, 1]
Model::Real randomUniform3 = rng->randomInclusiveExclusive(); // Over the range [0, 1)
Model::Real randomUniform4 = rng->randomExclusive(); // Over the range (0, 1)

Model::Real randomExponential = rng->randomExponential(10.0);

...

algorithmInstance->deleteRng(rng);

Accessing Variables And Functions

You can access variables and functions defined as part of your algorithm using a Model::IdentifierDatabase instance that is tied to the algorithmInstance you are working with. The Model::IdentifierDatabase allows you to locate Model::IdentifierData instances based on the name of a variable or function or on a numeric handle. You can also use the Model::IdentifierDatabase to iterate through all the available variables and functions.

The Identifier Database

To access the identifier database for an given algorithm or model, simply use the Model::Api::identifierDatabase method. Note that assigning one Model::Api::identifierDatabase instance to another is fast due to the use of copy-on-write semantics.

Once you have a Model::IdentifierDatabase instance you can query, you can use the Model::IdentifierDatabase::identifierDataByName method to obtain information about a variable or function using the name of the variable or function as defined in the document. Character strings you provide to this function should be UTF-8 encoded.

The C++ code sample below shows how you can obtain a Model::IdentifierDatabase from your algorithm/model instance and identify the type of the variables \(a _ x\), \(range\), and \(\alpha _ i\).

// Include necessary headers

#include <model_api.h>
#include <model_api_types.h>
#include <model_identifier_data.h>
#include <model_identifier_database.h>

...

// Obtain an identifier database instance to work with
Model::IdentifierDatabase database = algorithmInstance->identifierDatabase();

// Query for variable data.
Model::IdentifierData varax = database->identifierDataByName("a", "x");
Model::IdentifierData varRange = database->identifierDataByName("range");
Model::IdentifierData varAlphaI = database->identiferDataByName("\xCE\xB1", "i");

// Let's get the variable types
Model::ValueType varaxType = varax.valueType();
Model::ValueType varRangeType = varRange.valueType();
Model::ValueType varAlphaIType = varAlphaI.valueType();

You can also iterate through all the variables and functions using the Model::IdentifierDatabaseIterator class. This class operates much like a C++ STL forward iterator. The code sample below demonstrates the use of the Model::IdentifierDatabaseIterator.

// Include necessary headers

#include <iostream>

#include <model_api.h>
#include <model_api_types.h>
#include <model_identifier_data.h>
#include <model_identifier_database.h>

...

// Obtain an identifier database instance to work with -- same as before.
Model::IdentifierDatabase database = algorithmInstance->identifierDatabase();

// Iterate through the database
for (Model::IdentifierDatabase::iterator it : database) {
    std::cout << it->text1() << "_{" << it->text2() << "} - " << std::endl;
}

Identifier Data Entries

Each entry in the Model::IdentifierDatabase is stored and reported as a Model::IdentifierData instance. Like the Model::IdentifierDatabase class, the Model::IdentifierData class uses copy-on-write semantics so making copies of an instance is efficient.

You can use the Model::IdentifierData instance associated with a variable or function to query information about a variable or function and to update the value of a variable.

You can use the Model::IdentifierData::text1 and Model::IdentifierData::text2 methods to query the name of a variable or function. The returned strings will be UTF-8 encoded. For a variable such as \(\zeta _ x\), the returned strings would be ‘’\xCE\xB6’’ and ‘’x’’ for the methods text1 and text2 respectively.

You can use the Model::IdentifierData::isVariable and Model::IdentifierData::isFunction methods to determine if an identifier is referencing a variable or a function.

For variables, you can use the Model::IdentifierData::valueType method to determine the type of a variable. Returned values are defined in the enumeration Model::ValueType, defined in model_api_types.h.

You can query the current value of a variable using the Model::IdentifierData::value method. This method will return a Model::Variant type discussed in :ref:Aion Variant Type.

Variable Entries

You can update the value of a variable using the Model::IdentifierData::setValue method. Note that variables are not initialized automatically to default values when you run an algorithm so you can force the value of variables before running them by using this method.

The sample C++ code below validates that a variable contains a complex value, queries the current value, then modifies the value.

// Include necessary headers

#include <model_api.h>
#include <model_api_types.h>
#include <model_variant.h>
#include <model_identifier_data.h>
#include <model_identifier_database.h>

...

// Obtain an identifier database instance to work with -- same as before.
Model::IdentifierDatabase database = algorithmInstance->identifierDatabase();
Model::IdentifierData varPAlpha = database.identifierDataByName("p", "\xCE\B1");

if (varPAlpha.valueType() == Model::ValueType::COMPLEX) {
    // Get the current value
    Model::Complex currentValue = varPAlpha.value().toComplex();

    // Then update the value with the complex conjugate
    varPAlpha.setValue(currentValue.conj());
} else {
    // *** Value wasn't complex type ***
}

Function Entries

You can obtain a pointer to a user defined function using the Model::IdentifierData::functionAddress method. You must manually cast the function to an appropriate type using a C++ reinterpret_cast.

User defined functions will always include the same two first parameters:

  • A pointer to the Model::Api instance,

  • and reference to a Model::Rng instance.

This is true even if the user defined function does not require the use of a random number generator.

After those two parameters, the function will include the parameters as defined in the document.

As an example, if we defined the function \(L _ q\) in our document as:

\[L _ q \left ( \lambda \in \mathbb{R}, \mu \in \mathbb{R} \right ) = \frac{\lambda ^ 2}{\mu \left ( \mu - \lambda \right )}\]

We could access the function from C++ using the sample code:

// Include necessary headers

#include <cassert>

#include <model_api.h>
#include <model_api_types.h>
#include <model_variant.h>
#include <model_identifier_data.h>
#include <model_identifier_database.h>

typedef Model::Real (*LQ)(Model::Api*, Model::Rng&, Model::Real, Model::Real);

...

Model::Real interArrivalRate = 20.0;
Model::Real serviceRate = 22.0;

// Obtain an identifier database instance to work with -- same as before.
Model::IdentifierDatabase database = algorithmInstance->identifierDatabase();
Model::Rng* rng = algorithmInstance->createRng();

// Obtain the function identifier data -- similar to before.
Model::IdentifierData lq = database.identifierDataByName("L", "q");

// Safety check that we're dealing with a function.
assert(lq.isFunction);

// Get the function address, cast to the function type.
LQ lqFunction = reinterpret_cast<LQ>(lq.functionAddress());

// Call the function.
Model::Real meanQueueDepth = (*lqFunction)(
   algorithmInstance,
   *rng,
   interArrivalRate,
   serviceRate
);

Using Aion Data Types

You can use any of the Aion data types from your own application using the types provided. In addition, Aion provides a wrapper for a variant type that can contain values of any of the other types.

Aion Intrinsic Types

The file model_intrinsic_types.h provides three typedefs you can use to safely manipulate boolean, integer, and real values. Those types are:

  • Model::Boolean

  • Model::Integer

  • Model::Real

The Aion Complex Type

You can use the Model::Complex type to work with complex values in Aion. The type is functionally very similar to the C++ std::complex<double> type. The Model::Complex class maintains the same memory footprint, most of the same capabilities, and a very similar API.

If you prefer, you can convert between the Model::Complex type and the C++ std::complex<double> type with essentially no overhead using a C++ reinterpret_cast as shown below. The functions below allows you to do this very easily.

// Include necessary headers

#include <complex>
#include <model_complex.h>

...

inline std::complex<double>& toStdComplex(Model::Complex& aionComplex) {
    return *reinterpret_cast<std::complex<double>*>(*aionComplex);
}

inline const std::complex<double>& toStdComplex(const Model::Complex& aionComplex) const {
    return *reinterpret_cast<const std::complex<double>*>(*aionComplex);
}

inline Model::Complex& toModelComplex(std::complex<double>& stdComplex) {
    return *reinterpret_cast<Model::Complex*>(*stdComplex);
}

inline const Model::Complex& toModelComplex(const std::complex<double>& stdComplex) const {
    return *reinterpret_cast<const Model::Complex*>(*stdComplex);
}

Note

Using a reinterpret_cast is possible and reliable in this scenario because the ISO14882 standard that governs the C++ language imposes requirements on the memory layout of complex values. By design, Inesonic, LLC also imposes the same requirements on its implementation.

Do note that different implementations of the C++ standard library implement their std::complex template class differently. This is especially true with each platform’s implementation of complex division. For this reason, results using std::complex<double> will vary very slightly by platform. The Model::Complex type exists to ensure consistent behavior on all platforms that support Aion.

The Aion Matrix Types

Aion provides four user accessible matrix classes that act as wrappers for the internal matrix implementations. The provided matrix classes offer significant capability you can leverage.

All Aion matrix classes derive from the Model::Matrix base class allowing you to query limited basic information about a matrix without knowledge of the type of data contained in the matrix.

The provided matrix classes use copy-on-write semantics allowing you to efficiently assign and copy matrix classes. See Copy On Write for details.

You can use the data methods found in each matrix class to convert between Aion matrix classes and other matrix implementations. See Algorithmic Complexity And Memory Usage for details on the internal memory layout of matrix data.

At this time, Aion only supports dense matrices. In future, Aion will support other memory organizations for matrices. To future proof your own product, you should use the method Model::Matrix::matrixType to verify that the matrix is using the expected memory structure.

Note

Due to the use of copy-on-write semantics with the matrix classes, the pointer returned by the data methods as well as the value returned by Model::Matrix::matrixType is ephemeral and is likely to change as soon as additional operations are performed on the matrix.

You should avoid using these values while your algorithm is running and should expect the value to change as soon as you perform any operation that might modify the data contained within the matrix.

The code example below shows several example basic operations on matrix classes.

// Include necessary headers

#include <cassert>

#include <model_api.h>
#include <model_api_types.h>
#include <model_variant.h>
#include <model_identifier_data.h>
#include <model_identifier_database.h>

#include <model_matrix_real.h>
#include <model_matrix_complex.h>

...

// Obtain an identifier database instance to work with -- same as before.
Model::IdentifierDatabase database = algorithmInstance->identifierDatabase();
Model::IdentifierData varA = database.identifierDataByName("A");
Model::IdentifierData varB = database.identifierDataByName("B");

// Confirm we're working with two real matrices.
assert(varA.valueType() == Model::ValueType::MATRIX_REAL);
assert(varB.valueType() == Model::ValueType::MATRIX_REAL);

// Matrix types and the Model::Variant type can perform type conversions
// between compatible types.  The line below will first obtain the matrix as
// a real matrix then up-cast it to a complex matrix.  Note that this type
// conversion can be time consuming for large matrices so don't do this
// unless you really need to.
Model::MatrixComplex A = varA.toMatrixReal();

Model::MatrixReal B = varB.toMatrixReal();

// Lets multiply our two matrices and divide by 4.  Note that the
// multiplication will also silently trigger an upcast on matrix B
// to a complex value in order to perform the multiplication.
Model::MatrixComplex C = A * B / 4.0;

// Perform a discrete fourier transform on C.
Model::MatrixComplex fC = C.dft();

The Aion matrix types also include the build static template method you can use to build matrices in a single line. Matrices must be built in column fast mode although you can use the transpose operation to reverse the order for readability at the expense of a tiny amount of additional overhead (see the section Lazy Evaluation regarding optimizations of the transpose operation). The C++ sample code below demonstrates the use of the build static template method.

// Include necessary headers

#include <cassert>

#include <model_intrinsic_types.h>
#include <model_matrix_real.h>

...

// Building directly
Model::MatrixReal A = Model::MatrixReal::build(
    5, 4, // 5 rows, 4 columns
    11, 21, 31, 41, 51,
    12, 22, 32, 42, 52,
    13, 23, 33, 43, 53,
    14, 24, 34, 44, 54
);

// Using transpose for readability
Model::MatrixReal B = Model::MatrixReal::build(
    4, 5, // 5 rows, 4 columns
    11, 12, 13, 14,
    21, 22, 23, 24,
    31, 32, 33, 34,
    41, 42, 43, 44,
    51, 52, 53, 54
).transpose();

The Aion Range Pseudo-Types

Aion provides the Model::Range class you can use to define ranges of values. The Model::Range class makes use of the Model::Variant type so you should become familiar with the capabilities of the Model::Variant type before working extensively with ranges.

You can use ranges to define a range of values to be iterated across and can iterate across ranges of integer and real values. Future versions of Aion may include support for iteration across other types such as finite or Galois fields.

You can use the Model::Range::Iterator and Model::Range::ConstIterator classes to iterate through the values in a range. These classes work much like traditional STL bidirectional iterators.

You can also use the Model::Range::contains method to check if a value is included in the supplied range.

The sample code below shows how you can use ranges to iterate through a range of real values.

// Include necessary headers

#include <cassert>
#include <iostream>

#include <model_intrinisic_types.h>
#include <model_range.h>
#include <model_variant.h>

...

// We define range1 to iterate from 0 to 100, inclusive, by 1.
Model::Range range1(Model::Integer(0), Model::Integer(100));

// We define range2 to iterate from 1 to 34, inclusive, by 3.
Model::Range range2(Model::Real(1), Model::Real(4), Model::Real(34));

for (auto it : range1) {
    if (range2.contains(*it)) {
        std::cout << *it << " -- contained in range2" << std::endl;
    } else {
        std::cout << *it << std::endl;
    }
}

Note

In the example above, we forced the types of values inserted into the range so that the variant type can implicitly perform type conversion for us via the provided Model::Variant constructors.

Ranges can be used by the matrix classes to define slices and can be used to populate sets. The example below demonstrates some of these capabilities.

// Include necessary headers

#include <cassert>

#include Model_intrinsic_types.h>
#include <model_api.h>
#include <model_api_types.h>
#include <model_variant.h>
#include <model_identifier_data.h>
#include <model_identifier_database.h>
#include <model_range.h>
#include <model_matrix_real.h>
#include <model_set.h>

...

// Obtain an identifier database instance to work with -- same as before.
Model::IdentifierDatabase database = algorithmInstance->identifierDatabase();
Model::IdentifierData varA = database.identifierDataByName("A");

// Confirm we're working with two real matrices.
assert(varA.valueType() == Model::ValueType::MATRIX_REAL);

Model::MatrixReal A = varA.toMatrixReal();

// We want to slice our matrix so it contains odd rows from 1 through 11 and
// columns 1, and then columns 5 through 12, inclusive.
//
// We'll define the slice as matrix B.
//
// We use a range directly to slice the rows and a set defined using a range to
// slice the columns.  Note that the implicit ordering of sets guarantees that
// the columns will be sliced in numerical order independently from how we
// insert the values into the set.
Model::Range rows(Model::Integer(1), Model::Integer(3), Model::Integer(11));
Model::Set columns;
columns.insert(Model::Range(Model::Integer(5), Model::Integer(10));
columns.insert(Model::Integer(1));

Model::MatrixReal B = A.at(rows, columns);

The Aion Set Type

Aion provides the Model::Set class you can use to access and manipulate sets. The Model::Set class provides and accepts contents using the Aion Model::Variant class so you should become familiar with the capabilities of the Model::Variant class before working extensively with the Model::Set class.

You can use the Model::Set::build static template method to rapidly build sets containing multiple elements. The example below demonstrates how this can be done.

// Include necessary headers

#include <model_intrinsic_types.h>
#include <model_variant.h>
#include <model_matrix_real.h>
#include <model_set.h>

...

Model::Set A = Model::Set::build(
    Model::Integer(1),
    Model::Integer(54),
    Model::Range(Model::Integer(1), Model::Integer(3), Model::Integer(15)),
    Model::MatrixReal::build(2, 2, 10, 12, 7, 3)
)

You can use the Model::Set::insert method to insert new entries into the set. The method accepts Model::Variant types and ranges. A templatized version is also provided allowing you to insert multiple elements using a single statement.

The Model::Set class also provides several types of iterators you can use to traverse the contents of sets. These iterators support the implicit ordering rules described in Implicit Ordering, Sets, And Sorting. Note that iterating across sets is essentially a constant time operation so you can iterate across sets quickly even with implicit ordering.

The sample C++ code below shows how you can iterate across a set.

// Include necessary headers

#include <iostream>

#include <model_intrinsic_types.h>
#include <model_variant.h>
#include <model_set.h>

...

Model::Set A = Model::Set::build(
    Model::Integer(23),
    Model::Integer(54),
    Model::Range(Model::Integer(1), Model::Integer(3), Model::Integer(15)),
)
for (auto it : A) {
    std::cout << *it << ", ";
}

std::cout << std::endl;

The sample code above would write:

1, 3, 5, 7, 9, 11, 13, 15, 23, 54

To the standard output device.

The Model::Set class also includes methods that perform a number of set operations, including:

  • Model::Set::contains

  • Model::Set::uniteWith

  • Model::Set::unitedWith

  • Model::Set::intersectWith

  • Model::Set::intersectedWith

  • Model::Set::subtract

  • Model::Set::difference

  • Model::Set::cartesianProduct

The C++ operators - and * are also overloaded to provide the set difference operator and cartesian product operator.

The Aion Tuple Type

Aion provides the Model::Tuple class you can use to access and manipulate tuples. The Model::Tuple class provides and accepts contents using the Aion Model::Variant class so you should become familiar with the capabilities of the Model::Variant class before working extensively with the Model::Tuple class.

The Model::Tuple class provides many of the capabilities you would expect in the C++ std::vector<Model::Variant> class and includes several methods that make the Model::Tuple class somewhat source code compatible with the std::vector<Model::Variant> class.

You can use the Model::Tuple::build static template method to rapidly build tuples containing multiple elements. The Model::Tuple::build template method works almost identically to the Model::Set::build method described above except that the returned value is a tuple and the elements are ordered as provided.

You can use the Model::Tuple::append and Model::Tuple::prepend methods to append or prepend elements to a tuple. Like the Model::Set::insert methods, these methods accept Model::Variant types. Unlike the Model::Set::insert, these methods do not currently accept ranges.

Note

At this time, the Model::Tuple::prepend methods are not implemented to be efficient. When possible, avoid using the Model::Tuple::prepend methods. Inesonic, LLC may address this in a future release of Aion.

You can use the Model::Tuple::at method to query the value at a location and the Model::Tuple::update method to update the value at a location. Note that tuple index values are 1 based rather than 0 based.

You can use the Model::Tuple::toString method to convert a tuple to a UTF-8 encoded, nul terminated string. Note that you must deallocate the returned string using the C++ delete[] operator. This method will return a null pointer if the tuple could not be converted. You can use the provided Model::Tuple::Tuple(const char*) constructor to convert a nul terminated, UTF-8 encoded string to a tuple.

Lastly, the Model::Tuple class overloads several operators, including the * and *= operators which provide concatenation and the / and /= operators which provide a right cancellation operation.

The Aion Variant Type

The Model::Variant type is a wrapper around the Aion variant type used by sets and tuples. The Model::Variant type allows you to carry around and manipulate any of the provided Aion types, except ranges. You can also use the Model::Variant type to perform type conversion.

In many cases, you can work transparently with the underlying types in a variant as if the variant is the underlying type as the Model::Variant can perform implicit type conversions.

You can query the type of value in a Model::Variant type using the Model::Variant::valueType method. The method will return one of the values in the Model::ValueType enumeration or Model::ValueType::NONE if the variant does not contain any valid data.

The Model::Variant type also includes the Model::Variant::canConvertTo method you can use to determine if you can convert the value stored in a variant to a specified type. The function will return true if the conversion is possible or false if the conversion is not possible.

Additionally, if you are performing operations between multiple types, and need to convert to a compatible common type, you can use one of the Model::Variant::bestUpcast static methods. Note that a templatized version of the Model::Variant::bestUpcast method exists that allows you the check for the best common type across three or more variants. The Model::Variant::bestUpcast methods accept either Model::Variant values or Model::ValueType enumeration values in any combination.

Note

The Model::Variant class allows for aggressive conversion to boolean and boolean matrix types. For this reason, you may see the methods canConvertTo and bestUpcast report allowed conversions to boolean and matrix boolean types.

You can safely convert to types using one of the provided Model::Variant::to* methods. These methods accept an optional pointer to a C++ bool value that will return true on success or false on error.

You can also use the implicit type conversion operators to implicitely convert Model::Variant values to other types. The implicit conversion operators will throw a Model::InvalidRuntimeConversion error if the convesion is not allowed.

The sample C++ code below shows several features of the Model::Variant type.

// Include necessary headers

#include <cassert>

#include <model_api.h>
#include <model_api_types.h>
#include <model_variant.h>
#include <model_identifier_data.h>
#include <model_identifier_database.h>

#include <model_matrix_real.h>
#include <model_matrix_complex.h>

...

// Obtain an identifier database instance to work with and then a couple of
// values.
Model::IdentifierDatabase database = algorithmInstance->identifierDatabase();
Model::Variant varA = database.identifierDataByName("A").value();
Model::Variant varB = database.identifierDataByName("B").value();
Model::Variant varC = database.identifierDataByName("C").value();

// We don't care what the variant types are but we need a common type we can
// work with that map to some matrix type.
//
// The last value is set to Model::ValueType::MatrixInteger.  By adding this
// fourth value, we impose an additional requirement that we must be able to
// at least up-cast to an integer matrix.
Model::ValueType bestType = Model::Variant::bestUpcast(
    varA,
    varB,
    varC,
    Model::ValueType::MATRIX_INTEGER
);

if (bestType != Model::ValueType::NONE) {
    // Now let's perform our operation, storing the result in another variant.
    Model::Variant result;

    switch (bestType) {
        case Model::ValueType::MATRIX_INTEGER:
            bool ok;
            Model::MatrixInteger A = varA.toMatrixInteger(&ok);
            assert(ok); // Should always be true

            Model::MatrixInteger B = varB.toMatrixInteger();
            Model::MatrixInteger C = varC.toMatrixInteger()

            Model::MatrixInteger R = A * B + C;
            result = Model::Variant(R); // Explicitly convert back to a variant

            break;

        case Model::ValueType::MATRIX_REAL:
            bool ok;
            Model::MatrixReal A = varA;
            Model::MatrixReal B = varB;
            Model::MatrixReal C = varC;

            Model::MatrixReal R = A * B + C;
            result = R;

            break;

        case Model::ValueType::MATRIX_COMPLEX:
            bool ok;
            Model::MatrixComplex A = varA;
            Model::MatrixComplex B = varB;
            Model::MatrixComplex C = varC;

            Model::MatrixComplex R = A * B + C;
            result = R;

            break;

        default:
            // Getting here means bestUpcast returned an unexpected value.
            assert(false);
    }
} else {
    // *** NO CONVERSION POSSIBLE ***
}