Thursday, 2 June 2011

.NET examples: C++ and using Object fields

Recently we had a question from a user of the NAG Library for .NET. He wondered whether it was possible to use methods from objects rather than static methods as callback parameters to the NAG methods. The library was designed such that this should be possible, but we were made aware that there were not many examples of this usage in the documentation. Also the user was coding in C++/CLR and currently we have no examples of this, all examples being C#, F# or VB.Net.

We took the existing C# example for the optimisation routine e04uc and modified it in two ways. Firstly converting to C++/CLR, and then modifying the example so that the objective function being minimised is a method of an object (of locally declared class NagDelegate) and to demonstrate two ways in which data may be passed between successive calls to this callback parameter.



Note that C++/CLR has extended syntax (notably ^ and gcnew in this example) which declare managed data that will be managed and potentially garbage collected by the .NET runtime system.
Once the class and its method are defined, the only thing that needs to be done to pass this method to the e04uc method is to declare the delegate object using the syntax
E04::E04UC_OBJFUN^ objfunE  =
    gcnew E04::E04UC_OBJFUN(nagdelegate, &NagDelegate::objfun);
Unlike the situation in C#, you need to specify both the class and the particular method as two separate parameters when declaring the delegate.


As mentioned above, the method NagDelegate::objfun demonstrates two ways in which data may be accessed, in both cases here the data is a counter reporting how many times the objective function was evaluated by the optimisation algorithm. static_counter is a static variable and counter is a local field of the NagDelegate object. As may be seen by the results when the program is run, both methods work as expected, returning the same value.
  static_counter++;
  counter++;
The C++ example program is shown in full at the end of this post. To run it using Visual Studio, just start a new project and replace the default program by the code below. You must then select the project references and add a reference to the NagLibrary32 or NagLibrary64 .NET assemblies. The program should then run and produce the output shown.
// csharpfromcpp.cpp : main project file.
  
 #include "stdafx.h"
  
 using namespace System;
 using namespace NagLibrary;
  
 static int static_counter;
                           
 public ref class NagDelegate
 {
 public:
     int counter;
     void objfun(int% mode, int n, array <double>^ x, double% objf,
                 array <double>^ objgrd, int nstate)
     {
         //      Routine to evaluate objective function
         //      and its 1st derivatives.
         static_counter++;
         counter++;
         double one = 1.00e0;
         double two = 2.00e0;
         if ((mode == 0) || (mode == 2))
             {
                 objf = x[0] * x[3] * (x[0] + x[1] + x[2]) + x[2];
             }
         //
         if ((mode == 1) || (mode == 2))
             {
                 objgrd[0] = x[3] * (two * x[0] + x[1] + x[2]);
                 objgrd[1] = x[0] * x[3];
                 objgrd[2] = x[0] * x[3] + one;
                 objgrd[3] = x[0] * (x[0] + x[1] + x[2]);
             }
         //
     }
 };
  
 void confun( int% mode, int ncnln, int n, array< int >^ needc,
               array< double >^ x, array <double>^ c,
                     array <double,2>^ cjac, int nstate)
     {
       //      Routine to evaluate the nonlinear constraints
       //       and their 1st derivatives.
       double zero = 0.00e0;
       double two = 2.00e0;
       int i,  j;
       if (nstate == 1)
       {
         //  First call to confun.  Set all Jacobian elements to zero.
         //  Note that this will only work when 'Derivative Level = 3'
         //  (the default; see Section 11.2).
         for (j = 1; j <= n; j++)
         {
           for (i = 1; i <= ncnln; i++)
           {
             cjac[i - 1, j - 1] = zero;
           }
         }
       }
       //
       if (needc[0] > 0)
       {
         if ((mode == 0) || (mode == 2))
         {
           c[0] = (x[0]) * (x[0]) +
                          (x[1]) * (x[1]) + (x[2]) * (x[2]) + (x[3]) * (x[3]);
         }
         if ((mode == 1) || (mode == 2))
         {
           cjac[0, 0] = two * x[0];
           cjac[0, 1] = two * x[1];
           cjac[0, 2] = two * x[2];
           cjac[0, 3] = two * x[3];
         }
       }
       //
       if (needc[1] > 0)
       {
         if ((mode == 0) || (mode == 2))
         {
           c[1] = x[0] * x[1] * x[2] * x[3];
         }
         if ((mode == 1) || (mode == 2))
         {
           cjac[1, 0] = x[1] * x[2] * x[3];
           cjac[1, 1] = x[0] * x[2] * x[3];
           cjac[1, 2] = x[0] * x[1] * x[3];
           cjac[1, 3] = x[0] * x[1] * x[2];
         }
       }
       //
     }
  
  
 int main(array<System::String ^> ^args)
 {
         double objf = 0.0;
         int i,  ifail,  iter,  j,  n,  nclin,  ncnln;
         Console::WriteLine(L"e04uc Example Program Results");
         //      Skip heading in data file
         n = 4;
         nclin = 1;
         ncnln = 2;
         array< double, 2 >^ a = gcnew array< double, 2 >( nclin, n);
         a[0, 0]=1;
         a[0, 1]=1;
         a[0, 2]=1;
         a[0, 3]=1;
  
         array< double >^ bl = gcnew array< double >(n + nclin +ncnln);
         bl[0] = 1.0;
         bl[1] = 1.0;
         bl[2] = 1.0;
         bl[3] = 1.0;
         bl[4] = -1.0e+25;
         bl[5] = -1.0e+25;
         bl[6] = 25.0;
  
         array< double >^ bu = gcnew array< double >(n + nclin +ncnln);
         bu[0] = 5.0;
         bu[1] = 5.0;
         bu[2] = 5.0;
         bu[3] = 5.0;
         bu[4] = 20.0;
         bu[5] = 40.0;
         bu[6] = 1.0e25;
  
  
         array< double >^ c = gcnew array< double >(ncnln);
         array< double, 2 >^ cjac = gcnew array< double, 2 >(ncnln, n);
         array< double >^ clamda = gcnew array< double >(n + nclin +ncnln);
         array< double >^ objgrd = gcnew array< double >(n);
         array< double, 2 >^ r = gcnew array< double, 2 >(n, n);
         array< double >^ x = gcnew array< double >(n);
         x[0] = 1.0;        
         x[1] = 5.0;        
         x[2] = 5.0;        
         x[3] = 1.0;        
  
         array< int >^ istate = gcnew array< int >(n + nclin +ncnln);
  
              E04::e04ucOptions^ options = gcnew E04::e04ucOptions() ;
              E04::E04UC_CONFUN^ confunE = gcnew  E04::E04UC_CONFUN(confun);
  

              NagDelegate^ nagdelegate = 
                        gcnew NagDelegate();
              E04::E04UC_OBJFUN^ objfunE  =
                        gcnew  E04::E04UC_OBJFUN(nagdelegate,  &NagDelegate::objfun);
              E04::e04uc(n, nclin, ncnln, a, bl, bu, confunE, objfunE, iter, istate, c,
                         cjac, clamda, objf, objgrd, r, x, options, ifail);
  
              Console::WriteLine("The objective value on return is {0}. ",
                      objf);
              Console::WriteLine("Objfun has been  called {0} times, using class member",
                      nagdelegate->counter);
              Console::WriteLine("Objfun has been  called {0} times, using a static variable",
                      static_counter);
  
  
  
     return 0;
 }
When run the following output is produced:
e04uc Example Program Results
The objective value on return is 17.0140172891347.
Objfun has been  called 7 times, using class member
Objfun has been  called 7 times, using a static variable 

1 comment:

  1. Very useful post David. I have been using e04uc and my objfun needs lots of vectors that I cannot pass directly into objfun. So I made these static public or class-level variables which is a bit messy. Your method of having a class for the Nagdelegate works perfectly. I simply pass my data into the instance of the class. I now no longer have these global variables.

    ReplyDelete

NAG moderates all replies and reserves the right to not publish posts that are deemed inappropriate.