EnvironmentsMethods
Run your own C++ model

Most of the time, model code is not designed to be portable. For now, OpenMOLE handles Java, Scala, NetLogo and R (in the near future) via specific Tasks, but it is still far from covering the languages used to develop models around the world.

Meanwhile, you have to package your code using CARE, as explained in the Native Packaging section. The following contents expose how to handle your packaged model within OpenMOLE.


C++ code example


The toy model for this example is the following :


#include <iostream>
#include <sstream>
#include <fstream>

double* model_computation(double param1, double param2, double output[]){
  std::cout << "model computation with  parameters " << param1 << " and " << param2 << " \n" ;
  output[0] = param1 * 10 ;
  output[1] = param2 * 20 ;
  return output ;
}

int main(int argc , char* argv[]){
  // extract args from command line
  std::istringstream iss1 (argv[1]) ;
  std::istringstream iss2 (argv[2]) ;
  double val1 ;
  double val2 ;
  double output[2];

  if (iss1 >> val1 && iss2 >> val2 ){
   double* result = model_computation(val1, val2, output) ; //model computation call
   std::cout << "log : compute first result "<< result[0] << '\n' ;
   std::cout << "log : compute second result " <<  result[1]  << '\n' ;
   //CSV file dump
   std::ofstream myfile;
   myfile.open ("results.txt");
   myfile << result[0] << ','<< result[1]<< "\n";
   myfile.close();
   return 0;
 }
 else{
   std::cout << "Wrong arg : not a double " ;
   return -1;
 }
}

In this toy example, the "model code" is outside the main function, on purpose.
The main function has three steps:
  • first the inputs are extracted from command line,
  • then the model function is called with extracted values,
  • finally the results are displayed on standard output, and written in a text file.

Packaging it with CARE


Before launching CARE on this piece of code, let's check that the compilation and executions are running fine for the code itself:
> g++ hello_world.cpp
> ./a.out 12.24 24

we obtain the following result :
model computation with  parameters 12.24 and 24
log : compute first result 122.4
log : compute second result 480



In the directory where the hello_world.cpp file is, we do:
care-x86_64 -o ./cpp.tgz.bin -r ~ ./a.out 12.24 24
Please see the Native packaging page for more details.

The packaging step produces the following output:
care info: concealed path: $HOME /home/paul
care info: concealed path: /tmp
care info: revealed path: $PWD /home/paul/dev/cppExampleDocOpenMOLE
care info: revealed path: /home/paul/dev/cppExampleDocOpenMOLE/a.out
care info: revealed path: /home/paul
care info: ----------------------------------------------------------------------
main function call
model computation with  parameters 12.34 and 24
log : compute first result 122.4
log : compute second result 480
care info: ----------------------------------------------------------------------
care info: Hints:
care info:   - search for "conceal" in `care -h` if the execution didn't go as expected.
care info:   - run `././cpp.tgz.bin` or `care -x ./cpp.tgz.bin` to extract the output archive correctly.
        

Notice the lines in between the CARE info messages: these are the log messages of our initial code. Everything is ok so far.
The packaging has created two files: cpp.tgz.bin and results.txt, respectively the CARE archive and the results file.

Running it in OpenMOLE


We will now use OpenMOLE's ExplorationTask to run the model several times with different input values, with this script:
// Declare the variables (from the OpenMOLE point of view)
val arg1 = Val[Double]
val arg2 = Val[Double]
val output = Val[File]

// exploration of the inputs range
val exploration = ExplorationTask(
 (arg1 in (0.0 to 5.0 by 1.0)) x
  (arg2 in (0.0 until 4.0 by 1.0))
)

//  the care task for our archive
val cppTask =
  CARETask(workDirectory / "cpp.tgz.bin", "./a.out  ${arg1} ${arg2}") set (
    inputs += (arg1, arg2),
    outputFiles += ("results.txt", output),
    outputs += (arg1, arg2)
  )

// we fetch the data via a CopyFileHook, for each input combination
val copy = CopyFileHook(output, workDirectory / "cpp${arg1}${arg2}.txt")
// we run that locally , on 4 cores
val env = LocalEnvironment(4)
// the workflow
exploration -< (cppTask hook copy on env)
This script uses a Hook to catch the output file, check the dedicated page for more details.

Let's try this example on your own machine:
  • First, create a directory to put the CARE archive (see the GUI guide)
  • Then, inside this directory, create a new file with a .oms extension. This is our OpenMOLE script.
  • Finally, paste the script above and press "Play".

Looking at the output stream , we have lines looking like:
model computation with  parameters 4and 2
log : compute first result 40
log : compute second result 40
model computation with  parameters 3and 9
log : compute first result 30
log : compute second result 180

It looks a lot like the log from our initial code! Hooray!

The results files, called something like cpp2.01.0.txt, are located in the directory where we uploaded our CARE packaged code.

This is not a smart move if you have a lot of file to manage, so we should put them in a result dedicated directory, in order to compress the whole thing at the end of the experiment and download the resulting archive. To do that, let's replace the CopyFileHook line by the following:
val copy = CopyFileHook(output, workDirectory /"results/cpp${arg1}${arg2}.txt")