Tutorial on basic API usage

Description:

This test shows you how to set up a first simulation using the Aspherix(R) C++ API.

Introduction:

The idea of the API is to allow the interaction with Aspherix(R) by not just using an input script but by using a computer program written by the user. This allows the same type of interaction as with an input script plus an additional layer of access to internal data of Aspherix(R) which can, for example, be used for advanced post-processing.

In this first tutorial you are going to create a very basic form of such a program by using C++ to read in an input script and then execute another command at the end of this simulation. While not extremely useful in itself it will teach you how to compile an Aspherix(R) API program and execute it.

Prerequisites:

Linux:

To generate an Aspherix(R) API program on Linux you need the following software installed:

  • A compiler (e.g. g++)

  • CMake (Version >= 3.9)

  • An MPI library (e.g. OpenMPI)

  • Aspherix(R)

On Ubuntu you can quickly install the first three using the following command:

sudo apt install build-essential cmake libopenmpi-dev

and we assume that you have installed Aspherix(R) already, if not head over to our installation instructions.

The basic API example explained

The basic API example can be found in your Aspherix(R) installation folder in subdirectory examples/API/C++/basic_api_usage. There, the following four files should be present:

basic.asx
basic.cpp
CMakeLists.txt
meshes/plate.stl

The basic.asx is a standard Aspherix(R) input script that uses the STL mesh meshes/plate.stl and our goal is to run this case from within our C++ program which can be found in basic.cpp. To compile the C++ code into an executable we are using the CMake build system which needs some instructions on how to build our code and that can be found in the CMakeLists.txt. The contents of the two files mentioned last will be explained in detail below. The contents of the first two should be no surprise for you as they contain standard Aspherix(R) notation and data.

The basic.cpp file:

Let us now go through this C++ file line by line. If you are not familiar with the language you can go to www.cplusplus.com for an introduction.

// includes for exception handling and output
#include <exception>
#include <iostream>

// include for the Aspherix object
#include "aspherix.h"

The code, as most of the time starts of with the inclusion of some header files. The first two come from the C++ standard library and are used for the handling of exceptions and the output to the terminal, respectively. The last line includes the basic Aspherix(R) header file. Details about the contents can be found here. It will provides us access to create an Aspherix object as shown below.

int main(int argc, char **argv)

We declare a main function that will be called on startup containing the arguments coming from the command line (for details see here)

// initialize Aspherix object without any arguments
Aspherix_API::Aspherix asx("");

This is the first call towards Aspherix(R). We create a new Aspherix object that resides in the Aspherix_API namespace, the empty quotation marks here could be omitted but we have left them in here to indicate that we are not passing any command line options to Aspherix(R). You could pass an input script directly to Aspherix using "-in script.asx".

// Run an input script in a try/catch block to detect any errors during the run
try
{
    // run the file with name "basic.asx"
    asx.executeInputFile("basic.asx");
}
catch (std::exception &e)
{
    // if there was an error we output the corresponding error message and
    std::cerr << "Exception when running Aspherix: " << e.what() << std::endl;
    // exit the program
    exit(1);
}

The gist of these lines is that we want to execute an input file with Aspherix(R) using the executeInputFile function. This function takes one argument and that is the filename of the script we want to execute. Note, the filename is relative to the path where the executable is executed in, something that will be important later.

The rest of the code is for exception handling. Basically, should anything go wrong when executing the input file the code will abort gracefully by going into the catch block and executing the code inside. In this case it will output a message to stderr and tell you what exactly went wrong using the e.what() function.

// Let's output some information to the terminal
std::cout << "Completed execution of input script. Next, we execute a single command." << std::endl;

This line just writes some output to the terminal (stdout).

// finally we simulate an additional 1e-2 seconds
asx.executeCommand("simulate time 1e-2");

After executing the input script above Aspherix(R) will not complete the simulation. Instead it is waiting for further input, just as if you were adding another line to the input script.

Using the Aspherix object we can instruct Aspherix(R) to execute another command using the executeCommand function. In this case we continue the simulation for another 0.01 seconds.

// And we tell the user that we are done
std::cout << "Completed execution of single command. Goodbye" << std::endl;

return 0;

Finally, after the second simulation step is done we write some output to the terminal and exit the program with a return value of 0 (indicating successful completion).

The CMakeLists.txt file:

The CmakeLists.txt file is used to tell our build system (CMake) how to generate the appropriate build environment. In principle this could also be replaced by a simple Makefile but CMake allows us to easily handle external dependencies such as MPI and Aspherix(R).

For most of your Aspherix(R) API projects the CMakeLists.txt file shown below should be sufficient and will not need much adjustment.

# project name
PROJECT(BASIC CXX)

The first block of code in this file tells cmake that we are building a project called BASIC which is based on the CXX (C++) language.

# cmake wants to know which version it needs to run this file
CMAKE_MINIMUM_REQUIRED(VERSION 3.9)

# we want to build an optimized version of the code
IF(NOT CMAKE_BUILD_TYPE)
  SET(CMAKE_BUILD_TYPE Release)
ENDIF()

This is followed by telling CMake that the minimum required version of CMake itself is 3.9 and that if no option is set, we want to build an optimized version of the code. Note, this only refers to your code, not to Aspherix(R), the latter is always optimized.

# add all \*.cpp and \*.h files to the two variables CXX_FILES and H_FILES, respectively
FILE(GLOB CXX_FILES \*.cpp)
FILE(GLOB H_FILES \*.h)

# add all files listed in the variables to the executable and call the executable "basic"
ADD_EXECUTABLE(basic ${CXX_FILES} ${H_FILES})

In this code block we tell CMake which files we want to include in our executable which is going to be called basic. The variables CXX_FILES and H_FILES are populated in the first two lines with all *.cpp and *.h files that are present in the source directory, respectively. And are then assigned to the executable in the last line.

# now we tell cmake to find the mpi package and make it mandatory
FIND_PACKAGE(MPI REQUIRED)

# add the mpi includes and libraries to the respective compilation stages
INCLUDE_DIRECTORIES(${MPI_INCLUDE_PATH})
TARGET_LINK_LIBRARIES(basic ${MPI_LIBRARIES})

# if there are any mpi compiler flags then add them to the compile stage of our executable
IF(MPI_COMPILE_FLAGS)
    SET_PROPERTY(TARGET basic APPEND PROPERTY COMPILE_FLAGS "${MPI_COMPILE_FLAGS}")
ENDIF()
# if there are any mpi linker flags then add them to the linking stage of our executable
IF(MPI_LINK_FLAGS)
    SET_PROPERTY(TARGET basic APPEND PROPERTY LINK_FLAGS "${MPI_LINK_FLAGS}")
ENDIF()

Aspherix(R) is an inherently parallel software using MPI. Because of this, we need to link with MPI even if it is not going to be used in this basic example. See the API MPI tutorial for a slightly more advanced example using MPI. This is where CMake starts to shine. Using the FIND_PACKAGE command we can search for MPI and since we absolutely require it, the REQUIRE flag is set.

The lines following after that tell CMake to use the MPI include files and libraries when compiling and linking our executable. Additionally, should there be any compiler or linker flags from MPI that are required then those will be added to the compiling and linking stages.

# next, we tell cmake to search for aspherix and that it is mandatory
FIND_PACKAGE(aspherix CONFIG REQUIRED)

# add the aspherix includes and libraries to the respective compilation stages
INCLUDE_DIRECTORIES(${ASPHERIX_INCLUDE_PATH})
ADD_DEFINITIONS(${ASPHERIX_COMPILE_OPTIONS})
IF(WIN32)
    TARGET_LINK_LIBRARIES(basic ${ASPHERIX_LIBRARY_PATH} ${CCD_LIBRARY_PATH})
ELSE()
    TARGET_LINK_LIBRARIES(basic aspherix)
ENDIF()

We also need to tell CMake to include Aspherix(R) when building our executable. We follow the same principle as above using the FIND_PACKAGE function. Since we are using a config file for Aspherix(R) we need the additional CONFIG flag. More on that later.

Similar to the MPI library we also need to add include path, compile options and libraries to the respective building steps. Again, this part of the CMake code can be reused in any other API project you may have with no or little changes.

# here we tell cmake where to install the resulting "basic" executable
INSTALL(TARGETS basic
        RUNTIME DESTINATION bin
        LIBRARY DESTINATION lib
        ARCHIVE DESTINATION lib
        PUBLIC_HEADER DESTINATION include)

Finally, CMake also allows us to specify how to install our executable after building. For this the corresponding subfolders for the individual files generated by CMake can be specified. We will see how this works a bit later.

Compiling the API example

Linux:

To compile the example in Linux we will use a terminal that should be open at the subfolder examples/API/C++/basic_api_usage. And ls should show the four files mentioned above:

$ ls
basic.asx  basic.cpp  CMakeLists.txt  meshes

To build a CMake project it is a good idea to create a new folder in which we will do the compilation:

$ mkdir build
$ cd build

To set up our build environment we need to run cmake with all required options and point it to our source directory. In the current case this is ... Thus, we execute:

$ cmake -Daspherix_DIR=$HOME/DCS-Computing/Aspherix-7.1.0/share/aspherix -DCMAKE_INSTALL_PREFIX=../install ..

The -Daspherix_DIR sets the variable aspherix_DIR which should point to the subfolder share/aspherix of your Aspherix(R) installation. The CMAKE_INSTALL_PREFIX variable tells CMake where to put the executable after it was built during the installation step. Here we tell it to use the install folder which shall be created in our parent folder. Finally, the .. as mentioned above refers to the source folder which is our parent folder.

If everything works out you should see an output similar to the following:

-- The CXX compiler identification is GNU 9.3.0
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Found MPI_CXX: /usr/lib/x86_64-linux-gnu/openmpi/lib/libmpi_cxx.so (found version "3.1")
-- Found MPI: TRUE (found version "3.1")
-- Configuring done
-- Generating done
-- Build files have been written to: /home/user/DCS-Computing/Aspherix-7.1.0/examples/API/C++/basic_api_usage/build

Should you instead see something like this:

CMake Error at CMakeLists.txt:36 (FIND_PACKAGE):
  Could not find a package configuration file provided by "aspherix" with any
  of the following names:

    aspherixConfig.cmake
    aspherix-config.cmake

  Add the installation prefix of "aspherix" to CMAKE_PREFIX_PATH or set
  "aspherix_DIR" to a directory containing one of the above files.  If
  "aspherix" provides a separate development package or SDK, be sure it has
  been installed.


-- Configuring incomplete, errors occurred!

Then you should recheck the folder that you have provided to the aspherix_DIR variable.

Now the build system should be all set up for compiling which can be done by executing:

$ make

Followed by an output similar to this one:

Scanning dependencies of target basic
[ 50%] Building CXX object CMakeFiles/basic.dir/basic.cpp.o
[100%] Linking CXX executable basic
[100%] Built target basic

And finally we can install our executable in the ../install folder by using:

$ make install

yielding an output similar to:

[100%] Built target basic
Install the project...
-- Install configuration: "Release"
-- Installing: /home/user/DCS-Computing/Aspherix-7.1.0/examples/API/C++/basic_api_usage/install/bin/basic
-- Set runtime path of "/home/user/DCS-Computing/Aspherix-7.1.0/examples/API/C++/basic_api_usage/install/bin/basic" to ""

Running the API example

Linux:

Now that we have compiled our example we can go to our parent folder and inspect the content of the install folder:

$ cd ..
$ ls install/
bin

which, as we only build an executable only contains a bin folder, which in turn contains the basic executable:

$ ls install/bin/
basic

Note, in the basic.cpp file above we specified the input script as basic.asx so if we run the basic executable we must ensure that it is located where this file is (you can verify this by using ls).

To execute the code simply use:

$ install/bin/basic

and watch the simulation output:

Aspherix (Version Aspherix 7.1.0, compiled 2026-03-12-13:20:23 by vagrant, git commit 3efa378ae0fe305d4e235e2e5c66d2244884a712)
Checkout of asx_solver OK.
Created orthogonal box = (-0.1 -0.1 -1) to (1 1 1)

[... more output from the Aspherix(R) simulation ...]

Completed execution of input script. Next, we execute a single command.

[... more output from the second Aspherix(R) simulation step ...]

Completed execution of single command. Goodbye

Should you get an error like the following instead:

$ install/bin/basic
install/bin/basic: error while loading shared libraries: libaspherix.so: cannot open shared object file: No such file or directory

then you need to tell your environment where to find the aspherix library. This can be done by using:

$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$HOME/DCS-Computing/Aspherix-7.1.0/lib

or to make this change permanent (in case you are using bash):

$ echo "export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$HOME/DCS-Computing/Aspherix-7.1.0/lib" >> ~/.bashrc

Note, this should only be necessary if you ticked the “Set environment variables in ~/.profile and ~/.bashrc” during the installation (default is on).