Manticore  Version 1.5.3
Physics of Molecular Clouds
solve.cc
Go to the documentation of this file.
1 
16 #include <algorithm>
17 #include <ctime>
18 #include <thread>
19 
20 // Random deviates and statistics.
21 #include <gsl/gsl_randist.h>
22 #include <gsl/gsl_rng.h>
23 #include <gsl/gsl_statistics_double.h>
24 
25 // Non-linear solver.
26 #include <ceres/ceres.h>
27 #include <glog/logging.h>
28 
29 #include "Graybody.h"
30 #include "PACS.h"
31 #include "SPIRE.h"
32 
33 namespace mu = mutils;
34 
35 
37 namespace manticore {
38 
39 const double mu_g = (2.75*m_H),
40  imu_g = 1.0/mu_g;
41 
43 struct GrayData {
45  GrayData(Graybody *g, std::vector<mapDataType> *o, size_t numBands) :
46  gray(g), outMap(o), nBands(numBands)
47  {
50  band.resize(nBands);
51  }
52 
54 
56 
58  iFerr,
59  dnu0,
60  fixed;
61 
63  band;
64 
66  *outMap;
67 
68  unsigned nBands;
69 };
70 
71 
93 template <unsigned NB, unsigned NP>
94 class Fcost_gray : public ceres::SizedCostFunction<NB,NP> {
95 public:
97  static constexpr double scale = 128.0;
98 
100  Fcost_gray(const GrayData &gd) : gd_(gd) { clearCache(); }
101 
103  virtual bool Evaluate(double const* const* parameters, double* residuals,
104  double** jacobians) const final {
105  const char *const fn = "Fcost_gray";
106  const double *p = parameters[0];
107  double *jac = (jacobians && jacobians[0] ? jacobians[0] : nullptr);
108 
109  // Clear cache.
110  if (!residuals) { clearCache(); return true; }
111 
112  // Check cache.
113  if (memcmp(p, pcache_, sizeof(pcache_)) == 0) {
114  MU_DEBUG(fn, "Using cache...");
115  memcpy(residuals, rcache_, sizeof(rcache_));
116  if (jac) { memcpy(jac, jcache_, sizeof(jcache_)); }
117  return true;
118  }
119 
120  constexpr unsigned P0 = NP&1; // Parameter index offset.
121  const double
122  rescale = 1.0/scale,
123  Th = (P0==0 ? p[0] : gd_.fixed[0]),
124  Sh = p[1-P0] * rescale,
125  Tc = (NP > 2 ? p[2-P0] : 0.0),
126  Sc = (NP > 2 ? p[3-P0] : 0.0) * rescale;
127 
128  MU_DEBUG(fn, "Th = %7.6f, Sh = %9.8f, Tc = %7.6f, Sc = %9.8f%s",
129  Th, Sh, Tc, Sc, jac ? " *" : "");
130 
131  const auto &Fobs = gd_.Fobs, &iFerr = gd_.iFerr;
132  const auto &gray = *gd_.gray;
133  const auto band = gd_.band;
134  for (unsigned i=0; i != NB; i++) {
135  auto Di = band[i];
136  double w = iFerr[i], F = (NP < 3 ? gray.F(Th, Sh, Di) :
137  gray.F(Tc, Sc, Th, Sh, Di));
138  residuals[i] = w*(F - Fobs[i]);
139 
140  if (jac) {
141  if (NP <= 2) {
142  if (NP == 2) {
143  jac[NP*i+0] = w*gray.dF_dT(Th, Sh, Di);
144  }
145  jac[NP*i+1-P0] = w*gray.dF_dS(Th, Sh, Di)*rescale;
146  } else {
147  if (NP == 4) {
148  jac[NP*i+0] = w*gray.dF_dTh(Tc, Sc, Th, Sh, Di);
149  }
150  jac[NP*i+1-P0] = w*gray.dF_dSh(Tc, Sc, Th, Sh, Di)*rescale;
151  jac[NP*i+2-P0] = w*gray.dF_dTc(Tc, Sc, Th, Sh, Di);
152  jac[NP*i+3-P0] = w*gray.dF_dSc(Tc, Sc, Th, Sh, Di)*rescale;
153  }
154 
155  // Cache results.
156  memcpy(pcache_, p, sizeof(pcache_));
157  memcpy(rcache_, residuals, sizeof(rcache_));
158  memcpy(jcache_, jac, sizeof(jcache_));
159  }
160  }
161 
162  return true;
163  }
164 
165 private:
166  // Observation fitting data.
167  const GrayData &gd_;
168 
169  // Latest results are cached as Ceres recomputes values needlessly.
170  // For cache coherency, this is done only when the Jacobian is requested.
171  mutable double
172  pcache_[NP],
173  rcache_[NB],
174  jcache_[NB*NP];
175 
177  void clearCache() const { for (auto &p: pcache_) { p = -1.0; } }
178 };
179 
180 
183 {
184  char buf[64];
185  struct tm tmbuf;
186  auto t = std::time(nullptr);
187  std::string rtn = asctime_r(localtime_r(&t, &tmbuf), buf);
188  rtn.pop_back(); // Remove newline.
189  return rtn;
190 }
191 
192 
209  std::vector<mapDataType> &bandError,
210  const GrayData &gdata, unsigned nParam)
211 {
212  const auto &gray = *gdata.gray;
213  const int n = sqrt(1.0*modelMapLen);
214  const double in1 = 1.0/(n-1);
215 
216  for (unsigned iMap=0; iMap != modelMapLen*modelMapLen; iMap++) {
217  auto i = iMap/modelMapLen, j = iMap%modelMapLen;
218  double
219  Tc = 6.0*(1.0 + (j%n)*in1), // Tc ~ [6, 12] K
220  Sc = mu_g*3e21*pow(100.0, (j/n)*in1), // Nc(H2) ~ [3e21, 3e23] cm^-2
221  Th = 15.0 + 3.0*(i%n)*in1, // Th ~ [15, 18] K
222  Sh = mu_g*3e19*pow(100.0, (i/n)*in1); // Nc(H2) ~ [3e19, 3e21] cm^-2
223  MU_DEBUG( "makeModelMap",
224  "(%lu, %lu) Tc = %.2f, Sc = %.2e, Th = %.2f, Sh = %.2e",
225  i, j, Tc, Sc, Th, Sh);
226 
227  for (unsigned iBand=0; iBand != bandData.size(); iBand++) {
228  auto &data = bandData[iBand], &err = bandError[iBand];
229  auto Di = gdata.band[iBand];
230  data[iMap] = (nParam == 2 ? gray.F(Th, Sh, Di) :
231  gray.F(Tc, Sc, Th, Sh, Di)) /
232  gdata.dnu0[iBand];
233  err[iMap] = 0.001*data[iMap];
234  MU_DEBUG("makeModelMap", " [%lu] %.4e", iBand, data[iMap]);
235  }
236  }
237 }
238 
239 
255 double gridSample(double data, double err, unsigned iBand, unsigned iCycle,
256  unsigned nSamp)
257 {
258  if (nSamp == 2) { return data + ((iCycle>>iBand)&1 ? -err : +err); }
259  else {
260  unsigned div = 1U;
261  for (unsigned i=iBand; i != 0; i--) { div *= nSamp; }
262 
263  int digit = (iCycle/div) % nSamp;
264  double half = 0.5*(nSamp-1);
265  return data + (digit/half-1.0)*err;
266  }
267 }
268 
269 
286 double sysSample(double data, unsigned iBand, unsigned iCycle,
287  const std::vector<std::string> &instr,
288  const std::vector<const Detector *> &bands)
289 {
290  // Which instrument?
291  unsigned iInstr = 0;
292  for (unsigned i=0; i != instr.size(); i++) {
293  if (! bands[iBand]->name().compare(0, instr[i].size(), instr[i])) {
294  iInstr = i;
295  break;
296  }
297  }
298 
299  // Correlated/uncorrelated error offsets this cycle.
300  // Skip the cycle if either is zero and this iteration is a repeat.
301  unsigned cBit = (iCycle>>(iInstr+bands.size()))&1, uBit = (iCycle>>iBand)&1;
302  auto sErr = bands[iBand]->sysError();
303  double cErr = (cBit ? -sErr.first : +sErr.first),
304  uErr = (uBit ? -sErr.second : +sErr.second);
305  MU_DEBUG("sysSample", "%lu[%lu] %lu/%g %lu/%g",
306  iCycle, iBand, cBit, cErr, uBit, uErr);
307  return ((cBit && !cErr) || (uBit && !uErr) ? NAN : data*(1.0+cErr+uErr));
308 }
309 
310 
323 void calcStats(double stats[], const double x0[], unsigned n)
324 {
325  double x[n];
326  memcpy(x, x0, sizeof(x));
327  std::sort(x, x+n);
328  double med = gsl_stats_median_from_sorted_data(x, 1, n),
329  qdev = gsl_stats_quantile_from_sorted_data(x, 1, n, 0.8413) -
330  gsl_stats_quantile_from_sorted_data(x, 1, n, 0.1587),
331  ave = gsl_stats_mean(x, 1, n),
332  sdev = sqrt(gsl_stats_variance_m(x, 1, n, ave)),
333  min = x[0], max = x[n-1];
334 
335  stats[0] = med, stats[1] = 0.5*qdev;
336  stats[2] = ave, stats[3] = sdev;
337  stats[4] = min, stats[5] = max;
338 }
339 
354 unsigned findMedianModel(const double pstats[][6], unsigned nParam,
355  const double *pdata, unsigned nModels, unsigned nCols)
356 {
357  unsigned P0 = nParam&1, imin = 0;
358  double min = 1e300;
359  for (unsigned i=0; i != nModels; i++) {
360  double merit = 0.0;
361  for (unsigned j=P0; j != P0+nParam; j++) {
362  merit += fabs(pdata[nCols*j+i] - pstats[j][0]) / pstats[j][1];
363  }
364  merit += 0.1*fabs(pdata[nCols*4+i]-pdata[nCols*4]); // chi2_dof
365  if (merit < min) { min = merit, imin = i; }
366  }
367  return imin;
368 }
369 
387 int trialFit(ceres::Problem &prob, ceres::Solver::Summary &sum,
388  ceres::Solver::Options &opts, const ceres::CostFunction *cost,
389  double p[], unsigned NP, const double p0[] = nullptr,
390  const ceres::Solver::Summary *sum0 = nullptr)
391 {
392  // Invalidate cost cache (manticore-specific; cf. Fcost_gray).
393  const double *const pp = p;
394  (void )cost->Evaluate(&pp, nullptr, nullptr);
395 
396  // Calculate fit, restore previous one if superior.
397  ceres::Solve(opts, &prob, &sum);
398  auto nIter = sum.num_successful_steps + sum.num_unsuccessful_steps - 1;
399  //
400  if (p0 && sum0 && (!sum.IsSolutionUsable() ||
401  (sum0->IsSolutionUsable() && sum0->final_cost < sum.final_cost))) {
402  memcpy(p, p0, NP*sizeof(double));
403  sum = *sum0;
404  }
405  return nIter;
406 }
407 
433 int robustFit(ceres::Problem &prob, ceres::Solver::Summary &sum,
434  ceres::Solver::Options &opts, const ceres::CostFunction *cost,
435  double p[], const double dp[],
436  const double pmin[], const double pmax[],
437  unsigned NP, double minCost = 0.25, double maxCost = 25.0,
438  bool refit = false)
439 {
440  // Trial solution.
441  int nIter = trialFit(prob, sum, opts, cost, p, NP);
442 
443  // Check solution.
444  auto needRefit = [minCost,maxCost,refit](const ceres::Solver::Summary &s) {
445  return (!s.IsSolutionUsable() ||
446  s.termination_type == ceres::NO_CONVERGENCE ||
447  s.final_cost > maxCost || (s.final_cost > minCost && refit));
448  };
449  if (needRefit(sum)) {
450  // Perturb initial conditions.
451  auto perturb = [NP](double p[], const double dp[],
452  const double pmin[], const double pmax[]) noexcept {
453  for (unsigned i=0; i != NP; i++) {
454  double del = 0.1*fabs(dp[i]);
455  p[i] = std::max(pmin[i]+del, std::min(p[i]+dp[i], pmax[i]-del));
456  }
457  };
458 
459  // Save trial solution.
460  ceres::Solver::Summary sum0{sum};
461  double p0[NP];
462  memcpy(p0, p, sizeof(p0));
463 
464  // Use simple 10% relative error estimate if none provided.
465  // Alternate signs for balance.
466  double dp0[NP];
467  if (dp) { memcpy(dp0, dp, sizeof(dp0)); }
468  else { for (unsigned i=0; i != NP; i++) { dp0[i] = 0.10*p[i]; } }
469  //
470  for (unsigned i=1; i < NP; i+=2) { dp0[i] = -dp0[i]; }
471 
472  // Perturb parameters and try again.
473  perturb(p, dp0, pmin, pmax);
474  nIter += trialFit(prob, sum, opts, cost, p, NP, p0, &sum0);
475  if (needRefit(sum)) {
476  // Reverse all signs and try one last time.
477  for (unsigned i=0; i != NP; i++) { dp0[i] = -dp0[i]; }
478  memcpy(p0, p, sizeof(p0)), sum0 = sum;
479  perturb(p, dp0, pmin, pmax);
480  nIter += trialFit(prob, sum, opts, cost, p, NP, p0, &sum0);
481  }
482  }
483 
484  return nIter;
485 }
486 
498 {
500  for (auto &b: band) {
501  bool found = false;
502  auto iname = b->name();
503  iname = iname.substr(0, iname.find('-')+1);
504 
505  for (auto &i: instr) { if (! i.compare(iname)) { found = true; break; } }
506  if (! found) { instr.push_back(iname); }
507  }
508  return instr;
509 }
510 
539 template<unsigned NP>
541  const std::vector<mapDataType> &bandError,
542  const mapDataType &tempMap,
543  const mapDataType &chi2Map,
544  const mu::CommandLine &cli,
545  const GrayData &gdata0, unsigned id, unsigned nThreads)
546 {
547  constexpr unsigned P0 = NP&1, // Index of first active parameter.
548  M0 = (NP > 2 ? 4 : 0); // Cold component map index offset.
549  const unsigned NB = gdata0.nBands;
550 
551  const char *const fn = "findParameters";
552  size_t minBands = cli.getInteger('n', NP==4 ? NP : 3);
553 
554  auto &Thalo = gdata0.outMap->at(0),
555  &dThalo = gdata0.outMap->at(1),
556  &Nhalo = gdata0.outMap->at(2),
557  &dNhalo = gdata0.outMap->at(3),
558  &Tcore = gdata0.outMap->at(0+M0), // Unused for NP <= 2.
559  &dTcore = gdata0.outMap->at(1+M0), // Unused for NP <= 2.
560  &Ncore = gdata0.outMap->at(2+M0), // Unused for NP <= 2.
561  &dNcore = gdata0.outMap->at(3+M0), // Unused for NP <= 2.
562  &Cmap = gdata0.outMap->at(4+M0),
563  &dCmap = gdata0.outMap->at(5+M0),
564  &Imap = gdata0.outMap->at(6+M0);
565 
566  auto *diffMap = &gdata0.outMap->at(7+M0),
567  *fracMap = &gdata0.outMap->at(7+M0+(NP>2 ? NB : 0)); // Unused NP <= 2.
568  const bool absdiff = cli.getOption('a');
569  const double relerr = cli.getDouble('A', 1e-3),
570  minCost = 0.125*NB, maxCost = 4.0*NB;
571 
572  // Unique instruments (ignoring band).
573  std::vector<string> instr = setInstruments(gdata0.band);
574 
575  // Data resampling option.
576  const bool sysSamp = cli.getOption('S');
577  const unsigned sysBits = (sysSamp ? instr.size()+NB : 0);
578  const unsigned gridSamp = std::min(cli.getOption('G'), 2U),
579  randSamp = std::max(0L, cli.getInteger('R', 0)),
580  nSamp = 1+(gridSamp ? (gridSamp == 1 ? 16 : 81) :
581  sysSamp ? std::max(1U, randSamp) : randSamp) *
582  (sysSamp ? 1U<<sysBits : 1);
583  gsl_rng *rng = gsl_rng_alloc(gsl_rng_mt19937);
584  gsl_rng_set(rng, 12345678901234567890UL + id);
585 
586  // Single-temperature chi^2 bypass mask.
587  // Pixels whose chi2Map[i] < chi2Max are skipped (NaN-filled) here.
588  double chi2Max = (chi2Map.size() ? cli.getDouble('c', 1.0) : -1.0);
589  if (chi2Map.size() && chi2Map.size() != bandData[0].size()) {
590  MU_ERROR(fn, "Ignoring --single-temp map (size %lu, require %lu)",
591  chi2Map.size(), bandData[0].size());
592  chi2Max = -1.0;
593  }
594 
595  // Thread-local storage.
596  Graybody gray{*gdata0.gray};
597  GrayData gdata{gdata0};
598  gdata.gray = &gray;
599 
600  // Model flux.
601  auto calcF = [NB,&gdata,&gray](double F[], double Th, double Sh,
602  double Tc, double Sc) {
603  for (unsigned j=0; j != NB; j++) {
604  auto Dj = gdata.band[j];
605  F[j] = (NP < 3 ? gray.F(Th, Sh, Dj) : gray.F(Tc, Sc, Th, Sh, Dj));
606  }
607  };
608 
609  // Model data resampling. Sigma limited to reduce fitter stress.
610  // Note this biases the randF variance estimate slightly lower.
611  auto sample = [rng](double mean, double sigma) {
612  double dev;
613  do { dev = gsl_ran_gaussian(rng, 1.0); } while (fabs(dev) > 2.5);
614  return mean + dev*sigma;
615  };
616  auto randF = [NB,&gdata,&sample](const double F[]) {
617  for (unsigned j=0; j != NB; j++) {
618  if (double iFerr = gdata.iFerr[j]) {
619  gdata.Fobs[j] = sample(F[j], 1.0/iFerr);
620  }
621  }
622  };
623  auto gridF = [NB,gridSamp,&gdata](const double F[], unsigned iSamp) {
624  for (unsigned j=0; j != NB; j++) {
625  if (double iFerr = gdata.iFerr[j]) {
626  gdata.Fobs[j] = gridSample(F[j], 1.0/iFerr, j, iSamp, 1+gridSamp);
627  }
628  }
629  };
630  auto sysF = [NB,&instr, &gdata](const double F[], unsigned iSamp) {
631  for (unsigned j=0; j != NB; j++) {
632  gdata.Fobs[j] = sysSample(F[j], j, iSamp, instr, gdata.band);
633  }
634  };
635 
636  // Initial guess.
637  const double T0 = cli.getDouble('T', 15.0), S0 = 0.05,
638  T1 = (NP > 2 ? 10.0 : 0.0), S1 = (NP > 2 ? 0.5 : 0.0),
639  iDOF = 1.0/std::max(NB-NP, 1U);
640  size_t maxIter = std::max(1L, cli.getInteger('m', 50));
641  gdata.fixed.push_back(T0); // Used when Th held fixed.
642 
643  // Ceres setup.
644  constexpr double Sscale = Fcost_gray<1,1>::scale, Srescale = (1.0/Sscale);
645  const double Nmin = cli.getDouble('N', 0.0) * mu_g;
646  double cov[4*4],
647  p[] = {0.0, 0.0, 0.0, 0.0},
648  p0[] = {T0, S0*Sscale, T1, S1*Sscale},
649  pmin[] = { 5.0, Nmin*Sscale, 5.0, 0.0*Sscale},
650  pmax[] = {50.0, 50.0*Sscale, 15.0, 50.0*Sscale};
651  //
652  // Bound Tc <= Th if the latter is prescribed.
653  if (P0) { pmax[2] = T0; }
654  if (NP == 4) { pmin[0] = pmax[2] = std::min(14.0, T0-1.0); }
655 
656  ceres::Problem prob;
657  ceres::Solver::Summary sum;
658  ceres::CostFunction *cost;
659  switch (NB) {
660  case 1U: cost = new Fcost_gray<1,NP>(gdata); break;
661  case 2U: cost = new Fcost_gray<2,NP>(gdata); break;
662  case 3U: cost = new Fcost_gray<3,NP>(gdata); break;
663  default: cost = new Fcost_gray<4,NP>(gdata); break;
664  }
665  prob.AddResidualBlock(cost, nullptr, p+P0);
666  for (unsigned i=0; i != NP; i++) {
667  prob.SetParameterLowerBound(p+P0, i, pmin[i+P0]);
668  prob.SetParameterUpperBound(p+P0, i, pmax[i+P0]);
669  }
670 
671  ceres::Solver::Options opts;
672  opts.trust_region_strategy_type = ceres::LEVENBERG_MARQUARDT;
673  opts.linear_solver_type = ceres::DENSE_QR;
674  opts.dense_linear_algebra_library_type = ceres::EIGEN;
675  opts.initial_trust_region_radius = 1e4;
676  opts.min_trust_region_radius = 1e-12;
677  opts.max_trust_region_radius = 1e+12;
678  opts.use_nonmonotonic_steps = false;
679  opts.max_consecutive_nonmonotonic_steps = 4;
680  opts.max_num_consecutive_invalid_steps = 4;
681  opts.minimizer_progress_to_stdout = (mutils::Log::level >=
683  opts.max_num_iterations = maxIter;
684  opts.parameter_tolerance = relerr;
685  opts.function_tolerance = 0.1*relerr;
686 
687  // Debug check only.
688  opts.check_gradients = false;
689  opts.gradient_check_relative_precision = 1e-12;
690 
692  covblocks.emplace_back(p+P0, p+P0);
693 
694  ceres::Covariance::Options covopts;
695  covopts.algorithm_type = ceres::DENSE_SVD;
696  covopts.null_space_rank = -1;
697 
698  ceres::Covariance covinst(covopts);
699 
700  // Set initial conditions for first pixel.
701  // These are updated at the end on the loop.
702  memcpy(p, p0, (P0+NP)*sizeof(p[0]));
703 
704  // Multi-simulation parameter storage for statistical analysis.
705  double pdata[4+1][nSamp], pstats[4+1][6];
706 
707  // Loop over map pixels.
708  // Each thread traverses individual rows, interleaved to minimize
709  // differences in thread execution time across the map.
710  const unsigned nRows = gdata.axis[1], nCols = gdata.axis[0],
711  px = nRows*nCols, px0 = ((nRows+nThreads-1)/nThreads)*nCols,
712  px10 = px0/10, iSkip = (nThreads-1)*nCols;
713  unsigned iOut = (id==0 ? 0 : ~0U);
714  for (unsigned i=id*nCols, iCol=0, iTot=0; i < px;
715  iTot++, iCol=(iCol==nCols-1 ? 0 : iCol+1), i+=1+(iCol ? 0 : iSkip)) {
716  // Progress display.
717  if (iTot == iOut) {
718  MU_INFO(fn, "Solution %3lu%% complete @ %s",
719  (iOut == px0-1 ? 100 : 10*(iOut/px10)), asctime());
720  fflush(mutils::Log::stream);
721  iOut = (iOut == 9*px10 ? px0-1 : iOut+px10);
722  }
723 
724  // Reset target data.
725  unsigned nOk = 0;
726  for (unsigned j=0; j != NB; j++) {
727  auto Fobs = gdata.dnu0[j]*bandData[j][i],
728  Ferr = gdata.dnu0[j]*bandError[j][i];
729 
730  if (std::isfinite(Fobs) && std::isfinite(Ferr)) {
731  gdata.Fobs[j] = Fobs;
732  gdata.iFerr[j] = 1.0/Ferr;
733  nOk++;
734  } else {
735  gdata.Fobs[j] = gdata.iFerr[j] = 0.0; // Weight-out bad data.
736  }
737  }
738 
739  // Skip invalid/masked pixels.
740  if (nOk < minBands || (chi2Max >= 0.0 && chi2Map[i] <= chi2Max)) {
741  for (auto &omap: *gdata0.outMap) { omap[i] = NAN; }
742  memcpy(p, p0, (P0+NP)*sizeof(p[0]));
743  continue;
744  }
745 
746  // Initial temperature override.
747  if (tempMap.size()) {
748  p[0] = tempMap[i];
749  if (NP == 3) { prob.SetParameterUpperBound(p+P0, 1, p[0]); }
750  }
751 
752  // Find best-fit parameters.
753  int nIter = robustFit(prob, sum, opts, cost,
754  p+P0, nullptr, pmin+P0, pmax+P0,
755  NP, minCost, maxCost, nSamp > 1);
756  double chi2_dof = 2.0*sum.final_cost*iDOF, dchi2_dof = 0.0;
757  MU_DEBUG(fn, "CERES report:\n%s", sum.FullReport().c_str());
758 
759  // Check convergence.
760  if (! sum.IsSolutionUsable()) {
761  MU_WARN(fn, "(%ld,%ld) Solution unusable; masking pixel",
762  1+iCol, 1+i/nCols);
763  for (auto &omap: *gdata0.outMap) { omap[i] = NAN; }
764  memcpy(p, p0, (P0+NP)*sizeof(p[0]));
765  continue;
766  }
767  MU_INFO_IF(sum.termination_type == ceres::NO_CONVERGENCE, fn,
768  "(%ld,%ld) Iteration limit reached", 1+iCol, 1+i/nCols);
769 
770  // Fiducial best-fit parameters.
771  double Th = p[0], Sh = p[1]*Srescale, dTh = 0.0, dSh = 0.0,
772  Tc = p[2], Sc = p[3]*Srescale, dTc = 0.0, dSc = 0.0;
773 
774  if (nSamp == 1) {
775  // Use covariance matrix to estimate parameter variance.
776  if (covinst.Compute(covblocks, &prob)) {
777  covinst.GetCovarianceBlock(p+P0, p+P0, cov);
778  } else {
779  MU_INFO(fn, "(%ld,%ld) Bad covariance matrix; masking values",
780  1+iCol, 1+i/nCols);
781  for (unsigned j = 0; j != NP; j++) { cov[NP*j+j] = NAN; }
782  }
783  dTh = (P0 == 0 ? sqrt(cov[NP*0+0]) : 0.0);
784  dSh = sqrt(cov[NP*(1-P0)+(1-P0)])*Srescale;
785  dTc = (NP > 2 ? sqrt(cov[NP*(2-P0)+(2-P0)]) : 0.0);
786  dSc = (NP > 2 ? sqrt(cov[NP*(3-P0)+(3-P0)]) : 0.0)*Srescale;
787  } else {
788  // Resample data to estimate parameter variance.
789 
790  // Fiducial model flux.
791  double F0[NB], Fobs0[NB];
792  calcF(F0, Th, Sh, Tc, Sc);
793  memcpy(Fobs0, &gdata.Fobs[0], sizeof(Fobs0));
794 
795  // Fiducial model is the first sample.
796  unsigned nOk = 1;
797  pdata[0][0] = p[0], pdata[1][0] = p[1];
798  pdata[2][0] = p[2], pdata[3][0] = p[3];
799  pdata[4][0] = chi2_dof;
800 
801  // Data resampling loop.
802  for (unsigned j=1; j != nSamp; j++) {
803  // Fiducial initial guess.
804  p[0] = Th, p[1] = Sh*Sscale;
805  p[2] = Tc, p[3] = Sc*Sscale;
806 
807 #ifdef INIT_SAMPLES
808  // Resample initial guess.
809  if (P0 == 0) { p[0] += 2.0*(gsl_rng_uniform(rng)-0.5); }
810  p[1] *= 0.5 + 1.5*gsl_rng_uniform(rng);
811  if (NP > 2) {
812  p[2] += 2.0*(gsl_rng_uniform(rng)-0.5);
813  p[3] *= 0.5 + 1.5*gsl_rng_uniform(rng);
814  }
815 #else
816  // Resample "observed" flux applying both systematic and random errors.
817  const double *Fsys = F0;
818  if (sysSamp) { sysF(F0, j-1); Fsys = &gdata.Fobs[0]; }
819  //
820  if (gridSamp) { gridF(Fsys, (j-1)>>sysBits); }
821  else if (randSamp) { randF(Fsys); }
822  MU_DEBUG(fn, "sample %2lu: dF = {%7.4f, %7.4f, %7.4f, %7.4f}", nOk,
823  (gdata.Fobs[0]-F0[0])*gdata.iFerr[0],
824  (gdata.Fobs[1]-F0[1])*gdata.iFerr[1],
825  (gdata.Fobs[2]-F0[2])*gdata.iFerr[2],
826  (gdata.Fobs[3]-F0[3])*gdata.iFerr[3]);
827 
828  // Cycle skip check.
829  bool ok = true;
830  for (auto f: gdata.Fobs) {
831  if (! std::isfinite(f)) { ok = false; break; }
832  }
833  if (!ok) { MU_DEBUG(fn, "Skipping..."); continue; }
834 #endif // INIT_SAMPLES
835 
836  // Find and record best-fit parameters.
837  nIter += robustFit(prob, sum, opts, cost,
838  p+P0, nullptr, pmin+P0, pmax+P0,
839  NP, minCost, maxCost);
840  if (sum.IsSolutionUsable() &&
841  sum.termination_type != ceres::NO_CONVERGENCE) {
842  pdata[0][nOk] = p[0], pdata[1][nOk] = p[1];
843  pdata[2][nOk] = p[2], pdata[3][nOk] = p[3];
844  pdata[4][nOk] = 2.0*sum.final_cost*iDOF;
845  MU_DEBUG(fn, "sample %lu: p = {%7.4f, %7.4f, %7.4f, %7.4f} [%.4f]",
846  nOk, p[0], p[1], p[2], p[3], pdata[4][nOk]);
847  nOk++;
848  }
849  }
850 
851  // Analyze statistics.
852  if (P0) { pstats[0][1] = pstats[0][3] = 0.0; } // Fixed temperature.
853  for (unsigned j=P0; j != P0+NP; j++) {
854  calcStats(pstats[j], pdata[j]+0, nOk);
855  MU_DEBUG(fn, "pstats[%lu]: %7.4f(%7.4f) %7.4f(%7.4f) %7.4f %7.4f", j,
856  pstats[j][0], pstats[j][1], pstats[j][2],
857  pstats[j][3], pstats[j][4], pstats[j][5]);
858  }
859  dTh = pstats[0][3], dSh = pstats[1][3]*Srescale;
860  dTc = pstats[2][3], dSc = pstats[3][3]*Srescale;
861  //
862  calcStats(pstats[4], pdata[4]+0, nOk);
863  dchi2_dof = pstats[4][3];
864 
865  // Restore original observations.
866  memcpy(&gdata.Fobs[0], Fobs0, sizeof(Fobs0));
867 
868 #ifdef MEDIAN_FIT
869  // Replace fiducial fit with "median" solution.
870  unsigned iOpt = findMedianModel(pstats, NP, pdata[0], nOk, nSamp);
871  Th = pdata[0][iOpt], Sh = pdata[1][iOpt]*Srescale;
872  Tc = pdata[2][iOpt], Sc = pdata[3][iOpt]*Srescale;
873  chi2_dof = pdata[4][iOpt];
874 #else
875  // Perform trial fits using final uncertainties.
876  double dp[4];
877  for (unsigned j=P0; j != P0+NP; j++) {
878  p[j] = pstats[j][0], dp[j] = pstats[j][3];
879  }
880  nIter += robustFit(prob, sum, opts, cost, p+P0, dp+P0, pmin+P0, pmax+P0,
881  NP, minCost, maxCost, true);
882 
883  // Use new solution if superior.
884  double chi2_dof1 = 2.0*sum.final_cost*iDOF;
885  if (chi2_dof1 < pdata[4][0]) {
886  Th = p[0], Sh = p[1]*Srescale;
887  Tc = p[2], Sc = p[3]*Srescale;
888  chi2_dof = chi2_dof1;
889  } else {
890  p[0] = pdata[0][0], p[1] = pdata[1][0];
891  p[2] = pdata[2][0], p[3] = pdata[3][0];
892  }
893 #endif // MEDIAN_FIT
894  }
895  MU_DEBUG(fn, "(%2ld,%2ld) Th = %5.2f(%.2f), Sh = %7.4f(%.4f), "
896  "Tc = %5.2f(%.2f), Sc = %7.4f(%.4f), chi2/DOF = %.3f(%.3f)",
897  1+iCol, 1+i/nCols, Th, dTh, Sh, dSh, Tc, dTc, Sc, dSc,
898  chi2_dof, dchi2_dof);
899 
900  // Map outputs.
901  Thalo[i] = Th, dThalo[i] = dTh;
902  Nhalo[i] = Sh*imu_g, dNhalo[i] = dSh*imu_g;
903  if (NP > 2) {
904  Tcore[i] = Tc, dTcore[i] = dTc;
905  Ncore[i] = Sc*imu_g, dNcore[i] = dSc*imu_g;
906  }
907  Cmap[i] = chi2_dof, dCmap[i] = dchi2_dof;
908  Imap[i] = nIter;
909 
910  // Solution quality maps.
911  for (unsigned j=0; j != NB; j++) {
912  auto Dj = gdata.band[j];
913  double Fj = (NP < 3 ? gray.F(Th, Sh, Dj) : gray.F(Tc, Sc, Th, Sh, Dj));
914 
915  diffMap[j][i] = (Fj - gdata.Fobs[j])*(absdiff ? 1.0/gdata.dnu0[j] :
916  gdata.iFerr[j]);
917  if (NP > 2) { fracMap[j][i] = gray.F(Tc, Sc, Th, 0.0, Dj) / Fj; }
918  }
919 
920  // Chain next initial guess to previous solution (if sane).
921  // Otherwise reset to standard values.
922  if (chi2_dof > 40.0 || (NP > 2 && fabs(Tc-T1) > 3.0)) {
923  memcpy(p, p0, (P0+NP)*sizeof(p[0]));
924  }
925  }
926 
927  // Deallocate storage.
928  gsl_rng_free(rng);
929 }
930 
931 
954  const std::vector<long> &ll,
955  const std::vector<long> &ur,
956  const std::vector<long> &st,
957  const std::string &hduName)
958 {
959  const char *const fn = "getSingleMap";
960  mapDataType map;
962 
963  if (cli.getOption('s', &files, 1)) {
964  try {
966  fmap{new CCfits::FITS(files.front(), CCfits::Read, hduName, true)};
967  if (st[0]) { fmap->extension(hduName).read(map, ll, ur, st); }
968  else { fmap->extension(hduName).read(map); }
969  } catch (...) {
970  MU_WARN(fn, "No HDU '%s' in file '%s'", hduName, files.front());
971  map.resize(0);
972  }
973  }
974 
975  return map;
976 }
977 
978 
1008  CCfits::FITS *outFITS,
1009  const std::vector<mapDataType> &bandData,
1010  const std::vector<mapDataType> &bandError,
1011  const std::vector<CCfits::PHDU*> &bandHDU,
1012  const std::vector<long> &axis,
1013  const mu::CommandLine &cli,
1014  const std::vector<long> &ll,
1015  const std::vector<long> &ur,
1016  const std::vector<long> &st)
1017 {
1018  const char *const fn = "solve";
1019  bool modelMap = cli.getOption('M');
1020 
1021  // Dust model(s).
1022  bool extend = !cli.getOption('p');
1023  auto modelh = cli.getString('D', "DL3"),
1024  ratioh = cli.getString('g', "100.0");
1025 
1026  // Different core/halo models?
1027  auto mpos = modelh.find(','), rpos = ratioh.find(',');
1028  decltype(modelh) modelc{modelh}, ratioc{ratioh};
1029  if (mpos != mutils::npos) {
1030  modelc = modelh.substr(mpos+1);
1031  modelh.erase(mpos);
1032  }
1033  if (rpos != mutils::npos) {
1034  ratioc = ratioh.substr(rpos+1);
1035  ratioh.erase(rpos);
1036  }
1037 
1038  Dust dusth(modelh, std::stod(ratioh)), dustc(modelc, std::stod(ratioc));
1039  size_t nParam = (cli.getOption('4') ? 4 : cli.getOption('3') ? 3 :
1040  cli.getOption('1') ? 1 : 2);
1041  MU_INFO(fn, "Using %s source detector response",
1042  extend ? "extended" : "point");
1043  MU_INFO(fn, "Minimum (halo) N(H2) = %.1e cm^-2", cli.getDouble('N', 0.0));
1044  MU_INFO(fn, "Hot/halo dust model: %s, gas-to-dust ratio = %g",
1045  dusth.name(), dusth.rho());
1046  MU_INFO_IF(nParam > 2, fn, "Cold/core dust model: %s, gas-to-dust ratio = %g",
1047  dustc.name(), dustc.rho());
1048 
1049  // Band detector setup.
1050  // Currently supports PACS/SPIRE only!
1051  size_t nBands = bandData.size();
1053  for (size_t j=0; j != nBands; j++) {
1054  int band = getBand(*bandHDU[j]);
1055  auto d = (band < 200 ? (Detector *)new PACS(band, extend)
1056  : (Detector *)new SPIRE(band, extend));
1057  d->init();
1058  detect.emplace_back(d);
1059  }
1060 
1061  // Allocate result maps.
1062  const double relerr = cli.getDouble('A', 1e-3);
1063  auto px = axis[0]*axis[1];
1064  char comm[1024];
1065  snprintf(comm, sizeof(comm),
1066  "%s sources, N(H2) >= %.1e cm^-2,"
1067  " %s/%s dust, gas/dust = %g/%g, relerr = %.1e",
1068  (extend ? "Extended" : "Point"), cli.getDouble('N', 0.0),
1069  dusth.name().c_str(), dustc.name().c_str(),
1070  dusth.rho(), dustc.rho(), relerr);
1071  //
1072  std::vector<std::string> mapNames = {"T", "dT", "N(H2)", "dN(H2)"};
1073  if (nParam > 2) {
1074  mapNames = {"Th", "dTh", "Nh(H2)", "dNh(H2)",
1075  "Tc", "dTc", "Nc(H2)", "dNc(H2)"};
1076  }
1077  for (auto m: {"Chi2/DOF", "dChi2/DOF", "nIter"}) { mapNames.emplace_back(m); }
1078  for (auto &b: bandHDU) {
1079  // Band fit residuals.
1080  mapNames.push_back("diff" + std::to_string(getBand(*b)));
1081  }
1082  if (nParam > 2) {
1083  for (auto &b: bandHDU) {
1084  // Cold flux fraction.
1085  mapNames.push_back("frac" + std::to_string(getBand(*b)));
1086  }
1087  }
1088  //
1089  // CCFits API omits 'const'...
1090  std::vector<long> ax{axis};
1091  for (auto name: mapNames) {
1092  outMap.push_back(mapDataType(0.0, px));
1093  outHDU.push_back(outFITS->addImage(name, mapDataCode, ax));
1094  outHDU.back()->writeDate();
1095  outHDU.back()->writeComment(comm);
1096  }
1097 
1098  // Emission model.
1099  constexpr double MJy = 1e-17; // MJy -> erg/cm^2/s/Hz
1100  Graybody gray{dusth, dustc, 0.1*relerr};
1101  GrayData gdata{&gray, &outMap, nBands};
1102  for (size_t j=0; j != nBands; j++) {
1103  gdata.band[j] = detect[j].get();
1104  gdata.dnu0.push_back(detect[j]->freqWidth() * MJy);
1105  }
1106  gdata.axis = axis;
1107 
1108  // Theoretical model map option.
1109  if (modelMap) {
1110  makeModelMap(const_cast<std::vector<mapDataType>&>(bandData),
1111  const_cast<std::vector<mapDataType>&>(bandError),
1112  gdata, nParam);
1113  }
1114 
1115  // Google logging for Ceres solver.
1116  google::InitGoogleLogging("manticore");
1117 
1118  // Create threads and process maps.
1119  unsigned nThreads = std::max(1L, cli.getInteger('t', 1));
1120  auto tempMap = getSingleMap(cli, ll, ur, st, "T");
1121  auto chi2Map = getSingleMap(cli, ll, ur, st, "Chi2/DOF");
1122  auto solver = (nParam == 1 ? &findParameters<1> :
1123  nParam == 2 ? &findParameters<2> :
1124  nParam == 3 ? &findParameters<3> :
1125  &findParameters<4>);
1126 
1127 #undef CHI2_MIN
1128 #ifdef CHI2_MIN
1129 // Quick hack to examine global chi^2 behavior vs. systematic errors.
1130 auto bandData1 = bandData;
1131 double sErr[5], sMax[5] = {0.05, 0.04, 0.015, 0.015, 0.015};
1132 const unsigned iChi = (nParam <= 2 ? 4 : 8);
1133 for (unsigned j=0, n=5, nSamp=n*n*n*n*n; j != nSamp; j++) {
1134  // Error state.
1135  for (unsigned k=0; k != 5; k++) {
1136  sErr[k] = gridSample(0.0, sMax[k], k, j, n);
1137  }
1138  for (unsigned k=0; k != px; k++) {
1139  bandData1[0][k] = bandData[0][k] * (1.0 + sErr[0]);
1140  bandData1[1][k] = bandData[1][k] * (1.0 + sErr[1] + sErr[2]);
1141  bandData1[2][k] = bandData[2][k] * (1.0 + sErr[1] + sErr[3]);
1142  bandData1[3][k] = bandData[3][k] * (1.0 + sErr[1] + sErr[4]);
1143  }
1144 #else
1145 # define bandData1 bandData
1146 #endif // CHI2_MIN
1147 
1148  std::vector<std::thread> threads;
1149  for (unsigned i=1; i != nThreads; i++) {
1150  threads.emplace_back(solver, bandData1, bandError, tempMap, chi2Map, cli,
1151  gdata, i, nThreads);
1152  }
1153 
1154  solver(bandData1, bandError, tempMap, chi2Map, cli, gdata, 0, nThreads);
1155  //
1156  for (auto &t: threads) { t.join(); }
1157 
1158 #ifdef CHI2_MIN
1159  double chi2_tot = 0.0;
1160  for (unsigned k=0; k != px; k++) { chi2_tot += outMap[iChi][k]; }
1161  MU_INFO(fn, "<chi2>: %6.3f %+6.3f %+6.3f %+6.3f %+6.3f %+6.3f\n",
1162  chi2_tot/px, sErr[0], sErr[1], sErr[2], sErr[3], sErr[4]);
1163 }
1164 #endif // CHI2_MIN
1165 
1166 }
1167 
1168 } // namespace manticore
std::vector< double > fixed
Fixed parameters (optional).
Definition: solve.cc:57
static FILE * stream
Message log file stream.
Definition: Log.h:433
std::vector< double > iFerr
Inverse flux uncertainty in each band.
Definition: solve.cc:57
void calcStats(double stats[], const double x0[], unsigned n)
Computes median (and arithmetic) statistics on an input vector.
Definition: solve.cc:323
std::vector< double > Fobs
Observed flux in each band.
Definition: solve.cc:57
unsigned getOption(const Option &option, Arguments *values=nullptr, unsigned nMax=-1) const
Retrieves all copies of a command line option.
Definition: CommandLine.cc:197
T front(T... args)
void makeModelMap(std::vector< mapDataType > &bandData, std::vector< mapDataType > &bandError, const GrayData &gdata, unsigned nParam)
Replace input observations with theoretical model.
Definition: solve.cc:208
#define MU_ERROR(src, msg,...)
Log a message at level Log::ERROR.
Definition: Log.h:164
double rcache_[NB]
Residual cache.
Definition: solve.cc:172
std::string asctime()
Current time a la std::asctime().
Definition: solve.cc:182
T stod(T... args)
T to_string(T... args)
#define bandData1
Ceres solver graybody cost model.
Definition: solve.cc:94
std::vector< long > axis
Image dimensions.
Definition: solve.cc:55
Command line options and arguments.
Definition: CommandLine.h:47
const std::string & name() const noexcept
Current model name.
Definition: Dust.h:65
std::string getString(char shortName, const std::string &defValue) const
Retrieves unique string option.
Definition: CommandLine.cc:392
#define MU_DEBUG(src, msg,...)
Log a message at level Log::DEBUG.
Definition: Log.h:68
Fcost_gray(const GrayData &gd)
Constructor.
Definition: solve.cc:100
std::vector< std::string > Arguments
Command line arguments type.
Definition: CommandLine.h:51
const double imu_g
Inverse mean molecular weight.
Definition: solve.cc:40
T time(T... args)
int robustFit(ceres::Problem &prob, ceres::Solver::Summary &sum, ceres::Solver::Options &opts, const ceres::CostFunction *cost, double p[], const double dp[], const double pmin[], const double pmax[], unsigned NP, double minCost=0.25, double maxCost=25.0, bool refit=false)
Non-linear fit with robustness checking.
Definition: solve.cc:433
double jcache_[NB *NP]
Jacobian cache.
Definition: solve.cc:172
Package namespace.
Definition: Detector.cc:15
T resize(T... args)
std::vector< const Detector * > band
Band detectors.
Definition: solve.cc:63
T min(T... args)
constexpr double m_H
Mass of hygdrogen atom (g).
Definition: manticore.h:33
double sysSample(double data, unsigned iBand, unsigned iCycle, const std::vector< std::string > &instr, const std::vector< const Detector * > &bands)
Sample systematic data errors on a fixed grid.
Definition: solve.cc:286
T push_back(T... args)
const GrayData & gd_
Definition: solve.cc:167
void clearCache() const
Clear result cache.
Definition: solve.cc:177
unsigned nBands
Number of observation bands.
Definition: solve.cc:68
static int level
Message logging level for output to stream.
Definition: Log.h:430
T pop_back(T... args)
std::map< std::string, manticore::tableType > modelMap
Definition: Dust.cc:641
std::vector< double > dnu0
Effective bandwidth.
Definition: solve.cc:57
std::vector< mapDataType > * outMap
Output maps.
Definition: solve.cc:66
MathUtils package.
Definition: CommandLine.cc:10
std::string::size_type npos
Definition: statics.cc:24
PACS detector class.
Definition: PACS.h:17
T isfinite(T... args)
static constexpr int DEBUG
Verbose information.
Definition: Log.h:350
T max(T... args)
Detector base class.
Definition: Detector.h:51
Graybody emission model.
Definition: Graybody.h:40
void solve(std::vector< mapDataType > &outMap, std::vector< CCfits::ExtHDU * > &outHDU, CCfits::FITS *outFITS, const std::vector< mapDataType > &bandData, const std::vector< mapDataType > &bandError, const std::vector< CCfits::PHDU * > &bandHDU, const std::vector< long > &axis, const mutils::CommandLine &cli, const std::vector< long > &ll, const std::vector< long > &ur, const std::vector< long > &st)
Performs least-squares fitting of band SEDs.
Definition: solve.cc:1006
double gridSample(double data, double err, unsigned iBand, unsigned iCycle, unsigned nSamp)
Sample random data errors on a fixed grid.
Definition: solve.cc:255
std::vector< std::string > setInstruments(const std::vector< const Detector * > &band)
Set unique instrument types.
Definition: solve.cc:497
T find(T... args)
T size(T... args)
unsigned findMedianModel(const double pstats[][6], unsigned nParam, const double *pdata, unsigned nModels, unsigned nCols)
Finds optimal model from a set of alternatives.
Definition: solve.cc:354
mapDataType getSingleMap(const mu::CommandLine &cli, const std::vector< long > &ll, const std::vector< long > &ur, const std::vector< long > &st, const std::string &hduName)
Read a map plane from the --single-temp FITS file.
Definition: solve.cc:953
int getBand(const CCfits::HDU &hdu)
Returns nominal band center (microns) from an HDU.
Definition: manticore.cc:105
Graybody * gray
Graybody emission model.
Definition: solve.cc:53
const double mu_g
Mean molecular weight.
Definition: solve.cc:39
SPIRE detector class.
Definition: SPIRE.h:14
T c_str(T... args)
#define MU_INFO(src, msg,...)
Log a message at level Log::INFO.
Definition: Log.h:100
constexpr long modelMapLen
One-dimensional length of model map image.
Definition: manticore.h:18
virtual bool Evaluate(double const *const *parameters, double *residuals, double **jacobians) const final
Evaluate the fitting function and its Jacobian.
Definition: solve.cc:103
T back(T... args)
#define MU_INFO_IF(cond, src, msg,...)
Conditionally log a message at level Log::INFO.
Definition: Log.h:110
#define MU_WARN(src, msg,...)
Log a message at level Log::WARN.
Definition: Log.h:132
int trialFit(ceres::Problem &prob, ceres::Solver::Summary &sum, ceres::Solver::Options &opts, const ceres::CostFunction *cost, double p[], unsigned NP, const double p0[]=nullptr, const ceres::Solver::Summary *sum0=nullptr)
Trial fit with rejection check.
Definition: solve.cc:387
Observation fitting data structure.
Definition: solve.cc:43
void findParameters(const std::vector< mapDataType > &bandData, const std::vector< mapDataType > &bandError, const mapDataType &tempMap, const mapDataType &chi2Map, const mu::CommandLine &cli, const GrayData &gdata0, unsigned id, unsigned nThreads)
Finds best-fit model parameters.
Definition: solve.cc:540
T sort(T... args)
constexpr int mapDataCode
Output FITS maps data code (cf. mapDataType).
Definition: manticore.h:15
GrayData(Graybody *g, std::vector< mapDataType > *o, size_t numBands)
Constructor.
Definition: solve.cc:45
static constexpr double scale
Surface density scaling factor.
Definition: solve.cc:97
long getInteger(char shortName) const
Retrieves unique decimal integer option.
Definition: CommandLine.cc:341
double getDouble(char shortName, double defValue) const
Retrieves unique floating point option.
Definition: CommandLine.cc:376
double rho() const noexcept
Current model gas-to-dust ratio.
Definition: Dust.h:68
std::valarray< float > mapDataType
Output FITS maps data type (cf. mapDataCode).
Definition: manticore.h:12
T emplace_back(T... args)
double pcache_[NP]
Parameter cache.
Definition: solve.cc:172
Dust model.
Definition: Dust.h:28