1 // Copyright (C) 2008 Peter Carbonetto. All Rights Reserved.
2 // This code is published under the Eclipse Public License.
3 //
4 // Author: Peter Carbonetto
5 //         Dept. of Computer Science
6 //         University of British Columbia
7 //         September 18, 2008
8 
9 #include "callbackfunctions.hpp"
10 #include "matlabexception.hpp"
11 
12 // Functions for class CallbackFunctions.
13 // -----------------------------------------------------------------
CallbackFunctions(const mxArray * ptr)14 CallbackFunctions::CallbackFunctions (const mxArray* ptr)
15   : objfunc(0), gradfunc(0), constraintfunc(0), jacobianfunc(0),
16   jacstrucfunc(0), hessianfunc(0), hesstrucfunc(0), iterfunc(0) {
17   const mxArray* p;  // A pointer to a MATLAB array.
18 
19   // Check whether we are provided with a structure array.
20   if (!mxIsStruct(ptr))
21     throw MatlabException("The second input must be a STRUCT");
22 
23   // Get the function handle for computing the objective.
24   p = mxGetField(ptr,0,"objective");
25   if (!p)
26     throw MatlabException("You must specify a callback routine for \
27 computing the value of the objective function");
28   if (mxIsEmpty(p) || !isFunctionHandle(p))
29     throw MatlabException("You did not provide a valid function handle for \
30 computing the value of the objective function");
31   objfunc = new MatlabFunctionHandle(p);
32 
33   // Get the function handle for computing the gradient.
34   p = mxGetField(ptr,0,"gradient");
35   if (!p)
36     throw MatlabException("You must specify a callback routine for \
37 computing the gradient of the objective");
38   if (mxIsEmpty(p) || !isFunctionHandle(p))
39     throw MatlabException("You did not provide a valid function handle for \
40 computing the gradient of the objective");
41   gradfunc = new MatlabFunctionHandle(p);
42 
43   // Get the function handle for computing the constraints, if such a
44   // function was specified.
45   p = mxGetField(ptr,0,"constraints");
46   if (p) {
47     if (mxIsEmpty(p) || !isFunctionHandle(p))
48       throw MatlabException("You did not provide a valid function handle \
49 for computing the response of the constraints");
50     constraintfunc = new MatlabFunctionHandle(p);
51   }
52   else
53     constraintfunc = new MatlabFunctionHandle();
54 
55   // Get the function handle for computing the Jacobian. This function
56   // is necessary if there are constraints.
57   p = mxGetField(ptr,0,"jacobian");
58   if (p) {
59     if (mxIsEmpty(p) || !isFunctionHandle(p))
60       throw MatlabException("You did not provide a valid function handle \
61 for computing the first derivatives (Jacobian) of the constraints");
62     jacobianfunc = new MatlabFunctionHandle(p);
63   }
64   else {
65     if (*constraintfunc)
66       throw MatlabException("You must provide a function that returns the \
67 first derivatives (Jacobian) of the constraints");
68     jacobianfunc = new MatlabFunctionHandle();
69   }
70 
71   // Get the function handle for computing the sparsity structure of
72   // the Jacobian. This function is necessary if the Jacobian is being
73   // computed.
74   p = mxGetField(ptr,0,"jacobianstructure");
75   if (p) {
76     if (mxIsEmpty(p) || !isFunctionHandle(p))
77       throw MatlabException("You did not provide a valid function handle \
78 for computing the sparsity structure of the Jacobian");
79     jacstrucfunc = new MatlabFunctionHandle(p);
80   }
81   else {
82     if (*jacobianfunc)
83       throw MatlabException("You must provide a function that returns the \
84 sparsity structure of the Jacobian");
85     jacstrucfunc = new MatlabFunctionHandle();
86   }
87 
88   // Get the function handle for computing the Hessian. This function
89   // is always optional.
90   p = mxGetField(ptr,0,"hessian");
91   if (p) {
92     if (mxIsEmpty(p) || !isFunctionHandle(p))
93       throw MatlabException("You did not provide a valid function handle \
94 for computing the Hessian of the Lagrangian");
95     hessianfunc = new MatlabFunctionHandle(p);
96   }
97   else
98     hessianfunc = new MatlabFunctionHandle();
99 
100   // Get the function handle for computing the sparsity structure of
101   // the Hessian of the Lagrangian. This function is necessary if the
102   // Hessian is being computed.
103   p = mxGetField(ptr,0,"hessianstructure");
104   if (p) {
105     if (mxIsEmpty(p) || !isFunctionHandle(p))
106       throw MatlabException("You did not provide a valid function handle \
107 for computing the sparsity structure of the Hessian");
108     hesstrucfunc = new MatlabFunctionHandle(p);
109   }
110   else {
111     if (*hessianfunc)
112       throw MatlabException("You must provide a function that returns the \
113 sparsity structure of the Hessian");
114     hesstrucfunc = new MatlabFunctionHandle();
115   }
116 
117   // Get the iterative callback function handle. This function is
118   // always optional.
119   p = mxGetField(ptr,0,"iterfunc");
120   if (p) {
121     if (mxIsEmpty(p) || !isFunctionHandle(p))
122       throw MatlabException("You did not provide a valid function handle \
123 for the iterative callback");
124     iterfunc = new MatlabFunctionHandle(p);
125   }
126   else
127     iterfunc = new MatlabFunctionHandle();
128 }
129 
~CallbackFunctions()130 CallbackFunctions::~CallbackFunctions() {
131   if (objfunc)        delete objfunc;
132   if (gradfunc)       delete gradfunc;
133   if (constraintfunc) delete constraintfunc;
134   if (jacobianfunc)   delete jacobianfunc;
135   if (jacstrucfunc)   delete jacstrucfunc;
136   if (hessianfunc)    delete hessianfunc;
137   if (hesstrucfunc)   delete hesstrucfunc;
138   if (iterfunc)       delete iterfunc;
139 }
140 
computeObjective(const Iterate & x) const141 double CallbackFunctions::computeObjective (const Iterate& x) const {
142   double         f;  // The return value.
143   bool           success;
144   const mxArray* inputs[1];
145   mxArray*       outputs[1];
146 
147   // Call the MATLAB callback function.
148   inputs[0] = x;
149   success = objfunc->evaluate(1,1,inputs,outputs);
150   if (!success)
151     throw MatlabException("There was an error when executing the objective \
152 callback function");
153 
154   // Get the output from the MATLAB callback function, which is the
155   // value of the objective function at x.
156   mxArray* ptr = outputs[0];
157   if (!mxIsDouble(ptr) || mxIsComplex(ptr) || mxGetNumberOfElements(ptr) != 1)
158     throw MatlabException("The first return value of the objective \
159 callback function must be a real double scalar");
160   if (mxIsSparse(ptr)) {
161     // convert sparse objective (unlikely but possible) to full
162     mexCallMATLAB(1, &ptr, 1, outputs, "full");
163     mxDestroyArray(outputs[0]);
164   }
165   f = *mxGetPr(ptr);
166 
167   // Free the dynamically allocated memory.
168   mxDestroyArray(ptr);
169 
170   return f;
171 }
172 
computeGradient(const Iterate & x,double * g) const173 void CallbackFunctions::computeGradient (const Iterate& x, double* g) const {
174   bool           success;
175   const mxArray* inputs[1];
176   mxArray*       outputs[1];
177 
178   // Call the MATLAB callback function.
179   inputs[0] = x;
180   success = gradfunc->evaluate(1,1,inputs,outputs);
181   if (!success)
182     throw MatlabException("There was an error when executing the \
183 gradient callback function");
184 
185   // Get the output from the MATLAB callback function, which is the
186   // value of the gradient of the objective function at x.
187   mxArray* ptr = outputs[0];
188   if (!mxIsCell(ptr) && (!mxIsDouble(ptr) || mxIsComplex(ptr)))
189     throw MatlabException("The gradient callback must return a real double vector");
190   if (mxIsSparse(ptr)) {
191     // convert sparse gradient to full (simplest method, not fastest)
192     mexCallMATLAB(1, &ptr, 1, outputs, "full");
193     mxDestroyArray(outputs[0]);
194   }
195   Iterate grad(ptr);
196   if (numvars(x) != numvars(grad))
197     throw MatlabException("Invalid gradient passed back from MATLAB \
198 routine");
199   grad.copyto(g);
200 
201   // Free the dynamically allocated memory.
202   mxDestroyArray(ptr);
203 }
204 
computeConstraints(const Iterate & x,int m,double * c) const205 void CallbackFunctions::computeConstraints(const Iterate& x, int m, double* c) const {
206   bool           success;
207   const mxArray* inputs[1];
208   mxArray*       outputs[1];
209 
210   // Call the MATLAB callback function.
211   inputs[0] = x;
212   success = constraintfunc->evaluate(1,1,inputs,outputs);
213   if (!success)
214     throw MatlabException("There was an error when executing the \
215 constraints callback function");
216 
217   // Get the output from the MATLAB callback function, which is the
218   // value of vector-valued constraint function at x.
219   mxArray* ptr = outputs[0];
220   if ((unsigned) m != mxGetNumberOfElements(ptr))
221     throw MatlabException("Invalid constraint values passed back from \
222 Matlab routine");
223   if (!mxIsDouble(ptr) || mxIsComplex(ptr))
224     throw MatlabException("The constraint callback must return a real double vector");
225   if (mxIsSparse(ptr)) {
226     // convert sparse constraint vector (unlikely but possible) to full
227     mexCallMATLAB(1, &ptr, 1, outputs, "full");
228     mxDestroyArray(outputs[0]);
229   }
230   copymemory(mxGetPr(ptr),c,m);
231 
232   // Free the dynamically allocated memory.
233   mxDestroyArray(ptr);
234 }
235 
getJacobianStructure(int n,int m) const236 SparseMatrix* CallbackFunctions::getJacobianStructure (int n, int m) const {
237   const mxArray* inputs[1];
238   mxArray*       outputs[1];
239   bool           success;
240 
241   // Call the MATLAB callback function.
242   success = jacstrucfunc->evaluate(0,1,inputs,outputs);
243   if (!success)
244     throw MatlabException("There was an error when getting the structure \
245 of the Jacobian matrix from MATLAB");
246 
247   // Get the output from the MATLAB callback function, which is the
248   // sparse matrix specifying the structure of the Jacobian.
249   mxArray* ptr = outputs[0];
250   if (!mxIsSparse(ptr) || mxIsComplex(ptr))
251     throw MatlabException("Jacobian must be a real sparse matrix");
252   if ((int) mxGetM(ptr) != m || (int) mxGetN(ptr) != n ||
253       !SparseMatrix::inIncOrder(ptr))
254     throw MatlabException("Jacobian must be an m x n sparse matrix with \
255 row indices in increasing order, where m is the number of constraints and \
256 n is the number of variables");
257   if (!mxIsDouble(ptr)) {
258     // convert non-double Jacobian structure to double
259     mexCallMATLAB(1, &ptr, 1, outputs, "double");
260     mxDestroyArray(outputs[0]);
261   }
262   SparseMatrix* J = new SparseMatrix(ptr);  // The return value.
263 
264   // Free the dynamically allocated memory.
265   mxDestroyArray(ptr);
266 
267   return J;
268 }
269 
getHessianStructure(int n) const270 SparseMatrix* CallbackFunctions::getHessianStructure (int n) const {
271   const mxArray* inputs[1];
272   mxArray*       outputs[1];
273   bool           success;
274 
275   // Call the MATLAB callback function.
276   success = hesstrucfunc->evaluate(0,1,inputs,outputs);
277   if (!success)
278     throw MatlabException("There was an error when getting the structure \
279 of the Hessian matrix from MATLAB");
280 
281   // Get the output from the MATLAB callback function, which is the
282   // sparse matrix specifying the structure of the Hessian.
283   mxArray* ptr = outputs[0];
284   if (!mxIsSparse(ptr) || mxIsComplex(ptr))
285     throw MatlabException("Hessian must be a real sparse matrix");
286   if ((int) mxGetM(ptr) != n || (int) mxGetN(ptr) != n ||
287       !SparseMatrix::isLowerTri(ptr) || !SparseMatrix::inIncOrder(ptr))
288     throw MatlabException("Hessian must be an n x n sparse, symmetric and \
289 lower triangular matrix with row indices in increasing order, where n is \
290 the number of variables");
291   if (!mxIsDouble(ptr)) {
292     // convert non-double Hessian structure to double
293     mexCallMATLAB(1, &ptr, 1, outputs, "double");
294     mxDestroyArray(outputs[0]);
295   }
296   SparseMatrix* H = new SparseMatrix(ptr);  // The return value.
297 
298   // Free the dynamically allocated memory.
299   mxDestroyArray(ptr);
300 
301   return H;
302 }
303 
computeJacobian(int m,const Iterate & x,SparseMatrix & J) const304 void CallbackFunctions::computeJacobian (int m, const Iterate& x,
305 					 SparseMatrix& J) const {
306   bool           success;
307   const mxArray* inputs[1];
308   mxArray*       outputs[1];
309 
310   // Call the MATLAB callback function.
311   inputs[0] = x;
312   success = jacobianfunc->evaluate(1,1,inputs,outputs);
313   if (!success)
314     throw MatlabException("There was an error when executing the \
315 Jacobian callback function");
316 
317   // Get the output from the MATLAB callback function, which is the
318   // sparse matrix specifying the value the Jacobian.
319   mxArray* ptr = outputs[0];
320   if (!mxIsSparse(ptr) || mxIsComplex(ptr))
321     throw MatlabException("Jacobian must be a real sparse matrix");
322   if ((int) mxGetM(ptr) != m || (int) mxGetN(ptr) != numvars(x) ||
323       !SparseMatrix::inIncOrder(ptr))
324     throw MatlabException("Jacobian must be an m x n sparse matrix with \
325 row indices in increasing order, where m is the number of constraints and \
326 n is the number of variables");
327   if (!mxIsDouble(ptr)) {
328     // convert non-double Jacobian to double
329     mexCallMATLAB(1, &ptr, 1, outputs, "double");
330     mxDestroyArray(outputs[0]);
331   }
332   SparseMatrix Jnew(ptr);
333   Jnew.copyto(J);
334 
335   // Free the dynamically allocated memory.
336   mxDestroyArray(ptr);
337 }
338 
computeHessian(const Iterate & x,double sigma,int m,const double * lambda,SparseMatrix & H) const339 void CallbackFunctions::computeHessian (const Iterate& x, double sigma, int m,
340 					const double* lambda, SparseMatrix& H) const {
341   bool           success;
342   const mxArray* inputs[3];
343   mxArray*       outputs[1];
344 
345   // Create the input arguments to the MATLAB routine, sigma and lambda.
346   mxArray* psigma  = mxCreateDoubleScalar(sigma);
347   mxArray* plambda = mxCreateDoubleMatrix(m,1,mxREAL);
348   copymemory(lambda,mxGetPr(plambda),m);
349 
350   // Call the MATLAB callback function.
351   inputs[0] = x;
352   inputs[1] = psigma;
353   inputs[2] = plambda;
354   success = hessianfunc->evaluate(3,1,inputs,outputs);
355   if (!success)
356     throw MatlabException("There was an error when executing the Hessian \
357 callback function");
358 
359   // Get the output from the MATLAB callback function, which is the
360   // sparse matrix specifying the value the Hessian.
361   mxArray* ptr = outputs[0];
362   if (!mxIsSparse(ptr) || mxIsComplex(ptr))
363     throw MatlabException("Hessian must be a real sparse matrix");
364   if ((int) mxGetM(ptr) != numvars(x) || (int) mxGetN(ptr) != numvars(x) ||
365       !SparseMatrix::isLowerTri(ptr) || !SparseMatrix::inIncOrder(ptr))
366     throw MatlabException("Hessian must be an n x n sparse, symmetric and \
367 lower triangular matrix with row indices in increasing order, where n is \
368 the number of variables");
369   if (!mxIsDouble(ptr)) {
370     // convert non-double Hessian to double
371     mexCallMATLAB(1, &ptr, 1, outputs, "double");
372     mxDestroyArray(outputs[0]);
373   }
374   SparseMatrix Hnew(ptr);
375   Hnew.copyto(H);
376 
377   // Free the dynamically allocated memory.
378   mxDestroyArray(ptr);
379   mxDestroyArray(psigma);
380   mxDestroyArray(plambda);
381 }
382 
iterCallback(int t,double f,double inf_pr,double inf_du,double mu,double d_norm,double regularization_size,double alpha_du,double alpha_pr,int ls_trials,const Ipopt::IpoptData * ip_data,Ipopt::IpoptCalculatedQuantities * ip_cq,int n) const383 bool CallbackFunctions::iterCallback (int t, double f,
384 				      double inf_pr, double inf_du,
385 				      double mu, double d_norm,
386 				      double regularization_size,
387 				      double alpha_du, double alpha_pr,
388 				      int ls_trials, const Ipopt::IpoptData* ip_data,
389 				      Ipopt::IpoptCalculatedQuantities* ip_cq,
390 				      int n) const {
391   bool           success;
392   const mxArray* inputs[3];
393   mxArray*       outputs[1];
394 
395   // Create the input arguments to the MATLAB routine.
396   mxArray* pt = mxCreateDoubleScalar(t);
397   mxArray* pf = mxCreateDoubleScalar(f);
398 
399   // Create structure to hold extra IPOPT variables
400   const char* varfields[9];
401   varfields[0] = "x";
402   varfields[1] = "inf_pr";
403   varfields[2] = "inf_du";
404   varfields[3] = "mu";
405   varfields[4] = "d_norm";
406   varfields[5] = "regularization_size";
407   varfields[6] = "alpha_du";
408   varfields[7] = "alpha_pr";
409   varfields[8] = "ls_trials";
410   mxArray *varStruct = mxCreateStructMatrix(1,1,9,varfields);
411   mxSetField(varStruct,0,"inf_pr",mxCreateDoubleScalar(inf_pr));
412   mxSetField(varStruct,0,"inf_du",mxCreateDoubleScalar(inf_du));
413   mxSetField(varStruct,0,"mu",mxCreateDoubleScalar(mu));
414   mxSetField(varStruct,0,"d_norm",mxCreateDoubleScalar(d_norm));
415   mxSetField(varStruct,0,"regularization_size",mxCreateDoubleScalar(regularization_size));
416   mxSetField(varStruct,0,"alpha_du",mxCreateDoubleScalar(alpha_du));
417   mxSetField(varStruct,0,"alpha_pr",mxCreateDoubleScalar(alpha_pr));
418   mxSetField(varStruct,0,"ls_trials",mxCreateDoubleScalar(ls_trials));
419 
420   //The following code translates IPOPT's NLP to the Original NLP, so we can extract x
421   //Original code by Steven Dirske, Stefan Vigerske [GAMS]
422   Ipopt::TNLPAdapter* tnlp_adapter = NULL;
423   if(ip_cq != NULL) {
424       Ipopt::OrigIpoptNLP* orignlp = dynamic_cast<Ipopt::OrigIpoptNLP*>(GetRawPtr(ip_cq->GetIpoptNLP()));
425       if(orignlp != NULL)
426           tnlp_adapter = dynamic_cast<Ipopt::TNLPAdapter*>(GetRawPtr(orignlp->nlp()));
427   }
428   //If we successfully converted the NLP, extract x [ResortX auto sorts and fills in fixed vars]
429   if(tnlp_adapter != NULL && ip_data != NULL && IsValid(ip_data->curr())) {
430       mxSetField(varStruct,0,"x",mxCreateDoubleMatrix(n,1,mxREAL)); //ip_data->curr()->x()->Dim()+1
431       tnlp_adapter->ResortX(*ip_data->curr()->x(),mxGetPr(mxGetField(varStruct,0,"x")));
432   }
433 
434   // Call the MATLAB callback function.
435   inputs[0] = pt;
436   inputs[1] = pf;
437   inputs[2] = varStruct;
438   success = iterfunc->evaluate(3,1,inputs,outputs);
439   if (!success)
440     throw MatlabException("There was an error when executing the iterative \
441 callback function");
442 
443   // Get the output from the MATLAB callback function, which is a
444   // boolean value telling whether or not IPOPT should continue.
445   mxArray* ptr = outputs[0];
446   if (!mxIsLogicalScalar(ptr))
447     throw MatlabException("The return value for the iterative callback must \
448 either be TRUE or FALSE");
449   bool b = mxIsLogicalScalarTrue(ptr);
450 
451   // Free the dynamically allocated memory.
452   mxDestroyArray(ptr);
453   mxDestroyArray(pt);
454   mxDestroyArray(pf);
455   mxDestroyArray(varStruct);
456 
457   return b;
458 }
459