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.
Platform |
Location |
---|---|
Windows |
|
Linux |
|
MacOS |
|
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.
Class (In Namespace |
Defined In |
Provides |
---|---|---|
|
|
A common base class used by Aion to control your algorithm or model. |
|
|
Class that encapsulates data about a single identifier used by your model. |
|
|
A database containing information about all the identifiers in your algorithm or model. |
|
|
An iterator you can use to traverse
|
|
|
A friendly interface to the Aion random number generator. |
|
|
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.
Type (In Namespace |
Defined In |
Provides |
---|---|---|
|
|
An enumeration indicating the reason your algorithm or model aborted. |
|
|
An enumeration of supported input/output devices. |
|
|
Type used to reference an identifier by numeric value. |
|
|
Type used to reference a code breakpoint by numeric value. value. Values are used to map mathematical expressions to instruction addresses. |
|
|
Enumeration of supported data types. |
|
|
Enumeration of current algorithm/model operating states. |
Table 26 briefly lists key namespace scope and global functions that you may need to use.
Function |
Defined In |
Provides |
---|---|---|
|
A function that will allocate a new model/algorithm instance. |
|
|
A function that will deallocate a model/algorithm instance, freeing memory. |
Table 27 briefly lists namespace scope constants you may need to use.
Constant (In Namespace |
Defined In |
Provides |
---|---|---|
|
|
A value you can use to indicate or identify an invalid operation handle. |
|
|
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.
Define |
Defined In |
Provides |
---|---|---|
|
|
Support for |
|
|
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.
Class (In Namespace |
Defined In |
Provides |
---|---|---|
|
|
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. |
|
|
Exception raised when a file can-not be closed. |
|
|
Base class for all file errors. |
|
|
Exception raised when a file can-not be opened. |
|
|
Exception raised when a file can-not be read. |
|
|
Exception raised when a file seek operation fails. |
|
|
Exception raised when a file can-not be written |
|
|
Base class for all Aion exceptions. |
|
|
Exception that is raised if there is insufficient memory available for a data structure. |
|
|
Exception raised when an invalid file number is provided to one of the Aion file functions. |
|
|
Exception that is raised when a run-time type conversion fails. |
|
|
Exception raised when a provided string is not UTF-8 encoded and can not be converted to Unicode. |
|
|
Exception raised by high-level file read/write functions when a file type is unrecognized or can-not be determined. |
|
|
Exception that is raised on a user requested abort. |
Function (In Namespace |
Defined In |
Provides |
---|---|---|
|
|
Disables a specific exception type. |
|
|
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.
Class (In Namespace |
Defined In |
Provides |
---|---|---|
|
|
The Aion complex type. |
|
|
Base class for all matrix types. |
|
|
Boolean matrix type. |
|
|
Complex matrix type. |
|
|
Integer matrix type. |
|
|
Real matrix type. |
|
|
Range pseudo-type. |
|
|
Range iterator. |
|
|
Set type. |
|
|
Set iterator used to traverse sets. |
|
|
Tuple type. |
|
|
Tuple constant iterator. |
|
|
Tuple iterator. |
|
|
Base class for all tuple iterators. |
|
|
A variant type used by the |
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.
Type (In Namespace |
Defined In |
Provides |
---|---|---|
|
|
A boolean type you can use. |
|
|
An integer type you can use. |
|
|
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.
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.
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 returnModel::State::ACTIVE
. If an error occurs, the method will returnModel::State::ABORTED
. If the algorithm has completed or was never started, the method will returnModel::State::STOPPED
. If an algorithm is in the process of shutting down in response to an error, the valueModel::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.
Algorithm |
|
---|---|
Mercenne Twister MT19937-64 PRNG |
|
Mercenne Twiser SFMT216091 PRNG |
|
XOR-Shift-Rotate 256++ PRNG |
|
XOR-Shift-Rotate 256** PRNG |
|
OS Provided Cryptographic 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:
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 ***
}