Skip to content

Latest commit

 

History

History

SAP Automated Analytics C++ Model Integration - Mini How-To

This document explains how to integrate C++ models generated by Automated Analytics from an external C++ program.

Code Generation

SAP Automated Analytics

Code generation is done through SAP Automated Analytics API call below:

generateCode(in string iType, in string iDirectory,  in string iFileName);  

or

generateCode2(in string iType, in string String iType, String iDirectory, String iFileName, String iTargetName, String iSpaceName, String iKeyName )

where iType is set to "C++" and iFileName is the name of the C++ file to generate ( see "API Reference Guide" for more information).

SAP HANA Automated Predictive Library

Code generation can also be done through SAP HANA Automated Predictive Library API call below:

CALL "SAP_PA_APL"."sap.pa.apl.base::EXPORT_APPLY_CODE"(FUNC_HEADER, MODEL, OPERATION_CONFIG, RESULT)

with parameter "APL/CodeType" set to "C++" ( see SAP HANA Automated Predictive Library Developer Library Guide for more information ).

Specifications

The C++ KMX framework is based on several interfaces and classes that are described below:

  • KxCppRTModel: interface implemented by each generated model
  • KxCppRTModelManager holds a map of models, allows to get back models by name
  • KxCppRTCase representation of one input record or output record, it is composed of KxCppRTValue elements
  • KxCppRTValue representation of one variable value

The C++ KMX generator we propose, produces a single file the model's class definition and the implementation. Each file contains only a single class defining the apply function of a model. In addition, the name of the model is equal to the class name (this name is created by SAP Automated Analytics Framework). Each generated model is registered in the model manager (this process is automatic, see the static at the end of this example).

Here is a sample skeleton of such a generated file:

...  
// definition of all categories  
...  
class ExportedModelInCPP : public virtual KxCppRTModel  
{  
 public :  
  ExportedModelInCPP();  
  ExportedModelInCPP(KxCppRTModelManager& iModelManager);  
  
// Others functions definition...  
 ...  
 private:  
  ... // Internal functions definition  
  double Kxen_RobustRegression_10e4c50l__0_KxVar1(KxCppRTCase iInput) const;  
...  
};  
  
ExportedModelInCPP::ExportedModelInCPP(KxCppRTModelManager& iModelManager)  
{  
 // register the model into the model manager  
 iModelManager.registerModel("CPPKxModel", this);  
 ...  
}  
  
void  
ExportedModelInCPP::apply(const KxCppRTCase& iInput, KxCppRTCase &oOutput) const  
{  
 Kxen_RobustRegression_10e4c50l__0_KxVar1 (iInput, oOutput);  
}  
  
static ExportedModelInCPP gKxMyModel(KxCppRtModelManager::instance());  

C++ KMX Details

We detail here the behavior and usage of each class in the C++ KMX framework.

Each generated model implements a generic interface called KxCppRTModel.

The KxCppRTModel interface has the following methods:

  • String getModelName() const : This method returns the name of the model as a string
  • void apply(const KxCppRTCase&, KxCppRTCase&) const : Apply the model on a data row which is called a case. This method needs input object providing input variable values and returns an object containing results value. Both input and output values are using an object to represent a set of values that will be described further.
  • const StringVector& getInputVariables() const : This method specifies the input variables that the generated model needs. It returns a vector of needed input variable names.
  • const StringVector& getOutputVariables() cons : This method specifies the output variables generated by the model. It returns a vector of output variable names.
class KxCppRTModel  
{  
 public:  
  virtual ~KxCppRTModel() {};  
  virtual const KxSTL::string& getModelName() const = 0;  
  virtual const StringVector& getModelInputVariables() const = 0;  
  virtual const StringVector& getModelOutputVariables() const = 0;  
  virtual void apply(const KxCppRTCase& iInput, KxCppRTCase& iOutput) const = 0;  
};

The KxCppRTCase interface allows feed models with values. It provides services that allows the model to access values by rank (KMX generated codes use the variable rank internally), but it allows the external environment to set the values using the names of the input variables.

The KxCppRTCase interface will be implemented by the integrator in order to connect its physical data values to the model class instances.

  • void setValue(String const& iVariableName, KxCppRTValue const& value): This method is used by the calling program to fill the input case with proper values associated with variable names. This method is also used by the model in order to fill the proper output value.
  • const KxCppRTValue& getValue(Int ) const : This method is used by the model in order to get the value associated by the variable of rank from the input case provided by the integrator..
  • const KxCppRTValue& getValueFromName (String const& iVariableName) const : This method can be used by the external program in order to get back the generated value from the model in the output case.
class KxCppRTCase  
{  
  virtual ~KxCppRTCase() {};  
  virtual void setValue(KxSTL::string const& iName, KxCppRTValue const& iValue) = 0;  
  virtual const KxCppRTValue& getValue(int i) const = 0;  
  virtual const KxCppRTValue& getValueFromName(KxSTL::string const& iName) const = 0;  
};  

An example of a very simple implementation is provided (see SampleMappedCase.cpp)

The KMX framework uses a model manager which provides model registering facilities. It associates each model with a name.

struct sPrivateData;  
  
class KxCppRTModelManager  
{  
 public:  
  
  ~KxCppRTModelManager ();  
  
  static KxCppRTModelManager& instance();  
  
  void registerModel(KxSTL::string const& iModelName, KxCppRTModel* iModelPtr) {  
   mModelFactory[iModelName] = iModelPtr;  
  }  
  
  static const KxCppRTModel& getKxModel (KxSTL::string const& iModelName) {  
   return instance().getModel(iModelName);  
  }  
  
  KxSTL::vector getListModel() {  
  ...  
  }  
 private:  
  KxCppRTModelManager () {}  
  
  const KxCppRTModel& getModel (KxSTL::string iModelName) {...}  
  
  struct sPrivateData *mData;  
};  
  
struct sPrivateData {  
 KxSTL::map< KxSTL::string, KxCppRTModel* > mModelFactory;  
};

The CPP Runtime uses KxCppRTValue as data.

struct sValueData;  
  
class KxCppRTValue  
{  
 public:  
  KxCppRTValue(KxCppRTValue const& iOther);  
  
  KxCppRTValue();  
  KxCppRTValue(KxSTL::string const& iValue);  
  KxCppRTValue(const char* iValue);  
  ~KxCppRTValue();  
  KxSTL::string const& getValue() const;  
  KxCppRTValue& operator=(KxCppRTValue const& iOther);  
 private:  
  struct sValueData* mValueData;  
};  
  
struct sValueData  
{  
 KxSTL::string mValue;  
 sValueData() {}  
 sValueData(KxSTL::string const& iValue) : mValue(iValue) {}  
 sValueData(const char* iValue) : mValue(iValue) {}  
 sValueData(sValueData const& iOther) : mValue(iOther.mValue) {}  
};  

Using the generated models

The sample main program provided will create an instance of the generated model and feed it with the proper cases.

#include "StringUtilities.h"  
#include "KxCppRTModelManager.h"  
#include "SampleMappedCase.cpp"  
  
int main( int argc, char ** argv )  
{  
 FILE* lInFile = NULL;  
 FILE* lOutFile = stdout;  
  
 ...  
  
 lInFile = fopen(..., "r");  
 lModelName = ...;  
 lOutFile = fopen(..., "w");  
  
 ...  
  
// return model called <lModelName>  
 const KxCppRTModel& lModel = KxCppRTModelManager::getKxModel(lModelName);  
  
 // return the variable names used  
 KxSTL::vector lInputNames = lModel.getModelInputVariables();  
  
 SampleMappedCase lInCase = SampleMappedCase(lInputNames);  
  
...  
  
 SampleMappedCase lOutCase =  
 SampleMappedCase(lModel.getModelOutputVariables());  
  
 while (...)  
 {  
  ...  
  lInCase.setValue(...,  
  ...);  
  // apply  
  lModel.apply(lInCase, lOutCase);  
   
  ...  
 }  
  
 fclose(lOutFile);  
 fclose(lInFile);  
  
 return 0;  
}

Maintaining generated codes

In this sample implementation, the generated codes are located in a stand-alone dynamic library or DLL.
To add a new model run-time in this library or DLL, the makefile maching your platform (linux.mak, ...) must be updated: the value of the target called MODEL_OBJECT must be replaced by the list of generated models.

# Name of the model to be used in the sample
MODEL_FILE = ExportedModelInCPP

# Model Object file
MODEL_OBJECT = $(MODEL_FILE).o