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