Gradients for the latent distribution
[openmx:openmx.git] / src / Compute.cpp
1 /*
2  *  Copyright 2013 The OpenMx Project
3  *
4  *  Licensed under the Apache License, Version 2.0 (the "License");
5  *  you may not use this file except in compliance with the License.
6  *  You may obtain a copy of the License at
7  *
8  *       http://www.apache.org/licenses/LICENSE-2.0
9  *
10  *   Unless required by applicable law or agreed to in writing, software
11  *   distributed under the License is distributed on an "AS IS" BASIS,
12  *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  *  See the License for the specific language governing permissions and
14  *  limitations under the License.
15  */
16
17 #include "omxDefines.h"
18 #include "Compute.h"
19 #include "omxState.h"
20 #include "omxExportBackendState.h"
21 #include "omxRFitFunction.h"
22
23 std::vector<int> FitContext::markMatrices;
24
25 void FitContext::init()
26 {
27         size_t numParam = varGroup->vars.size();
28         fit = parent? parent->fit : 0;
29         est = new double[numParam];
30         grad = new double[numParam];
31         hess = new double[numParam * numParam];
32 }
33
34 FitContext::FitContext()
35 {
36         parent = NULL;
37         varGroup = Global->freeGroup[0];
38         init();
39
40         size_t numParam = varGroup->vars.size();
41         for (size_t v1=0; v1 < numParam; v1++) {
42                 est[v1] = Global->freeGroup[0]->vars[v1]->start;
43                 grad[v1] = nan("unset");
44                 for (size_t v2=0; v2 < numParam; v2++) {
45                         hess[v1 * numParam + v2] = nan("unset");
46                 }
47         }
48 }
49
50 // arg to control what to copy? usually don't want everything TODO
51 FitContext::FitContext(FitContext *parent, FreeVarGroup *varGroup)
52 {
53         this->parent = parent;
54         this->varGroup = varGroup;
55         init();
56
57         FreeVarGroup *src = parent->varGroup;
58         FreeVarGroup *dest = varGroup;
59         size_t svars = parent->varGroup->vars.size();
60         size_t dvars = varGroup->vars.size();
61
62         size_t d1 = 0;
63         for (size_t s1=0; s1 < src->vars.size(); ++s1) {
64                 if (src->vars[s1] != dest->vars[d1]) continue;
65                 est[d1] = parent->est[s1];
66                 grad[d1] = parent->grad[s1];
67
68                 size_t d2 = 0;
69                 for (size_t s2=0; s2 < src->vars.size(); ++s2) {
70                         if (src->vars[s2] != dest->vars[d2]) continue;
71                         hess[d1 * dvars + d2] = parent->hess[s1 * svars + s2];
72                         if (++d2 == dvars) break;
73                 }
74
75                 if (++d1 == dvars) break;
76         }
77         if (d1 != dvars) error("Parent free parameter group is not a superset");
78
79         // pda(parent->est, 1, svars);
80         // pda(est, 1, dvars);
81         // pda(parent->grad, 1, svars);
82         // pda(grad, 1, dvars);
83         // pda(parent->hess, svars, svars);
84         // pda(hess, dvars, dvars);
85 }
86
87 void FitContext::copyParamToModel(omxMatrix *mat)
88 { copyParamToModel(mat->currentState); }
89
90 void FitContext::copyParamToModel(omxMatrix *mat, double *at)
91 { copyParamToModel(mat->currentState, at); }
92
93 void FitContext::updateParentAndFree()
94 {
95         FreeVarGroup *src = varGroup;
96         FreeVarGroup *dest = parent->varGroup;
97         size_t svars = varGroup->vars.size();
98         size_t dvars = parent->varGroup->vars.size();
99
100         parent->fit = fit;
101
102         size_t s1 = 0;
103         for (size_t d1=0; d1 < dest->vars.size(); ++d1) {
104                 if (dest->vars[d1] != src->vars[s1]) continue;
105                 parent->est[d1] = est[s1];
106                 parent->grad[d1] = grad[s1];
107
108                 size_t s2 = 0;
109                 for (size_t d2=0; d2 < dest->vars.size(); ++d2) {
110                         if (dest->vars[d2] != src->vars[s2]) continue;
111                         parent->hess[d1 * dvars + d2] = hess[s1 * svars + s2];
112                         if (++s2 == svars) break;
113                 }
114
115                 if (++s1 == svars) break;
116         }
117         
118         // pda(est, 1, svars);
119         // pda(parent->est, 1, dvars);
120         // pda(grad, 1, svars);
121         // pda(parent->grad, 1, dvars);
122         // pda(hess, svars, svars);
123         // pda(parent->hess, dvars, dvars);
124
125         delete this;
126 }
127
128 void FitContext::log(const char *where, int what)
129 {
130         size_t count = varGroup->vars.size();
131         std::string buf(where);
132         buf += " ---\n";
133         if (what & FF_COMPUTE_FIT) buf += string_snprintf("fit: %.5f\n", fit);
134         if (what & FF_COMPUTE_ESTIMATE) {
135                 buf += "est: c(";
136                 for (size_t vx=0; vx < count; ++vx) {
137                         buf += string_snprintf("%.5f", est[vx]);
138                         if (vx < count - 1) buf += ", ";
139                 }
140                 buf += ")\n";
141         }
142         if (what & FF_COMPUTE_GRADIENT) {
143                 buf += "grad: c(";
144                 for (size_t vx=0; vx < count; ++vx) {
145                         buf += string_snprintf("%.5f", grad[vx]);
146                         if (vx < count - 1) buf += ", ";
147                 }
148                 buf += ")\n";
149         }
150         if (what & FF_COMPUTE_HESSIAN) {
151                 buf += "hess: c(";
152                 for (size_t v1=0; v1 < count; ++v1) {
153                         for (size_t v2=0; v2 < count; ++v2) {
154                                 buf += string_snprintf("%.5f", hess[v1 * count + v2]);
155                                 if (v1 < count-1 || v2 < count-1) buf += ", ";
156                         }
157                         buf += "\n";
158                 }
159                 buf += ")\n";
160         }
161         mxLogBig(buf);
162 }
163
164 void FitContext::cacheFreeVarDependencies()
165 {
166         omxState *os = globalState;
167         size_t numMats = os->matrixList.size();
168         size_t numAlgs = os->algebraList.size();
169
170         markMatrices.clear();
171         markMatrices.resize(numMats + numAlgs, 0);
172
173         // More efficient to use the appropriate group instead of the default group. TODO
174         FreeVarGroup *varGroup = Global->freeGroup[0];
175         for (size_t freeVarIndex = 0; freeVarIndex < varGroup->vars.size(); freeVarIndex++) {
176                 omxFreeVar* freeVar = varGroup->vars[freeVarIndex];
177                 int *deps   = freeVar->deps;
178                 int numDeps = freeVar->numDeps;
179                 for (int index = 0; index < numDeps; index++) {
180                         markMatrices[deps[index] + numMats] = 1;
181                 }
182         }
183 }
184
185 void FitContext::fixHessianSymmetry()
186 {
187         // make non-symmetric entries symmetric, if possible
188         size_t numParam = varGroup->vars.size();
189         for (size_t h1=1; h1 < numParam; h1++) {
190                 for (size_t h2=0; h2 < h1; h2++) {
191                         double upper = hess[h1 * numParam + h2];
192                         double lower = hess[h2 * numParam + h1];
193                         if (isfinite(upper)) continue;
194                         if (isfinite(lower)) {
195                                 hess[h1 * numParam + h2] = lower;
196                         } else {
197                                 log("FitContext", FF_COMPUTE_ESTIMATE|FF_COMPUTE_GRADIENT|FF_COMPUTE_HESSIAN);
198                                 error("Hessian is not finite at [%d,%d]", h1,h2);
199                         }
200                 }
201         }
202 }
203
204 static void omxRepopulateRFitFunction(omxFitFunction* oo, double* x, int n)
205 {
206         omxRFitFunction* rFitFunction = (omxRFitFunction*)oo->argStruct;
207
208         SEXP theCall, estimate;
209
210         PROTECT(estimate = allocVector(REALSXP, n));
211         double *est = REAL(estimate);
212         for(int i = 0; i < n ; i++) {
213                 est[i] = x[i];
214         }
215
216         PROTECT(theCall = allocVector(LANGSXP, 4));
217
218         SETCAR(theCall, install("imxUpdateModelValues"));
219         SETCADR(theCall, rFitFunction->model);
220         SETCADDR(theCall, rFitFunction->flatModel);
221         SETCADDDR(theCall, estimate);
222
223         REPROTECT(rFitFunction->model = eval(theCall, R_GlobalEnv), rFitFunction->modelIndex);
224
225         UNPROTECT(2); // theCall, estimate
226 }
227
228 void FitContext::copyParamToModel(omxState* os)
229 {
230         copyParamToModel(os, est);
231 }
232
233 void FitContext::copyParamToModel(omxState* os, double *at)
234 {
235         size_t numParam = varGroup->vars.size();
236         if(OMX_DEBUG) {
237                 mxLog("Copying %d free parameter estimates to model %p", numParam, os);
238         }
239
240         if(numParam == 0) return;
241
242         size_t numMats = os->matrixList.size();
243         size_t numAlgs = os->algebraList.size();
244
245         os->computeCount++;
246
247         if(OMX_VERBOSE) {
248                 std::string buf;
249                 buf += string_snprintf("Call: %d.%d (%d) ", os->majorIteration, os->minorIteration, os->computeCount);
250                 buf += ("Estimates: [");
251                 for(size_t k = 0; k < numParam; k++) {
252                         buf += string_snprintf(" %f", at[k]);
253                 }
254                 buf += ("]\n");
255                 mxLogBig(buf);
256         }
257
258         for(size_t k = 0; k < numParam; k++) {
259                 omxFreeVar* freeVar = varGroup->vars[k];
260                 for(size_t l = 0; l < freeVar->locations.size(); l++) {
261                         omxFreeVarLocation *loc = &freeVar->locations[l];
262                         omxMatrix *matrix = os->matrixList[loc->matrix];
263                         int row = loc->row;
264                         int col = loc->col;
265                         omxSetMatrixElement(matrix, row, col, at[k]);
266                         if(OMX_DEBUG) {
267                                 mxLog("Setting location (%d, %d) of matrix %d to value %f for var %d",
268                                         row, col, loc->matrix, at[k], k);
269                         }
270                 }
271         }
272
273         if (RFitFunction) omxRepopulateRFitFunction(RFitFunction, at, numParam);
274
275         for(size_t i = 0; i < numMats; i++) {
276                 if (markMatrices[i]) {
277                         int offset = ~(i - numMats);
278                         omxMarkDirty(os->matrixList[offset]);
279                 }
280         }
281
282         for(size_t i = 0; i < numAlgs; i++) {
283                 if (markMatrices[i + numMats]) {
284                         omxMarkDirty(os->algebraList[i]);
285                 }
286         }
287
288         if (!os->childList) return;
289
290         for(int i = 0; i < Global->numChildren; i++) {
291                 copyParamToModel(os->childList[i]);
292         }
293 }
294
295 FitContext::~FitContext()
296 {
297         delete [] est;
298         delete [] grad;
299         delete [] hess;
300 }
301
302 omxFitFunction *FitContext::RFitFunction = NULL;
303
304 void FitContext::setRFitFunction(omxFitFunction *rff)
305 {
306         if (rff) {
307                 Global->numThreads = 1;
308                 if (RFitFunction) {
309                         error("You can only create 1 MxRFitFunction per independent model");
310                 }
311         }
312         RFitFunction = rff;
313 }
314
315 omxCompute::~omxCompute()
316 {}
317
318 void omxComputeOperation::initFromFrontend(SEXP rObj)
319 {
320         SEXP slotValue;
321         PROTECT(slotValue = GET_SLOT(rObj, install("id")));
322         int id = INTEGER(slotValue)[0];
323         varGroup = Global->findVarGroup(id);
324         if (!varGroup) varGroup = Global->freeGroup[0];
325 }
326
327 class omxComputeSequence : public omxCompute {
328         std::vector< omxCompute* > clist;
329
330  public:
331         virtual void initFromFrontend(SEXP rObj);
332         virtual void compute(FitContext *fc);
333         virtual void reportResults(FitContext *fc, MxRList *out);
334         virtual double getOptimizerStatus();
335         virtual ~omxComputeSequence();
336 };
337
338 class omxComputeIterate : public omxCompute {
339         std::vector< omxCompute* > clist;
340         int maxIter;
341         double tolerance;
342         bool verbose;
343
344  public:
345         virtual void initFromFrontend(SEXP rObj);
346         virtual void compute(FitContext *fc);
347         virtual void reportResults(FitContext *fc, MxRList *out);
348         virtual double getOptimizerStatus();
349         virtual ~omxComputeIterate();
350 };
351
352 class omxComputeOnce : public omxComputeOperation {
353         typedef omxComputeOperation super;
354         std::vector< omxMatrix* > algebras;
355         std::vector< omxExpectation* > expectations;
356         bool start;
357         const char *context;
358         bool gradient;
359         bool hessian;
360
361  public:
362         virtual void initFromFrontend(SEXP rObj);
363         virtual void compute(FitContext *fc);
364         virtual void reportResults(FitContext *fc, MxRList *out);
365 };
366
367 static class omxCompute *newComputeSequence()
368 { return new omxComputeSequence(); }
369
370 static class omxCompute *newComputeIterate()
371 { return new omxComputeIterate(); }
372
373 static class omxCompute *newComputeOnce()
374 { return new omxComputeOnce(); }
375
376 struct omxComputeTableEntry {
377         char name[32];
378         omxCompute *(*ctor)();
379 };
380
381 static const struct omxComputeTableEntry omxComputeTable[] = {
382         {"MxComputeEstimatedHessian", &newComputeEstimatedHessian},
383         {"MxComputeGradientDescent", &newComputeGradientDescent},
384         {"MxComputeSequence", &newComputeSequence },
385         {"MxComputeIterate", &newComputeIterate },
386         {"MxComputeOnce", &newComputeOnce },
387         {"MxComputeNewtonRaphson", &newComputeNewtonRaphson},
388 };
389
390 omxCompute *omxNewCompute(omxState* os, const char *type)
391 {
392         omxCompute *got = NULL;
393
394         for (size_t fx=0; fx < OMX_STATIC_ARRAY_SIZE(omxComputeTable); fx++) {
395                 const struct omxComputeTableEntry *entry = omxComputeTable + fx;
396                 if(strcmp(type, entry->name) == 0) {
397                         got = entry->ctor();
398                         break;
399                 }
400         }
401
402         if (!got) error("Compute %s is not implemented", type);
403
404         return got;
405 }
406
407 void omxComputeSequence::initFromFrontend(SEXP rObj)
408 {
409         SEXP slotValue;
410         PROTECT(slotValue = GET_SLOT(rObj, install("steps")));
411
412         for (int cx = 0; cx < length(slotValue); cx++) {
413                 SEXP step = VECTOR_ELT(slotValue, cx);
414                 SEXP s4class;
415                 PROTECT(s4class = STRING_ELT(getAttrib(step, install("class")), 0));
416                 omxCompute *compute = omxNewCompute(globalState, CHAR(s4class));
417                 compute->initFromFrontend(step);
418                 if (isErrorRaised(globalState)) break;
419                 clist.push_back(compute);
420         }
421 }
422
423 void omxComputeSequence::compute(FitContext *fc)
424 {
425         for (size_t cx=0; cx < clist.size(); ++cx) {
426                 FitContext *context = fc;
427                 if (fc->varGroup != clist[cx]->varGroup) {
428                         context = new FitContext(fc, clist[cx]->varGroup);
429                 }
430                 clist[cx]->compute(context);
431                 if (context != fc) context->updateParentAndFree();
432                 if (isErrorRaised(globalState)) break;
433         }
434 }
435
436 void omxComputeSequence::reportResults(FitContext *fc, MxRList *out)
437 {
438         // put this stuff in a new list?
439         // merge with Iterate TODO
440         for (size_t cx=0; cx < clist.size(); ++cx) {
441                 FitContext *context = fc;
442                 if (fc->varGroup != clist[cx]->varGroup) {
443                         context = new FitContext(fc, clist[cx]->varGroup);
444                 }
445                 clist[cx]->reportResults(context, out);
446                 if (context != fc) context->updateParentAndFree();
447                 if (isErrorRaised(globalState)) break;
448         }
449 }
450
451 double omxComputeSequence::getOptimizerStatus()
452 {
453         // for backward compatibility, not indended to work generally
454         for (size_t cx=0; cx < clist.size(); ++cx) {
455                 double got = clist[cx]->getOptimizerStatus();
456                 if (got != NA_REAL) return got;
457         }
458         return NA_REAL;
459 }
460
461 omxComputeSequence::~omxComputeSequence()
462 {
463         for (size_t cx=0; cx < clist.size(); ++cx) {
464                 delete clist[cx];
465         }
466 }
467
468 void omxComputeIterate::initFromFrontend(SEXP rObj)
469 {
470         SEXP slotValue;
471
472         PROTECT(slotValue = GET_SLOT(rObj, install("maxIter")));
473         maxIter = INTEGER(slotValue)[0];
474
475         PROTECT(slotValue = GET_SLOT(rObj, install("tolerance")));
476         tolerance = REAL(slotValue)[0];
477         if (tolerance <= 0) error("tolerance must be positive");
478
479         PROTECT(slotValue = GET_SLOT(rObj, install("steps")));
480
481         for (int cx = 0; cx < length(slotValue); cx++) {
482                 SEXP step = VECTOR_ELT(slotValue, cx);
483                 SEXP s4class;
484                 PROTECT(s4class = STRING_ELT(getAttrib(step, install("class")), 0));
485                 omxCompute *compute = omxNewCompute(globalState, CHAR(s4class));
486                 compute->initFromFrontend(step);
487                 if (isErrorRaised(globalState)) break;
488                 clist.push_back(compute);
489         }
490
491         PROTECT(slotValue = GET_SLOT(rObj, install("verbose")));
492         verbose = asLogical(slotValue);
493 }
494
495 void omxComputeIterate::compute(FitContext *fc)
496 {
497         int iter = 0;
498         double prevFit = 0;
499         double change = tolerance * 10;
500         while (1) {
501                 for (size_t cx=0; cx < clist.size(); ++cx) {
502                         FitContext *context = fc;
503                         if (fc->varGroup != clist[cx]->varGroup) {
504                                 context = new FitContext(fc, clist[cx]->varGroup);
505                         }
506                         clist[cx]->compute(context);
507                         if (context != fc) context->updateParentAndFree();
508                         if (isErrorRaised(globalState)) break;
509                 }
510                 if (fc->fit == 0) {
511                         warning("Fit estimated at 0; something is wrong");
512                         break;
513                 }
514                 if (prevFit != 0) {
515                         change = prevFit - fc->fit;
516                         if (verbose) mxLog("fit %.9g change %.9g", fc->fit, change);
517                 }
518                 prevFit = fc->fit;
519                 if (isErrorRaised(globalState) || ++iter > maxIter || fabs(change) < tolerance) break;
520         }
521 }
522
523 void omxComputeIterate::reportResults(FitContext *fc, MxRList *out)
524 {
525         for (size_t cx=0; cx < clist.size(); ++cx) {
526                 FitContext *context = fc;
527                 if (fc->varGroup != clist[cx]->varGroup) {
528                         context = new FitContext(fc, clist[cx]->varGroup);
529                 }
530                 clist[cx]->reportResults(context, out);
531                 if (context != fc) context->updateParentAndFree();
532                 if (isErrorRaised(globalState)) break;
533         }
534 }
535
536 double omxComputeIterate::getOptimizerStatus()
537 {
538         // for backward compatibility, not indended to work generally
539         for (size_t cx=0; cx < clist.size(); ++cx) {
540                 double got = clist[cx]->getOptimizerStatus();
541                 if (got != NA_REAL) return got;
542         }
543         return NA_REAL;
544 }
545
546 omxComputeIterate::~omxComputeIterate()
547 {
548         for (size_t cx=0; cx < clist.size(); ++cx) {
549                 delete clist[cx];
550         }
551 }
552
553 void omxComputeOnce::initFromFrontend(SEXP rObj)
554 {
555         super::initFromFrontend(rObj);
556
557         SEXP slotValue;
558         PROTECT(slotValue = GET_SLOT(rObj, install("what")));
559         for (int wx=0; wx < length(slotValue); ++wx) {
560                 int objNum = INTEGER(slotValue)[wx];
561                 if (objNum >= 0) {
562                         omxMatrix *algebra = globalState->algebraList[objNum];
563                         if (algebra->fitFunction) {
564                                 setFreeVarGroup(algebra->fitFunction, varGroup);
565                                 omxCompleteFitFunction(algebra);
566                         }
567                         algebras.push_back(algebra);
568                 } else {
569                         omxExpectation *expectation = globalState->expectationList[~objNum];
570                         setFreeVarGroup(expectation, varGroup);
571                         omxCompleteExpectation(expectation);
572                         expectations.push_back(expectation);
573                 }
574         }
575
576         context = "";
577
578         PROTECT(slotValue = GET_SLOT(rObj, install("context")));
579         if (length(slotValue) == 0) {
580                 // OK
581         } else if (length(slotValue) == 1) {
582                 SEXP elem;
583                 PROTECT(elem = STRING_ELT(slotValue, 0));
584                 context = CHAR(elem);
585         }
586
587         PROTECT(slotValue = GET_SLOT(rObj, install("gradient")));
588         gradient = asLogical(slotValue);
589
590         PROTECT(slotValue = GET_SLOT(rObj, install("hessian")));
591         hessian = asLogical(slotValue);
592
593         if (algebras.size() == 1 && algebras[0]->fitFunction) {
594                 omxFitFunction *ff = algebras[0]->fitFunction;
595                 if (gradient && !ff->gradientAvailable) {
596                         error("Gradient requested but not available");
597                 }
598                 if (hessian && !ff->hessianAvailable) {
599                         error("Hessian requested but not available");
600                 }
601         }
602
603         PROTECT(slotValue = GET_SLOT(rObj, install("start")));
604         start = asLogical(slotValue);
605 }
606
607 void omxComputeOnce::compute(FitContext *fc)
608 {
609         if (algebras.size()) {
610                 int want = FF_COMPUTE_FIT;
611                 size_t numParam = fc->varGroup->vars.size();
612                 if (gradient) {
613                         want |= FF_COMPUTE_GRADIENT;
614                         OMXZERO(fc->grad, numParam);
615                 }
616                 if (hessian) {
617                         want |= FF_COMPUTE_HESSIAN;
618                         OMXZERO(fc->hess, numParam * numParam);
619                 }
620
621                 for (size_t wx=0; wx < algebras.size(); ++wx) {
622                         omxMatrix *algebra = algebras[wx];
623                         if (algebra->fitFunction) {
624                                 if (start) {
625                                         omxFitFunctionCompute(algebra->fitFunction, FF_COMPUTE_PREOPTIMIZE, fc);
626                                         fc->copyParamToModel(globalState);
627                                 }
628
629                                 omxFitFunctionCompute(algebra->fitFunction, want, fc);
630                                 fc->fit = algebra->data[0];
631                                 if (hessian) fc->fixHessianSymmetry();
632                         } else {
633                                 omxForceCompute(algebra);
634                         }
635                 }
636         } else if (expectations.size()) {
637                 for (size_t wx=0; wx < expectations.size(); ++wx) {
638                         omxExpectation *expectation = expectations[wx];
639                         omxExpectationCompute(expectation, context);
640                 }
641         }
642 }
643
644 void omxComputeOnce::reportResults(FitContext *fc, MxRList *out)
645 {
646         if (algebras.size()==0 || algebras[0]->fitFunction == NULL) return;
647
648         omxMatrix *algebra = algebras[0];
649
650         omxPopulateFitFunction(algebra, out);
651
652         out->push_back(std::make_pair(mkChar("minimum"), ScalarReal(fc->fit)));
653         out->push_back(std::make_pair(mkChar("Minus2LogLikelihood"), ScalarReal(fc->fit)));
654
655         size_t numFree = fc->varGroup->vars.size();
656         if (numFree) {
657                 SEXP estimate;
658                 PROTECT(estimate = allocVector(REALSXP, numFree));
659                 memcpy(REAL(estimate), fc->est, sizeof(double)*numFree);
660                 out->push_back(std::make_pair(mkChar("estimate"), estimate));
661
662                 if (gradient) {
663                         SEXP Rgradient;
664                         PROTECT(Rgradient = allocVector(REALSXP, numFree));
665                         memcpy(REAL(Rgradient), fc->grad, sizeof(double) * numFree);
666                         out->push_back(std::make_pair(mkChar("gradient"), Rgradient));
667                 }
668
669                 if (hessian) {
670                         SEXP Rhessian;
671                         PROTECT(Rhessian = allocMatrix(REALSXP, numFree, numFree));
672                         memcpy(REAL(Rhessian), fc->hess, sizeof(double) * numFree * numFree);
673                         out->push_back(std::make_pair(mkChar("hessian"), Rhessian));
674                 }
675         }
676 }