Message Boards Message Boards

Use Mathematica Kernel as an Omnipotent W|A API in C++ (Non-trivial)

Posted 11 years ago
To have the full benifit of this thread, a reader is expected to understand the object oriented programming in C++ including using pointer, defining class, operator overload and separation compilation. This example is written in C++ with XCode 4.6.3 on OSX 10.8.5. The Mathematica Kernel is from Version 9.  

 The idea behind this article is from an example in Chapter 8 of Savitch's Absolute C++ .The example was about to create a Money object  and to illustrate how to add the objects after overloading the plus/+ operator.

In this thread, I am going to add a very good feature to his example with the MathLink technology so we can add two money objects under different monetary system, ie. USD + CNY with the daily exchange rate.


To make the C++ code work as expected, you need to first follow this link to setup the linkers and frameworks path correctly on your machine and within the IDE. 

The key parts of this project are
1. Link the code to a Mathematica kernel
2. Load the WolframAlpha query into the C++ code and get the result
3. Embed (2) into the overloaded operator as part of the feature of our object: Money

Lets take a looks at the code: 
The implementation of the main.cpp is very concise. Launch the link to Mathematica Kernel and some pointers and one copy pointer to the Money objects. Then it simply print out the result and close the kernels.  The main also deletes the dynamic objects and close the Mathematica kernel before it quits. 

 MLENV ep = (MLENV)0;
 MLINK lp = (MLINK)0;// declared in mathlink.h
 
 int main(int argc, char* argv[])
 {
     init_and_openlink( argc, argv);
     Money* first = new Money(100,16);
     Money* second = new Money(10,52,"CNY");
     Money* res = new Money(*first + *second);
    Money* res2 = new Money(*second + *first);
    ... // print functions
    MLPutFunction( lp, "Exit", 0); //close kernel
    return 0;
}

The core object Money is also quite straightforward. The declaration contains three member instances which records the dollar part, cent part and currency symbol (like EUR, USD ... ) . Besides, I have one default constructor and a copy constructor. 

The key here is to define a suitable version of operator + (basically a new plus operation) here so things like USD + JPY would work properly. My idea is to make this operation non-commutable. This means that when USD + JPY the result prints JPY while JPY + USD yields USD currency. Certainly this is not perfect, but it is good enough to illustrate my idea.
 
 class Money
 {
     public:
         Money( ); //default
         Money(int theDollars, int theCents);
         Money(int theDollars, int theCents, std::string cur);
    
         Money(Money const &myMoney );//copy constructor
   
        std::string currency;
        int dollars;
        int cents;
};

//overload the operators
const Money operator +(const Money& amount1, const Money& amount2);

Savitch's version of this overload is fairly simple: convert all money to cents and sum things up. Do a divid-by-100 to get the dollar part and do a mod-by-100 to get the cent. Mine is the same is except there should be a conversion inside. This currency exchange rate is extracted by the getCurrencyRate().
 const Money operator +(const Money& amount1, const Money& amount2){
     int allcents1 = amount1.cents +  amount1.dollars*100;
     int allcents2 = amount2.cents +  amount2.dollars*100;
    
     double rate = getCurrencyRate (amount1, amount2) ;
    
     // in terms of the second currency
     int money = static_cast<int>(floor(static_cast<double>(allcents1)*rate + static_cast<double>(allcents2)));
    
    int dollarsFinal = money/100 ;
    int centsFinal = money%100 ;
   
    return Money(dollarsFinal,centsFinal,amount2.currency);
   
};

The above code should be rather plain if you know how to overload a operator in C++. The last part is exploring what is behind the getCurrencyRate function. The implementation is below:
 extern MLINK lp ;
 extern MLENV ep ;
 
 double getCurrencyRate (const Money &Money1, const Money &Money2  ) {
    
     double rate;
     int pkt;
     // 1 pick USD and JPY then make USD to JPY
     std::string temp = Money1.currency + " to " + Money2.currency;
    char rule[100];
    std::strcpy(rule,temp.c_str());
   
   //2. write a Mathematica code stack
    MLPutFunction( lp, "EvaluatePacket", 1);
    MLPutFunction( lp,"QuantityMagnitude",1);
    MLPutFunction(lp, "WolframAlpha",2);
    MLPutString(lp, rule); // this guy only works
    MLPutFunction(lp,"List",2);
    MLPutFunction(lp,"List",2);
    MLPutString(lp,"Result");
    MLPutInteger(lp,1);
    MLPutString(lp,"ComputableData");
//MLPutInteger( lp, n);
    MLEndPacket( lp);

    // 3 check results and return
    while( (pkt = MLNextPacket( lp), pkt) && pkt != RETURNPKT) {
    MLNewPacket( lp);
    if (MLError( lp)) error( lp);
        }

    MLGetReal64( lp, &rate);
    return rate;
}
Some preambles:
1. MLINK lp and MLENV ep are links to handle message passing between c++ and Mathematica Kernel
2. ML… indicates MathLink API's
3. the "while( (pkt = MLNextPacke…" is to check if there is a packet returns from Mathematica Kernel. We can skip this and it usually sits here as a error checking and prevent hanging.

Though chunk of code, it actually only contains three parts:
1. catenation of currency symbol of the variable temp ("USD to JPY"). "rule" is used because one of the MLPut*** function below only takes "char*".
2. A List of MLPut function
3. Get the result ( technically writes the return packet into the variable "rate") and return the exchange rate.

The second part above may look alien to the most viewers. I will first explain where it comes from. Because our subroutine here is basically to make a valid query into WolframAlpha, the result is just directly from WolframAlpha. In Mathematica we can use the api directly: 

Click the "+" sign beside the "Result: 6.14" pod and choose the "Computable Data option". Immediately you will see Mathematica returns this line: 

WolframAlpha["USD to CNY",{{"Result",1},"ComputableData"}]
If you wrap a Hold function to it and check its FullForm, you will see this:
 
Hold[WolframAlpha["USD to CNY",List[List["Result",1],"ComputableData"]]]
Observe that the MLPutFunction all takes the heads of above list as arguments. I added QuantityMagnitute here to kick out the unit part. Some functions takes three argments because they handles more than one argument. For instance, the List["Result" ,1] is a List function takes 2 inputs so I need
MLPutFunction(lp, "List" , 2);
MLPutString(lp,"Result");
MLPutInteger(lp,1);
This behavior is sort of building a stack. Finally, we just pick the value of rate from MLGet function through the link pool. 

The interesting thing is that once we have this part written in a separate file and encapsulate it in the operator overloading, the MathLink functions does not even bother us. The C++ can be used without reading the implementation of the getCurrency function. 

Improvements: Definitely I need better error handling function, this can be found by a manual written by Todd Galey online and the Mathematica documentation itself. Also if more parser is added to the operator, it could handle system like JPY which only has the cent part. Overall, I realize the example I have now should be sufficient to show how to combine Mathematica code with C++ in a real world problem.

Attachment is available. Again you need to set up XCode correctly before compiling this code.
POSTED BY: Shenghui Yang

enter image description here -- you have earned Featured Contributor Badge enter image description here Your exceptional post has been selected for our editorial column Staff Picks http://wolfr.am/StaffPicks and Your Profile is now distinguished by a Featured Contributor Badge and is displayed on the Featured Contributor Board. Thank you!

POSTED BY: EDITORIAL BOARD
Reply to this discussion
Community posts can be styled and formatted using the Markdown syntax.
Reply Preview
Attachments
Remove
or Discard

Group Abstract Group Abstract