void omxGlobal::reportProgress(const char *context, FitContext *fc) { if (omx_absolute_thread_num() != 0) { mxLog("omxGlobal::reportProgress called in a thread context (report this bug to developers)"); return; } R_CheckUserInterrupt(); time_t now = time(0); if (Global->maxSeconds > 0 && now > Global->startTime + Global->maxSeconds && !Global->timedOut) { Global->timedOut = true; Rf_warning("Time limit of %d minutes %d seconds exceeded", Global->maxSeconds/60, Global->maxSeconds % 60); } if (silent || now - lastProgressReport < 1 || fc->getGlobalComputeCount() == previousComputeCount) return; lastProgressReport = now; std::string str; if (previousReportFit == 0.0 || previousReportFit == fc->fit) { str = string_snprintf("%s %d %.6g", context, fc->getGlobalComputeCount(), fc->fit); } else { str = string_snprintf("%s %d %.6g %.4g", context, fc->getGlobalComputeCount(), fc->fit, fc->fit - previousReportFit); } reportProgressStr(str.c_str()); previousReportLength = str.size(); previousReportFit = fc->fit; previousComputeCount = fc->getGlobalComputeCount(); }
void omxSadmvnWrapper(omxFitFunction *oo, omxMatrix *cov, omxMatrix *ordCov, double *corList, double *lThresh, double *uThresh, int *Infin, double *likelihood, int *inform) { // SADMVN calls Alan Genz's sadmvn.f--see appropriate file for licensing info. // TODO: Check with Genz: should we be using sadmvn or sadmvn? // Parameters are: // N int # of vars // Lower double* Array of lower bounds // Upper double* Array of upper bounds // Infin int* Array of flags: 0 = (-Inf, upper] 1 = [lower, Inf), 2 = [lower, upper] // Correl double* Array of correlation coeffs: in row-major lower triangular order // MaxPts int Maximum # of function values (use 1000*N or 1000*N*N) // Abseps double Absolute Rf_error tolerance. Yick. // Releps double Relative Rf_error tolerance. Use EPSILON. // Error &double On return: absolute real Rf_error, 99% confidence // Value &double On return: evaluated value // Inform &int On return: 0 = OK; 1 = Rerun, increase MaxPts; 2 = Bad input // TODO: Separate block diagonal covariance matrices into pieces for integration separately double Error; double absEps = Global->absEps; double relEps = Global->relEps; int MaxPts = Global->maxptsa + Global->maxptsb * cov->rows + Global->maxptsc * cov->rows * cov->rows; int numVars = ordCov->rows; int fortranThreadId = omx_absolute_thread_num() + 1; /* FOR DEBUGGING PURPOSES */ /* numVars = 2; lThresh[0] = -2; uThresh[0] = -1.636364; Infin[0] = 2; lThresh[1] = 0; uThresh[1] = 0; Infin[1] = 0; smallCor[0] = 1.0; smallCor[1] = 0; smallCor[2] = 1.0; */ F77_CALL(sadmvn)(&numVars, lThresh, uThresh, Infin, corList, &MaxPts, &absEps, &relEps, &Error, likelihood, inform, &fortranThreadId); if(OMX_DEBUG && !oo->matrix->currentState->currentRow) { char infinCodes[3][20]; strcpy(infinCodes[0], "(-INF, upper]"); strcpy(infinCodes[1], "[lower, INF)"); strcpy(infinCodes[2], "[lower, upper]"); mxLog("Input to sadmvn is (%d rows):", numVars); //:::DEBUG::: omxPrint(ordCov, "Ordinal Covariance Matrix"); //:::DEBUG::: for(int i = 0; i < numVars; i++) { mxLog("Row %d: %f, %f, %d(%s)", i, lThresh[i], uThresh[i], Infin[i], infinCodes[Infin[i]]); } mxLog("Cor: (Lower %d x %d):", cov->rows, cov->cols); //:::DEBUG::: for(int i = 0; i < cov->rows*(cov->rows-1)/2; i++) { // mxLog("Row %d of Cor: ", i); // for(int j = 0; j < i; j++) mxLog(" %f", corList[i]); // (i*(i-1)/2) + j]); // mxLog(""); } } if(OMX_DEBUG) { mxLog("Output of sadmvn is %f, %f, %d.", Error, *likelihood, *inform); } }
void mxLogBig(const std::string &str) // thread-safe { ssize_t len = ssize_t(str.size()); if (len == 0) Rf_error("Attempt to log 0 characters with mxLogBig"); std::string fullstr; if (mxLogCurrentRow == -1) { fullstr = string_snprintf("[%d] ", omx_absolute_thread_num()); } else { fullstr = string_snprintf("[%d@%d] ", omx_absolute_thread_num(), mxLogCurrentRow); } fullstr += str; len = ssize_t(fullstr.size()); const char *outBuf = fullstr.c_str(); ssize_t wrote = mxLogWriteSynchronous(outBuf, len); if (wrote != len) Rf_error("mxLogBig only wrote %d/%d, errno %d", wrote, len, errno); }
void mxLog(const char* msg, ...) // thread-safe { const int maxLen = 240; char buf1[maxLen]; char buf2[maxLen]; va_list ap; va_start(ap, msg); vsnprintf(buf1, maxLen, msg, ap); va_end(ap); int len; if (mxLogCurrentRow == -1) { len = snprintf(buf2, maxLen, "[%d] %s\n", omx_absolute_thread_num(), buf1); } else { len = snprintf(buf2, maxLen, "[%d@%d] %s\n", omx_absolute_thread_num(), mxLogCurrentRow, buf1); } ssize_t wrote = mxLogWriteSynchronous(buf2, len); if (wrote != len) Rf_error("mxLog only wrote %d/%d, errno=%d", wrote, len, errno); }
void offDiag(int rx, int cx) { int threadId = omx_absolute_thread_num(); cnd.omxEstimateHessianOffDiagonal(rx, cx, hessWorkVector + threadId); }
void onDiag(int ii) { int threadId = omx_absolute_thread_num(); cnd.omxEstimateHessianOnDiagonal(ii, hessWorkVector + threadId); }
static void gradCov(omxFitFunction *oo, FitContext *fc) { const double Scale = Global->llScale; omxExpectation *expectation = oo->expectation; BA81FitState *state = (BA81FitState*) oo->argStruct; BA81Expect *estate = (BA81Expect*) expectation->argStruct; if (estate->verbose >= 1) mxLog("%s: cross product approximation", oo->name()); estate->grp.ba81OutcomeProb(estate->itemParam->data, FALSE); const int numThreads = Global->numThreads; const int numUnique = estate->getNumUnique(); ba81NormalQuad &quad = estate->getQuad(); const int numSpecific = quad.numSpecific; const int maxDims = quad.maxDims; const int pDims = numSpecific? maxDims-1 : maxDims; const int maxAbilities = quad.maxAbilities; Eigen::MatrixXd icovMat(pDims, pDims); if (maxAbilities) { Eigen::VectorXd mean; Eigen::MatrixXd srcMat; estate->getLatentDistribution(fc, mean, srcMat); icovMat = srcMat.topLeftCorner(pDims, pDims); Matrix tmp(icovMat.data(), pDims, pDims); int info = InvertSymmetricPosDef(tmp, 'U'); if (info) { omxRaiseErrorf("%s: latent covariance matrix is not positive definite", oo->name()); return; } icovMat.triangularView<Eigen::Lower>() = icovMat.transpose().triangularView<Eigen::Lower>(); } std::vector<int> &rowMap = estate->grp.rowMap; double *rowWeight = estate->grp.rowWeight; std::vector<bool> &rowSkip = estate->grp.rowSkip; const int totalQuadPoints = quad.totalQuadPoints; omxMatrix *itemParam = estate->itemParam; omxBuffer<double> patternLik(numUnique); const int priDerivCoef = pDims + triangleLoc1(pDims); const int numLatents = maxAbilities + triangleLoc1(maxAbilities); const int thrDerivSize = itemParam->cols * state->itemDerivPadSize; const int totalOutcomes = estate->totalOutcomes(); const int numItems = state->freeItemParams? estate->numItems() : 0; const size_t numParam = fc->varGroup->vars.size(); std::vector<double> thrGrad(numThreads * numParam); std::vector<double> thrMeat(numThreads * numParam * numParam); const double *wherePrep = quad.wherePrep.data(); if (numSpecific == 0) { omxBuffer<double> thrLxk(totalQuadPoints * numThreads); omxBuffer<double> derivCoef(totalQuadPoints * priDerivCoef); if (state->freeLatents) { #pragma omp parallel for num_threads(numThreads) for (int qx=0; qx < totalQuadPoints; qx++) { const double *where = wherePrep + qx * maxDims; calcDerivCoef(fc, state, estate, icovMat.data(), where, derivCoef.data() + qx * priDerivCoef); } } #pragma omp parallel for num_threads(numThreads) for (int px=0; px < numUnique; px++) { if (rowSkip[px]) continue; int thrId = omx_absolute_thread_num(); double *lxk = thrLxk.data() + thrId * totalQuadPoints; omxBuffer<double> expected(totalOutcomes); // can use maxOutcomes instead TODO std::vector<double> deriv0(thrDerivSize); std::vector<double> latentGrad(numLatents); std::vector<double> patGrad(numParam); double *grad = thrGrad.data() + thrId * numParam; double *meat = thrMeat.data() + thrId * numParam * numParam; estate->grp.ba81LikelihoodSlow2(px, lxk); // If patternLik is already valid, maybe could avoid this loop TODO double patternLik1 = 0; for (int qx=0; qx < totalQuadPoints; qx++) { patternLik1 += lxk[qx]; } patternLik[px] = patternLik1; // if (!validPatternLik(state, patternLik1)) complain, TODO for (int qx=0; qx < totalQuadPoints; qx++) { double tmp = lxk[qx]; mapLatentDeriv(state, estate, tmp, derivCoef.data() + qx * priDerivCoef, latentGrad.data()); for (int ix=0; ix < numItems; ++ix) { int pick = estate->grp.dataColumns[ix][rowMap[px]]; if (pick == NA_INTEGER) continue; OMXZERO(expected.data(), estate->itemOutcomes(ix)); expected[pick-1] = tmp; const double *spec = estate->itemSpec(ix); double *iparam = omxMatrixColumn(itemParam, ix); const int id = spec[RPF_ISpecID]; double *myDeriv = deriv0.data() + ix * state->itemDerivPadSize; (*Glibrpf_model[id].dLL1)(spec, iparam, wherePrep + qx * maxDims, expected.data(), myDeriv); } } gradCov_finish_1pat(1 / patternLik1, rowWeight[px], numItems, numLatents, numParam, state, estate, itemParam, deriv0, latentGrad, Scale, patGrad, grad, meat); } } else { const int totalPrimaryPoints = quad.totalPrimaryPoints; const int specificPoints = quad.quadGridSize; omxBuffer<double> thrLxk(totalQuadPoints * numSpecific * numThreads); omxBuffer<double> thrEi(totalPrimaryPoints * numThreads); omxBuffer<double> thrEis(totalPrimaryPoints * numSpecific * numThreads); const int derivPerPoint = priDerivCoef + 2 * numSpecific; omxBuffer<double> derivCoef(totalQuadPoints * derivPerPoint); if (state->freeLatents) { #pragma omp parallel for num_threads(numThreads) for (int qx=0; qx < totalQuadPoints; qx++) { const double *where = wherePrep + qx * maxDims; calcDerivCoef(fc, state, estate, icovMat.data(), where, derivCoef.data() + qx * derivPerPoint); for (int Sgroup=0; Sgroup < numSpecific; ++Sgroup) { calcDerivCoef1(fc, state, estate, where, Sgroup, derivCoef.data() + qx * derivPerPoint + priDerivCoef + 2 * Sgroup); } } } #pragma omp parallel for num_threads(numThreads) for (int px=0; px < numUnique; px++) { if (rowSkip[px]) continue; int thrId = omx_absolute_thread_num(); double *lxk = thrLxk.data() + totalQuadPoints * numSpecific * thrId; double *Ei = thrEi.data() + totalPrimaryPoints * thrId; double *Eis = thrEis.data() + totalPrimaryPoints * numSpecific * thrId; omxBuffer<double> expected(totalOutcomes); // can use maxOutcomes instead TODO std::vector<double> deriv0(thrDerivSize); std::vector<double> latentGrad(numLatents); std::vector<double> patGrad(numParam); double *grad = thrGrad.data() + thrId * numParam; double *meat = thrMeat.data() + thrId * numParam * numParam; estate->grp.cai2010EiEis(px, lxk, Eis, Ei); for (int qx=0, qloc = 0; qx < totalPrimaryPoints; qx++) { for (int sgroup=0; sgroup < numSpecific; ++sgroup) { Eis[qloc] = Ei[qx] / Eis[qloc]; ++qloc; } } for (int qloc=0, eisloc=0, qx=0; eisloc < totalPrimaryPoints * numSpecific; eisloc += numSpecific) { for (int sx=0; sx < specificPoints; sx++) { mapLatentDeriv(state, estate, Eis[eisloc] * lxk[qloc], derivCoef.data() + qx * derivPerPoint, latentGrad.data()); for (int Sgroup=0; Sgroup < numSpecific; Sgroup++) { double lxk1 = lxk[qloc]; double Eis1 = Eis[eisloc + Sgroup]; double tmp = Eis1 * lxk1; mapLatentDerivS(state, estate, Sgroup, tmp, derivCoef.data() + qx * derivPerPoint + priDerivCoef + 2 * Sgroup, latentGrad.data()); for (int ix=0; ix < numItems; ++ix) { if (estate->grp.Sgroup[ix] != Sgroup) continue; int pick = estate->grp.dataColumns[ix][rowMap[px]]; if (pick == NA_INTEGER) continue; OMXZERO(expected.data(), estate->itemOutcomes(ix)); expected[pick-1] = tmp; const double *spec = estate->itemSpec(ix); double *iparam = omxMatrixColumn(itemParam, ix); const int id = spec[RPF_ISpecID]; const int dims = spec[RPF_ISpecDims]; double *myDeriv = deriv0.data() + ix * state->itemDerivPadSize; const double *where = wherePrep + qx * maxDims; Eigen::VectorXd ptheta(dims); for (int dx=0; dx < dims; dx++) { ptheta[dx] = where[std::min(dx, maxDims-1)]; } (*Glibrpf_model[id].dLL1)(spec, iparam, ptheta.data(), expected.data(), myDeriv); } ++qloc; } ++qx; } } // If patternLik is already valid, maybe could avoid this loop TODO double patternLik1 = 0; for (int qx=0; qx < totalPrimaryPoints; ++qx) { patternLik1 += Ei[qx]; } patternLik[px] = patternLik1; gradCov_finish_1pat(1 / patternLik1, rowWeight[px], numItems, numLatents, numParam, state, estate, itemParam, deriv0, latentGrad, Scale, patGrad, grad, meat); } } for (int tx=1; tx < numThreads; ++tx) { double *th = thrGrad.data() + tx * numParam; for (size_t en=0; en < numParam; ++en) { thrGrad[en] += th[en]; } } for (int tx=1; tx < numThreads; ++tx) { double *th = thrMeat.data() + tx * numParam * numParam; for (size_t en=0; en < numParam * numParam; ++en) { thrMeat[en] += th[en]; } } for (size_t d1=0; d1 < numParam; ++d1) { fc->grad(d1) += thrGrad[d1]; } if (fc->infoB) { for (size_t d1=0; d1 < numParam; ++d1) { for (size_t d2=0; d2 < numParam; ++d2) { int cell = d1 * numParam + d2; fc->infoB[cell] += thrMeat[cell]; } } } }
static void sandwich(omxFitFunction *oo, FitContext *fc) { const double abScale = fabs(Global->llScale); omxExpectation *expectation = oo->expectation; BA81FitState *state = (BA81FitState*) oo->argStruct; BA81Expect *estate = (BA81Expect*) expectation->argStruct; if (estate->verbose >= 1) mxLog("%s: sandwich", oo->name()); estate->grp.ba81OutcomeProb(estate->itemParam->data, FALSE); const int numThreads = Global->numThreads; const int numUnique = estate->getNumUnique(); ba81NormalQuad &quad = estate->getQuad(); const int numSpecific = quad.numSpecific; const int maxDims = quad.maxDims; std::vector<int> &rowMap = estate->grp.rowMap; double *rowWeight = estate->grp.rowWeight; std::vector<bool> &rowSkip = estate->grp.rowSkip; const int totalQuadPoints = quad.totalQuadPoints; omxMatrix *itemParam = estate->itemParam; omxBuffer<double> patternLik(numUnique); std::vector<const double*> &itemSpec = estate->grp.spec; const int totalOutcomes = estate->totalOutcomes(); const int numItems = estate->grp.numItems(); const size_t numParam = fc->varGroup->vars.size(); const double *wherePrep = quad.wherePrep.data(); std::vector<double> thrBreadG(numThreads * numParam * numParam); std::vector<double> thrBreadH(numThreads * numParam * numParam); std::vector<double> thrMeat(numThreads * numParam * numParam); if (numSpecific == 0) { omxBuffer<double> thrLxk(totalQuadPoints * numThreads); #pragma omp parallel for num_threads(numThreads) for (int px=0; px < numUnique; px++) { if (rowSkip[px]) continue; int thrId = omx_absolute_thread_num(); double *lxk = thrLxk.data() + thrId * totalQuadPoints; omxBuffer<double> itemDeriv(state->itemDerivPadSize); omxBuffer<double> expected(totalOutcomes); // can use maxOutcomes instead TODO double *breadG = thrBreadG.data() + thrId * numParam * numParam; //a double *breadH = thrBreadH.data() + thrId * numParam * numParam; //a double *meat = thrMeat.data() + thrId * numParam * numParam; //b std::vector<double> patGrad(numParam); estate->grp.ba81LikelihoodSlow2(px, lxk); // If patternLik is already valid, maybe could avoid this loop TODO double patternLik1 = 0; for (int qx=0; qx < totalQuadPoints; qx++) { patternLik1 += lxk[qx]; } patternLik[px] = patternLik1; // if (!validPatternLik(state, patternLik1)) complain double weight = 1 / patternLik[px]; for (int qx=0; qx < totalQuadPoints; qx++) { double tmp = lxk[qx] * weight; double sqrtTmp = sqrt(tmp); std::vector<double> gradBuf(numParam); int gradOffset = 0; for (int ix=0; ix < numItems; ++ix) { if (ix) gradOffset += state->paramPerItem[ix-1]; int pick = estate->grp.dataColumns[ix][rowMap[px]]; if (pick == NA_INTEGER) continue; pick -= 1; const int iOutcomes = estate->itemOutcomes(ix); OMXZERO(expected.data(), iOutcomes); expected[pick] = 1; const double *spec = itemSpec[ix]; double *iparam = omxMatrixColumn(itemParam, ix); const int id = spec[RPF_ISpecID]; OMXZERO(itemDeriv.data(), state->itemDerivPadSize); (*Glibrpf_model[id].dLL1)(spec, iparam, wherePrep + qx * maxDims, expected.data(), itemDeriv.data()); (*Glibrpf_model[id].dLL2)(spec, iparam, itemDeriv.data()); for (int par = 0; par < state->paramPerItem[ix]; ++par) { int to = state->itemGradMap[gradOffset + par]; if (to >= 0) { gradBuf[to] -= itemDeriv[par] * sqrtTmp; patGrad[to] -= itemDeriv[par] * tmp; } } int derivBase = ix * state->itemDerivPadSize; for (int ox=0; ox < state->itemDerivPadSize; ox++) { int to = state->paramMap[derivBase + ox]; if (to >= int(numParam)) { int Hto = to - numParam; breadH[Hto] += abScale * itemDeriv[ox] * tmp * rowWeight[px]; } } } addSymOuterProd(abScale * rowWeight[px], gradBuf.data(), numParam, breadG); } addSymOuterProd(abScale * rowWeight[px], patGrad.data(), numParam, meat); } } else { Rf_error("Sandwich information matrix method is not implemented for bifactor models"); const int totalPrimaryPoints = quad.totalPrimaryPoints; const int specificPoints = quad.quadGridSize; omxBuffer<double> thrLxk(totalQuadPoints * numSpecific * numThreads); omxBuffer<double> thrEi(totalPrimaryPoints * numThreads); omxBuffer<double> thrEis(totalPrimaryPoints * numSpecific * numThreads); #pragma omp parallel for num_threads(numThreads) for (int px=0; px < numUnique; px++) { if (rowSkip[px]) continue; int thrId = omx_absolute_thread_num(); omxBuffer<double> expected(totalOutcomes); // can use maxOutcomes instead TODO omxBuffer<double> itemDeriv(state->itemDerivPadSize); double *breadG = thrBreadG.data() + thrId * numParam * numParam; //a double *breadH = thrBreadH.data() + thrId * numParam * numParam; //a double *meat = thrMeat.data() + thrId * numParam * numParam; //b std::vector<double> patGrad(numParam); double *lxk = thrLxk.data() + totalQuadPoints * numSpecific * thrId; double *Ei = thrEi.data() + totalPrimaryPoints * thrId; double *Eis = thrEis.data() + totalPrimaryPoints * numSpecific * thrId; estate->grp.cai2010EiEis(px, lxk, Eis, Ei); // If patternLik is already valid, maybe could avoid this loop TODO double patternLik1 = 0; for (int qx=0; qx < totalPrimaryPoints; ++qx) { patternLik1 += Ei[qx]; } patternLik[px] = patternLik1; for (int qx=0, qloc = 0; qx < totalPrimaryPoints; qx++) { for (int sgroup=0; sgroup < numSpecific; ++sgroup) { Eis[qloc] = Ei[qx] / Eis[qloc]; ++qloc; } } // WARNING: I didn't work out the math. I just coded this the way // it seems to make sense. for (int qloc=0, eisloc=0, qx=0; eisloc < totalPrimaryPoints * numSpecific; eisloc += numSpecific) { for (int sx=0; sx < specificPoints; sx++) { for (int Sgroup=0; Sgroup < numSpecific; Sgroup++) { std::vector<double> gradBuf(numParam); int gradOffset = 0; double lxk1 = lxk[qloc + Sgroup]; double Eis1 = Eis[eisloc + Sgroup]; double tmp = Eis1 * lxk1 / patternLik1; double sqrtTmp = sqrt(tmp); for (int ix=0; ix < numItems; ++ix) { if (ix) gradOffset += state->paramPerItem[ix-1]; if (estate->grp.Sgroup[ix] != Sgroup) continue; int pick = estate->grp.dataColumns[ix][rowMap[px]]; if (pick == NA_INTEGER) continue; OMXZERO(expected.data(), estate->itemOutcomes(ix)); expected[pick-1] = 1; const double *spec = itemSpec[ix]; double *iparam = omxMatrixColumn(itemParam, ix); const int id = spec[RPF_ISpecID]; const int dims = spec[RPF_ISpecDims]; OMXZERO(itemDeriv.data(), state->itemDerivPadSize); const double *where = wherePrep + qx * maxDims; Eigen::VectorXd ptheta(dims); for (int dx=0; dx < dims; dx++) { ptheta[dx] = where[std::min(dx, maxDims-1)]; } (*Glibrpf_model[id].dLL1)(spec, iparam, ptheta.data(), expected.data(), itemDeriv.data()); (*Glibrpf_model[id].dLL2)(spec, iparam, itemDeriv.data()); for (int par = 0; par < state->paramPerItem[ix]; ++par) { int to = state->itemGradMap[gradOffset + par]; if (to >= 0) { gradBuf[to] -= itemDeriv[par] * sqrtTmp; patGrad[to] -= itemDeriv[par] * tmp; } } int derivBase = ix * state->itemDerivPadSize; for (int ox=0; ox < state->itemDerivPadSize; ox++) { int to = state->paramMap[derivBase + ox]; if (to >= int(numParam)) { int Hto = to - numParam; breadH[Hto] += (abScale * itemDeriv[ox] * tmp * rowWeight[px]); } } } addSymOuterProd(abScale * rowWeight[px], gradBuf.data(), numParam, breadG); } qloc += numSpecific; ++qx; } } addSymOuterProd(abScale * rowWeight[px], patGrad.data(), numParam, meat); } } // only need upper triangle TODO for (int tx=1; tx < numThreads; ++tx) { double *th = thrBreadG.data() + tx * numParam * numParam; for (size_t en=0; en < numParam * numParam; ++en) { thrBreadG[en] += th[en]; } } for (int tx=1; tx < numThreads; ++tx) { double *th = thrBreadH.data() + tx * numParam * numParam; for (size_t en=0; en < numParam * numParam; ++en) { thrBreadH[en] += th[en]; } } for (int tx=1; tx < numThreads; ++tx) { double *th = thrMeat.data() + tx * numParam * numParam; for (size_t en=0; en < numParam * numParam; ++en) { thrMeat[en] += th[en]; } } //pda(thrBreadG.data(), numParam, numParam); //pda(thrBreadH.data(), numParam, numParam); //pda(thrMeat.data(), numParam, numParam); if (fc->infoA) { for (size_t d1=0; d1 < numParam; ++d1) { for (size_t d2=0; d2 < numParam; ++d2) { int cell = d1 * numParam + d2; fc->infoA[cell] += thrBreadH[cell] - thrBreadG[cell] + thrMeat[cell]; } } } if (fc->infoB) { for (size_t d1=0; d1 < numParam; ++d1) { for (size_t d2=0; d2 < numParam; ++d2) { int cell = d1 * numParam + d2; fc->infoB[cell] += thrMeat[cell]; } } } }
static double ba81ComputeEMFit(omxFitFunction* oo, int want, FitContext *fc) { const double Scale = Global->llScale; BA81FitState *state = (BA81FitState*) oo->argStruct; BA81Expect *estate = (BA81Expect*) oo->expectation->argStruct; omxMatrix *itemParam = estate->itemParam; std::vector<const double*> &itemSpec = estate->grp.spec; std::vector<int> &cumItemOutcomes = estate->grp.cumItemOutcomes; ba81NormalQuad &quad = estate->getQuad(); const int maxDims = quad.maxDims; const size_t numItems = itemSpec.size(); const int do_fit = want & FF_COMPUTE_FIT; const int do_deriv = want & (FF_COMPUTE_GRADIENT | FF_COMPUTE_HESSIAN | FF_COMPUTE_IHESSIAN); if (do_deriv && !state->freeItemParams) { omxRaiseErrorf("%s: no free parameters", oo->name()); return NA_REAL; } if (state->returnRowLikelihoods) { omxRaiseErrorf("%s: vector=TRUE not implemented", oo->name()); return NA_REAL; } if (estate->verbose >= 3) mxLog("%s: complete data fit(want fit=%d deriv=%d)", oo->name(), do_fit, do_deriv); if (do_fit) estate->grp.ba81OutcomeProb(itemParam->data, TRUE); const int thrDerivSize = itemParam->cols * state->itemDerivPadSize; std::vector<double> thrDeriv(thrDerivSize * Global->numThreads); double *wherePrep = quad.wherePrep.data(); double ll = 0; #pragma omp parallel for num_threads(Global->numThreads) reduction(+:ll) for (size_t ix=0; ix < numItems; ix++) { const int thrId = omx_absolute_thread_num(); const double *spec = itemSpec[ix]; const int id = spec[RPF_ISpecID]; const int dims = spec[RPF_ISpecDims]; Eigen::VectorXd ptheta(dims); const rpf_dLL1_t dLL1 = Glibrpf_model[id].dLL1; const int iOutcomes = estate->grp.itemOutcomes[ix]; const int outcomeBase = cumItemOutcomes[ix] * quad.totalQuadPoints; const double *weight = estate->expected + outcomeBase; const double *oProb = estate->grp.outcomeProb + outcomeBase; const double *iparam = omxMatrixColumn(itemParam, ix); double *myDeriv = thrDeriv.data() + thrDerivSize * thrId + ix * state->itemDerivPadSize; for (int qx=0; qx < quad.totalQuadPoints; qx++) { if (do_fit) { for (int ox=0; ox < iOutcomes; ox++) { ll += weight[ox] * oProb[ox]; } } if (do_deriv) { double *where = wherePrep + qx * maxDims; for (int dx=0; dx < dims; dx++) { ptheta[dx] = where[std::min(dx, maxDims-1)]; } (*dLL1)(spec, iparam, ptheta.data(), weight, myDeriv); } weight += iOutcomes; oProb += iOutcomes; } } size_t excluded = 0; if (do_deriv) { double *deriv0 = thrDeriv.data(); int perThread = itemParam->cols * state->itemDerivPadSize; for (int th=1; th < Global->numThreads; th++) { double *thrD = thrDeriv.data() + th * perThread; for (int ox=0; ox < perThread; ox++) deriv0[ox] += thrD[ox]; } int numFreeParams = int(state->numFreeParam); int ox=-1; for (size_t ix=0; ix < numItems; ix++) { const double *spec = itemSpec[ix]; int id = spec[RPF_ISpecID]; double *iparam = omxMatrixColumn(itemParam, ix); double *pad = deriv0 + ix * state->itemDerivPadSize; (*Glibrpf_model[id].dLL2)(spec, iparam, pad); HessianBlock *hb = state->hBlocks[ix].clone(); hb->mat.triangularView<Eigen::Upper>().setZero(); for (int dx=0; dx < state->itemDerivPadSize; ++dx) { int to = state->paramMap[++ox]; if (to == -1) continue; // Need to check because this can happen if // lbounds/ubounds are not set appropriately. if (0 && !std::isfinite(deriv0[ox])) { int item = ox / itemParam->rows; mxLog("item parameters:\n"); const double *spec = itemSpec[item]; int id = spec[RPF_ISpecID]; int numParam = (*Glibrpf_model[id].numParam)(spec); double *iparam = omxMatrixColumn(itemParam, item); pda(iparam, numParam, 1); // Perhaps bounds can be pulled in from librpf? TODO Rf_error("Deriv %d for item %d is %f; are you missing a lbound/ubound?", ox, item, deriv0[ox]); } if (to < numFreeParams) { if (want & FF_COMPUTE_GRADIENT) { fc->grad(to) -= Scale * deriv0[ox]; } } else { if (want & (FF_COMPUTE_HESSIAN | FF_COMPUTE_IHESSIAN)) { int Hto = state->hbMap[ox]; if (Hto >= 0) hb->mat.data()[Hto] -= Scale * deriv0[ox]; } } } fc->queue(hb); } } if (excluded && estate->verbose >= 1) { mxLog("%s: Hessian not positive definite for %d/%d items", oo->name(), (int) excluded, (int) numItems); } if (excluded == numItems) { omxRaiseErrorf("Hessian not positive definite for %d/%d items", (int) excluded, (int) numItems); } return Scale * ll; }