LSST Applications g1653933729+34a971ddd9,g1a997c3884+34a971ddd9,g2160c40384+da0d0eec6b,g28da252d5a+1236b942f7,g2bbee38e9b+e5a1bc5b38,g2bc492864f+e5a1bc5b38,g2ca4be77d2+192fe503f0,g2cdde0e794+704103fe75,g3156d2b45e+6e87dc994a,g347aa1857d+e5a1bc5b38,g35bb328faa+34a971ddd9,g3a166c0a6a+e5a1bc5b38,g3e281a1b8c+8ec26ec694,g4005a62e65+ba0306790b,g414038480c+9f5be647b3,g41af890bb2+c3a10c924f,g5065538af8+e7237db731,g5a0bb5165c+eae055db26,g717e5f8c0f+b65b5c3ae4,g80478fca09+4ce5a07937,g82479be7b0+08790af60f,g858d7b2824+b65b5c3ae4,g9125e01d80+34a971ddd9,ga5288a1d22+5df949a35e,gae0086650b+34a971ddd9,gb58c049af0+ace264a4f2,gbd397ab92a+2141afb137,gc28159a63d+e5a1bc5b38,gc805d3fbd4+b65b5c3ae4,gcf0d15dbbd+97632ccc20,gd6b7c0dfd1+de826e8718,gda6a2b7d83+97632ccc20,gdaeeff99f8+7774323b41,ge2409df99d+e6cadbf968,ge33fd446bb+b65b5c3ae4,ge79ae78c31+e5a1bc5b38,gf0baf85859+890af219f9,gf5289d68f6+a27069ed62,w.2024.37
LSST Data Management Base Package
Loading...
Searching...
No Matches
model.h
Go to the documentation of this file.
1#ifndef LSST_GAUSS2D_FIT_MODEL_H
2#define LSST_GAUSS2D_FIT_MODEL_H
3
4#include <algorithm>
5#include <map>
6#include <memory>
7#include <stdexcept>
8#include <string>
9
11#include "lsst/gauss2d/image.h"
14
15#include "data.h"
16#include "math.h"
17#include "parameters.h"
18#include "parametricmodel.h"
19#include "param_filter.h"
20#include "prior.h"
21#include "psfmodel.h"
22#include "source.h"
23#include "util.h"
24
25namespace lsst::gauss2d::fit {
26
27static const std::string ERRMSG_PARAMS
28 = "Were params fixed/freed since calling compute_*/evaluate?"
29 "Or does your PSF model have free parameters?";
30
32enum class EvaluatorMode {
33 image,
34 loglike,
38};
39
41 /*
42 * @param return_negative Whether the matrix should have all negative terms.
43 * Should be set to true if the inverse Hessian is being used to estimate errors.
44 * @param findiff_frac The value of the finite difference increment, as a fraction of the parameter value.
45 * @param findiff_add The minimum of the finite difference increment (added to the fraction).
46 */
47 bool return_negative = true;
48 double findiff_frac = 1e-4;
49 double findiff_add = 1e-4;
50};
51
55
56/*
57 A Model is a collection of Sources used to represent a model of the
58 provided Data. The main purpose of Models is to evaluate themselves to
59 generate output Images and/or gradients thereof to compare to the Data
60 for fitting.
61
62 Because Models are designed for repeated evaluations for fitting, one
63 must explicitly set up the Evaluators to perform the exact type of
64 evaluation required. In the future, a convenience "evaluate" function
65 with arguments could be added, but it would in any case require
66 storing and checking arguments against the previous call and therefore
67 be less efficient.
68*/
77template <typename T, typename Image, typename Indices, typename Mask>
78class Model : public ParametricModel {
79public:
85
95 Priors& priors)
96 : _data(std::move(data)),
97 _size(_data == nullptr ? 0 : _data->size()),
98 _size_priors(priors.size()) {
99 if (_data == nullptr) throw std::invalid_argument("Model data can't be null");
100 size_t size = this->size();
101 if (psfmodels.size() != size) {
102 throw std::invalid_argument("Model psfmodels.size()=" + std::to_string(psfmodels.size())
103 + "!= data.size()=" + std::to_string(size));
104 }
105
106 _outputs.resize(size);
107 _psfmodels.reserve(size);
108 size_t i = 0;
109 for (auto& psfmodel : psfmodels) {
110 if (psfmodel == nullptr)
111 throw std::invalid_argument("Model psfmodels[" + std::to_string(i) + "] can't be null");
112 _psfcomps.push_back(psfmodel->get_gaussians());
113 _psfmodels.push_back(psfmodel);
114 i++;
115 }
116
117 _priors.reserve(_size_priors);
118 i = 0;
119 for (auto& prior : priors) {
120 if (prior == nullptr)
121 throw std::invalid_argument("Model priors[" + std::to_string(i) + "] can't be null");
122 _priors.push_back(prior);
123 i++;
124 }
125
126 _sources.reserve(sources.size());
127 i = 0;
128 for (auto& source : sources) {
129 if (source == nullptr)
130 throw std::invalid_argument("Model Sources[" + std::to_string(i) + "] can't be null");
131 _sources.push_back(source);
132 i++;
133 }
134
135 _gaussians_srcs = this->_get_gaussians_srcs();
136 _factors_extra_in.resize(_size);
137 _factors_grad_in.resize(_size);
138 _map_extra_in.resize(_size);
139 _map_grad_in.resize(_size);
140 }
141
142private:
145 ExtraParamFactors _factors_extra = {};
146 std::vector<std::weak_ptr<Image>> _factors_extra_in;
147 GradParamFactors _factors_grad = {};
148 std::vector<std::weak_ptr<Image>> _factors_grad_in;
149 GaussiansMap _gaussians_srcs;
151 bool _is_setup = false;
152 std::vector<double> _likelihood_const_terms = {};
155 EvaluatorMode _mode;
156 size_t _n_params_free = 0;
157 ParameterMap _offsets_params = {};
161 PsfModels _psfmodels;
162 std::vector<std::shared_ptr<Image>> _outputs_prior = {};
163 std::shared_ptr<Image> _residuals_prior = nullptr;
164 size_t _size;
165 size_t _size_priors;
166 Sources _sources;
167
168 void _check_obs_idx(size_t idx) const {
169 if (!(idx < _size)) {
170 throw std::invalid_argument("idx=" + std::to_string(idx)
171 + " !< data->size()=" + std::to_string(_size));
172 }
173 }
174
176 GaussiansMap _get_gaussians_srcs() const {
177 GaussiansMap gaussians_srcs{};
178 size_t n_gaussians_src = 0;
179
180 for (const Channel& channel : _data->get_channels()) {
181 size_t n_gaussians_c = 0;
182 gaussians_srcs[channel] = std::vector<std::unique_ptr<const Gaussians>>();
183 auto& gaussians_src = gaussians_srcs[channel];
184 for (const auto& src : this->get_sources()) {
185 gaussians_src.push_back(src->get_gaussians(channel));
186 n_gaussians_c += gaussians_src.back()->size();
187 }
188 if (n_gaussians_src == 0) {
189 n_gaussians_src = n_gaussians_c;
190 } else if (n_gaussians_src != n_gaussians_c) {
191 throw std::logic_error("n_gaussians[" + channel.str() + "]=" + std::to_string(n_gaussians_c)
192 + "!=n_gaussians=" + std::to_string(n_gaussians_src));
193 }
194 }
195 return gaussians_srcs;
196 }
197
205 double _evaluate_observation(size_t idx, bool print = false, bool skip_zeroing = false) {
206 if (print) std::cout << "Evaluating observation[" << idx << "]" << std::endl;
207 if (_mode == EvaluatorMode::jacobian || _mode == EvaluatorMode::loglike_grad) {
208 const auto& channel = _data->at(idx).get().get_channel();
209 const size_t n_gaussians_psf = _psfcomps[idx]->size();
210 const auto& gaussians_src = _gaussians_srcs.at(channel);
211
212 const auto& factors_extra_in = _factors_extra_in[idx].lock();
213 const auto& factors_grad_in = _factors_grad_in[idx].lock();
214
215 size_t offset = 0;
216 for (size_t i_psf = 0; i_psf < n_gaussians_psf; ++i_psf) {
217 size_t i_src = 0;
218 for (const auto& src : this->get_sources()) {
219 if (print)
220 std::cout << "Setting factors for psf[" << i_psf << "], src[" << i_src << "]"
221 << std::endl;
222 const size_t n_gauss_src = gaussians_src[i_src++]->size();
223 if (n_gauss_src != src->get_n_gaussians(channel)) {
224 throw std::logic_error(
225 this->str() + "._data[" + channel.str() + "].get_n_gaussians(channel)="
226 + std::to_string(src->get_n_gaussians(channel))
227 + " != get_gaussians(channel).size()=" + std::to_string(n_gauss_src));
228 }
229 src->set_grad_param_factors(channel, _factors_grad, offset);
230 src->set_extra_param_factors(channel, _factors_extra, offset);
231 // Copy values to the actual input arrays used by the evaluator
232 const size_t row_max = offset + n_gauss_src;
233 for (size_t row = offset; row < row_max; ++row) {
234 if (print) {
235 std::cout << "factors_extra[" << row << "]=[";
236 }
237 for (size_t col = 0; col < lsst::gauss2d::N_EXTRA_FACTOR; ++col) {
238 factors_extra_in->set_value_unchecked(row, col, _factors_extra[row][col]);
239 if (print) {
240 std::cout << factors_extra_in->get_value_unchecked(row, col) << ",";
241 }
242 }
243 if (print) {
244 std::cout << "]\nfactors_grad[" << row << "]=[";
245 }
246 for (size_t col = 0; col < lsst::gauss2d::N_PARAMS_GAUSS2D; ++col) {
247 factors_grad_in->set_value_unchecked(row, col, _factors_grad[row][col]);
248 if (print) {
249 std::cout << factors_grad_in->get_value_unchecked(row, col) << ",";
250 }
251 }
252 if (print) {
253 std::cout << "]\n";
254 }
255 }
256 offset = row_max;
257 }
258 }
259 if (!skip_zeroing) {
260 auto& grads = *(this->_grads[idx]);
261 for (auto& grad : grads) grad->fill(0);
262 }
263 }
264 return _evaluators[idx]->loglike_pixel();
265 }
266
273 double _evaluate_priors(bool print = false, bool normalize_loglike = false) {
274 bool is_jacobian = _mode == EvaluatorMode::jacobian;
275 double loglike = 0;
276 size_t idx_resid = 0;
277
278 if (print) std::cout << "Evaluating priors" << std::endl;
279 for (const auto& prior : _priors) {
280 auto eval = prior->evaluate(is_jacobian, normalize_loglike);
281 loglike += eval.loglike;
282 if (_residuals_prior != nullptr) {
283 size_t idx_jac = idx_resid;
284 size_t n_resid = eval.residuals.size();
285 for (const double value : eval.residuals) {
286 this->_residuals_prior->set_value(0, idx_resid++, value);
287 }
288 // Reset the index back to the original start
289 idx_resid = idx_jac;
290 if (is_jacobian) {
291 for (const auto& [param, values] : eval.jacobians) {
292 if (values.size() != n_resid) {
293 throw std::logic_error("Prior=" + prior->str() + " param=" + param.get().str()
294 + " has n_values=" + std::to_string(values.size())
295 + " != residuals.size=" + std::to_string(n_resid));
296 }
297 const size_t idx_param = _offsets_params.at(param);
298 const auto& img = _outputs_prior.at(idx_param);
299 for (const double value : values) {
300 img->set_value(0, idx_jac++, value);
301 }
302 idx_jac = idx_resid;
303 }
304 }
305 idx_resid += n_resid;
306 }
307 if (print)
308 std::cout << "Evaluated prior with loglike=" << eval.loglike << "; at idx_resid=" << idx_resid
309 << std::endl;
310 }
311 return loglike;
312 }
313
329 _make_evaluator(const size_t idx_obs, EvaluatorMode mode = EvaluatorMode::image,
331 std::shared_ptr<Image> residual = nullptr, bool print = false) {
332 _check_obs_idx(idx_obs);
333
334 const Observation& observation = _data->at(idx_obs).get();
335 const Channel& channel = observation.get_channel();
336 const auto& psfcomps = _psfcomps.at(idx_obs);
337 const size_t n_c = observation.get_n_cols();
338 const size_t n_r = observation.get_n_rows();
339
340 const size_t n_outputs = outputs.size();
341 const bool has_outputs = n_outputs > 0;
342 const auto* outputs_obs = has_outputs ? &(outputs.at(idx_obs)) : nullptr;
343
344 const bool is_mode_image = mode == EvaluatorMode::image;
345 const bool is_mode_loglike_grad = mode == EvaluatorMode::loglike_grad;
346 const bool is_mode_jacobian = mode == EvaluatorMode::jacobian;
347 const bool do_output = is_mode_image || (mode == EvaluatorMode::loglike_image);
348
349 if (has_outputs) {
350 if (is_mode_jacobian || is_mode_loglike_grad) {
351 const size_t size_out = outputs_obs->size();
352 size_t n_obsect = is_mode_jacobian ? (_n_params_free + 1) : this->size();
353 if (size_out != n_obsect) {
354 throw std::invalid_argument("outputs[" + std::to_string(idx_obs)
355 + "].size()=" + std::to_string(size_out) + "!=n_"
356 + (is_mode_jacobian ? "free+1" : "obs") + "="
357 + std::to_string(n_obsect));
358 }
359 }
360 if (!(n_outputs > idx_obs)) {
361 throw std::invalid_argument("outputs.size()=" + std::to_string(n_outputs)
362 + " !> idx_obs=" + std::to_string(idx_obs));
363 }
364 }
365
366 std::shared_ptr<const CoordinateSystem> coordsys = observation.get_image().get_coordsys_ptr_const();
368 = !do_output ? nullptr
369 : (has_outputs ? outputs_obs->at(0)
370 : std::make_shared<Image>(n_r, n_c, nullptr, coordsys));
371
372 auto data_eval = !is_mode_image ? observation.get_image_ptr_const() : nullptr;
373 auto sigma_inv = !is_mode_image ? observation.get_sigma_inverse_ptr_const() : nullptr;
374
375 const auto& gaussians_src = _gaussians_srcs.at(channel);
376 const size_t n_gaussians_psf = psfcomps->size();
377
378 for (size_t p = 0; p < n_gaussians_psf; ++p) {
379 const auto& psfcomp = psfcomps->at(p);
380 const auto value = psfcomp.get_integral_value();
381 // TODO: Downgrade to warning and/or handle neatly
382 if (!(value > 0)) {
383 throw std::runtime_error("psfcomps[" + std::to_string(p) + "]=" + psfcomp.str()
384 + " get_integral_value()=" + std::to_string(value) + "!>0");
385 }
386 }
387
388 size_t n_gaussians_src = 0;
389 for (const auto& gaussians : gaussians_src) n_gaussians_src += gaussians->size();
390 const size_t n_gaussians_conv = n_gaussians_src * n_gaussians_psf;
391
393
394 for (auto psfit = psfcomps->cbegin(); psfit < psfcomps->cend(); psfit++) {
395 for (const auto& gaussians : gaussians_src) {
396 for (const auto& gaussian : *gaussians) {
397 data.emplace_back(std::make_shared<ConvolvedGaussian>(gaussian, *psfit));
398 }
399 }
400 }
401 auto gaussians = std::make_shared<const ConvolvedGaussians>(data);
402 if (gaussians->size() != n_gaussians_conv) {
403 throw std::logic_error("gaussians.size()=" + std::to_string(gaussians->size())
404 + "!= n_gaussians_conv=" + std::to_string(n_gaussians_conv)
405 + "= n_gaussians_src=" + std::to_string(n_gaussians_src)
406 + "= n_gaussians_psf=" + std::to_string(n_gaussians_psf)
407 + "(is_mode_jacobian=" + std::to_string(is_mode_jacobian));
408 }
409
410 std::shared_ptr<const Indices> map_extra_in, map_grad_in;
411 std::shared_ptr<const Image> factors_extra_in, factors_grad_in;
412
413 if (is_mode_jacobian || is_mode_loglike_grad) {
414 std::weak_ptr<Image> factors_extra_weak = _factors_extra_in[idx_obs];
415 std::weak_ptr<Image> factors_grad_weak = _factors_grad_in[idx_obs];
416 std::weak_ptr<Indices> map_extra_weak = _map_extra_in[idx_obs];
417 std::weak_ptr<Indices> map_grad_weak = _map_grad_in[idx_obs];
418
419 if (print) {
420 this->_make_param_maps<true>(factors_extra_weak, factors_grad_weak, map_extra_weak,
421 map_grad_weak, n_gaussians_conv, n_gaussians_psf, channel,
422 coordsys, idx_obs, map_extra_in, map_grad_in, factors_extra_in,
423 factors_grad_in);
424 } else {
425 this->_make_param_maps<false>(factors_extra_weak, factors_grad_weak, map_extra_weak,
426 map_grad_weak, n_gaussians_conv, n_gaussians_psf, channel,
427 coordsys, idx_obs, map_extra_in, map_grad_in, factors_extra_in,
428 factors_grad_in);
429 }
430 }
431
433 if (is_mode_jacobian) {
435 const size_t n_free = _n_params_free + 1;
436
437 for (size_t idx_img = 0; idx_img < n_free; ++idx_img) {
439 if (has_outputs) {
440 image = (*outputs_obs)[idx_img];
441 if (image == nullptr) {
442 throw std::invalid_argument("output[" + std::to_string(idx_img)
443 + "] can't be null for obs #" + std::to_string(idx_obs));
444 }
445 const auto& image_ref = observation.get_image();
446 std::string msg;
447 // We don't care about coordinate systems in this case
448 auto compatible = images_compatible(*image, image_ref, false, &msg);
449 if (!compatible) {
450 throw std::invalid_argument("outputs[" + std::to_string(idx_img) + "]=" + image->str()
451 + " incompatible with corresponding image="
452 + image_ref.str() + " for obs #" + std::to_string(idx_obs)
453 + " due to: " + msg);
454 }
455 } else {
456 image = std::make_shared<Image>(n_r, n_c, nullptr, coordsys);
457 }
458 arrays.emplace_back(image);
459 }
460 grads = std::make_shared<ImageArray<T, Image>>(&arrays);
461 } else if (is_mode_loglike_grad) {
463 if (print) {
464 std::cout << "Setting grads[" << idx_obs
465 << "] to image of n_cols=_n_params_free + 1=" << _n_params_free + 1
466 << "; _offsets_params.size()=" << _offsets_params.size() << std::endl;
467 }
468 auto image = has_outputs ? outputs_obs->at(0)
469 : std::make_shared<Image>(1, _n_params_free + 1, nullptr, coordsys);
470 arrays.emplace_back(image);
471 grads = std::make_shared<ImageArray<T, Image>>(&arrays);
472 } else {
473 grads = nullptr;
474 }
475 if (print) {
476 std::cout << observation.str() << "\n";
477 std::cout << gaussians->str() << "\n";
478 std::cout << "output is null: " << (output == nullptr) << "\n";
479 }
480
481 // Ensure grads & output aren't nullptr after move
482 auto grads2 = grads;
483 auto output2 = output;
485 = {std::move(output2), std::move(grads2)};
486
487 auto test = Evaluator(gaussians, data_eval, sigma_inv, output, residual, grads, map_grad_in,
488 factors_grad_in, map_extra_in, factors_extra_in);
489 auto evaluator
490 = std::make_unique<Evaluator>(gaussians, data_eval, sigma_inv, output, residual, grads,
491 map_grad_in, factors_grad_in, map_extra_in, factors_extra_in
492 //, background
493 );
494 return {std::move(evaluator), std::move(second)};
495 }
496
498 template <bool print>
499 void _make_param_maps(std::weak_ptr<Image> factors_extra_weak, std::weak_ptr<Image> factors_grad_weak,
500 std::weak_ptr<Indices> map_extra_weak, std::weak_ptr<Indices> map_grad_weak,
501 size_t n_gaussians_conv, size_t n_gaussians_psf, const Channel& channel,
502 std::shared_ptr<const CoordinateSystem> coordsys, size_t idx_obs,
503 std::shared_ptr<const Indices>& map_extra_in,
505 std::shared_ptr<const Image>& factors_extra_in,
506 std::shared_ptr<const Image>& factors_grad_in) {
507 unsigned int n_obsired = map_extra_weak.expired() + map_grad_weak.expired()
508 + factors_grad_weak.expired() + factors_extra_weak.expired();
509 const bool expired = n_obsired == 4;
510 if (print) {
511 std::cout << "expired=" << expired << "\n";
512 }
513 if (!((n_obsired == 0) || expired)) {
514 throw std::logic_error("jacobian n_obsired=" + std::to_string(n_obsired) + " not in (0,4)");
515 }
516 auto map_extra_mut = expired ? std::make_shared<Indices>(n_gaussians_conv, 2, nullptr, coordsys)
517 : map_extra_weak.lock();
518 auto map_grad_mut = expired ? std::make_shared<Indices>(
519 n_gaussians_conv, lsst::gauss2d::N_PARAMS_GAUSS2D, nullptr, coordsys)
520 : map_grad_weak.lock();
521 auto factors_extra_mut = expired ? std::make_shared<Image>(n_gaussians_conv, 3, nullptr, coordsys)
522 : factors_extra_weak.lock();
523 auto factors_grad_mut = expired ? std::make_shared<Image>(
524 n_gaussians_conv, lsst::gauss2d::N_PARAMS_GAUSS2D, nullptr, coordsys)
525 : factors_grad_weak.lock();
526 ;
527
528 ExtraParamMap map_extra = {};
529 GradParamMap map_grad = {};
530 ExtraParamFactors& factors_extra = _factors_extra;
531 GradParamFactors& factors_grad = _factors_grad;
532 factors_extra.clear();
533 factors_grad.clear();
534
535 map_extra.reserve(n_gaussians_conv);
536 map_grad.reserve(n_gaussians_conv);
537 factors_extra.reserve(n_gaussians_conv);
538 factors_grad.reserve(n_gaussians_conv);
539
540 /*
541 Make (and set) the param map; only make the factors (re-evaluated each time)
542 The maps depend on the order of free parameters.
543 the factors depend on their values, and can change every evaluation.
544 */
545 for (size_t i_psf = 0; i_psf < n_gaussians_psf; ++i_psf) {
546 for (const auto& src : this->get_sources()) {
547 src->add_grad_param_map(channel, map_grad, _offsets_params);
548 src->add_extra_param_map(channel, map_extra, map_grad, _offsets_params);
549
550 src->add_grad_param_factors(channel, factors_grad);
551 src->add_extra_param_factors(channel, factors_extra);
552 }
553 if constexpr (print) {
554 std::cout << "_offsets_params set to:" << std::endl;
555 std::cout << str_map_ref<true>(_offsets_params) << std::endl;
556 }
557 }
558 if (map_grad.size() != n_gaussians_conv) {
559 throw std::logic_error("map_grad.size()=" + std::to_string(map_grad.size())
560 + "!= n_gaussians_conv=" + std::to_string(n_gaussians_conv));
561 }
562 /*
563 Validate the map if there is already another Jacobian evaluator.
564
565 Do not support multiple evaluators set up with different maps (yet).
566 */
567 if (!expired) {
568 std::string sizes_mut = std::to_string(map_extra_mut->size()) + ","
569 + std::to_string(map_grad_mut->size()) + ","
570 + std::to_string(factors_extra_mut->size()) + ","
571 + std::to_string(factors_grad_mut->size());
572
573 std::string sizes = std::to_string(map_extra.size()) + "," + std::to_string(map_grad.size()) + ","
574 + std::to_string(factors_extra.size()) + ","
575 + std::to_string(factors_grad.size());
576
577 if (sizes != sizes_mut) {
578 throw std::runtime_error(
579 "make_evaluator Jacobian map_extra,map_grad,factors_extra,factors_grad sizes_new="
580 + sizes + "!=sizes_mut=" + sizes_mut + "; did you make a new evaluator with different"
581 "free parameters from an old one?");
582 }
583
584 std::array<std::string, 4> errmsgs = {"", "", "", ""};
585
586 size_t idx_err = 0;
587 if constexpr (print) std::cout << "map_extra=[" << std::endl;
588 for (size_t idx_row = 0; idx_row < map_extra.size(); idx_row++) {
589 const auto& row = map_extra[idx_row];
590 for (size_t col = 0; col < lsst::gauss2d::N_EXTRA_MAP; col++) {
591 const auto value_mut = map_extra_mut->get_value_unchecked(idx_row, col);
592 if ((idx_err < 2) && (row[col] != value_mut))
593 errmsgs[idx_err++] = "map_extra[" + std::to_string(idx_row) + "]["
595 + " != old=" + std::to_string(value_mut) + "\n";
596 }
597 if constexpr (print) {
598 std::cout << " ";
601 }
602 }
603 if constexpr (print) {
604 std::cout << "]" << std::endl << "map_grad=[" << std::endl;
605 }
606 for (size_t idx_row = 0; idx_row < map_grad.size(); idx_row++) {
607 const auto& row = map_grad[idx_row];
608 for (size_t col = 0; col < lsst::gauss2d::N_EXTRA_MAP; col++) {
609 const auto value_mut = map_grad_mut->get_value_unchecked(idx_row, col);
610 if ((idx_err < 4) && (row[col] != value_mut))
611 errmsgs[idx_err++] = "map_grad[" + std::to_string(idx_row) + "]["
613 + " != old=" + std::to_string(value_mut) + "\n";
614 }
615 if constexpr (print) {
616 std::cout << " ";
619 }
620 }
621 if constexpr (print) std::cout << "]" << std::endl;
622 std::string errmsg_extra = errmsgs[0] + errmsgs[1];
623 std::string errmsg_grad = errmsgs[0] + errmsgs[1];
624 if (!errmsg_extra.empty() || !errmsg_grad.empty()) {
625 std::string errmsg
626 = "make_evaluator Jacobian map_extra/map_grad have different values"
627 " from old; did you make a new evaluator with different free parameters from an "
628 " old one? Errors:\n"
629 + errmsg_extra;
630 if (!errmsg_extra.empty() && !errmsg_grad.empty()) errmsg += "...\n";
631 errmsg += errmsg_grad;
632 throw std::runtime_error(errmsg);
633 }
634 } else {
635 /*
636 Maps are stored as vectors in gauss2d_fit (to avoid unnecessary templating)
637 The inputs to gauss2d's Evaluator are templated Images, so copy the values
638 */
639 if constexpr (print) {
640 std::cout << "factors/maps: {\n";
641 }
642 for (size_t row = 0; row < n_gaussians_conv; ++row) {
643 for (size_t col = 0; col < lsst::gauss2d::N_EXTRA_MAP; ++col) {
644 map_extra_mut->set_value_unchecked(row, col, (map_extra)[row][col]);
645 }
646 for (size_t col = 0; col < lsst::gauss2d::N_EXTRA_FACTOR; ++col) {
647 factors_extra_mut->set_value_unchecked(row, col, (factors_extra)[row][col]);
648 }
649 for (size_t col = 0; col < lsst::gauss2d::N_PARAMS_GAUSS2D; ++col) {
650 map_grad_mut->set_value_unchecked(row, col, (map_grad)[row][col]);
651 factors_grad_mut->set_value_unchecked(row, col, (factors_grad)[row][col]);
652 }
653 if constexpr (print) {
654 std::cout << " row[" << row << "]: map_extra=[";
655 stream_iter_ref(map_extra[row], std::cout);
656 std::cout << ", factors_extra=";
657 stream_iter_ref(factors_extra[row], std::cout);
658 std::cout << ", map_grad=";
659 stream_iter_ref(map_grad[row], std::cout);
660 std::cout << ", factors_grad=";
661 stream_iter_ref(factors_grad[row], std::cout);
662 std::cout << "\n";
663 }
664 }
665 if constexpr (print) std::cout << "}" << std::endl;
666 }
667 _factors_extra_in[idx_obs] = factors_extra_mut;
668 _factors_grad_in[idx_obs] = factors_grad_mut;
669
670 map_extra_in = std::move(map_extra_mut);
671 map_grad_in = std::move(map_grad_mut);
672 factors_extra_in = std::move(factors_extra_mut);
673 factors_grad_in = std::move(factors_grad_mut);
674 }
675
676public:
677 void add_extra_param_map(const Channel& channel, ExtraParamMap& map_extra, const GradParamMap& map_grad,
678 ParameterMap& offsets) const override {
679 for (auto& source : _sources) source->add_extra_param_map(channel, map_extra, map_grad, offsets);
680 }
681 void add_extra_param_factors(const Channel& channel, ExtraParamFactors& factors) const override {
682 for (auto& source : _sources) source->add_extra_param_factors(channel, factors);
683 }
684 void add_grad_param_map(const Channel& channel, GradParamMap& map, ParameterMap& offsets) const override {
685 for (auto& source : _sources) source->add_grad_param_map(channel, map, offsets);
686 }
687 void add_grad_param_factors(const Channel& channel, GradParamFactors& factors) const override {
688 for (auto& source : _sources) source->add_grad_param_factors(channel, factors);
689 }
690
704 std::vector<double> compute_loglike_grad(bool include_prior = true, bool print = false,
705 bool verify = false, double findiff_frac = 1e-5,
706 double findiff_add = 1e-5, double rtol = 1e-3,
707 double atol = 1e-8) {
708 this->setup_evaluators(EvaluatorMode::loglike_grad, {}, {}, {}, nullptr, true, print);
709 this->evaluate(print);
710
711 auto filter_free = g2f::ParamFilter{false, true, true, true};
712 auto params_free = this->get_parameters_const_new(&filter_free);
713 params_free = nonconsecutive_unique<ParamBaseCRef>(params_free);
714
715 const size_t n_params = this->_offsets_params.size();
716 if (n_params != params_free.size()) {
717 throw std::runtime_error("_offsets_params=" + str_map_ref<true>(_offsets_params)
718 + ".size() = " + std::to_string(n_params)
719 + " != params_free=" + str_iter_ref<true>(params_free) + ".size() + 1 ="
720 + std::to_string(params_free.size()) + "; " + ERRMSG_PARAMS);
721 }
722 std::map<ParamBaseCRef, size_t> param_idx = {};
723 for (size_t idx_param = 0; idx_param < n_params; idx_param++) {
724 const auto param = params_free[idx_param];
725 if (_offsets_params.find(param) == _offsets_params.end()) {
726 throw std::runtime_error(param.get().str() + " not found in _offsets_params\n"
727 + ERRMSG_PARAMS);
728 }
729 param_idx[param] = idx_param;
730 }
731 if (print) {
732 std::cout << "params_free=" << str_iter_ref<true>(params_free) << std::endl;
733 std::cout << "_offsets_params=" << str_map_ref<true>(_offsets_params) << std::endl;
734 std::cout << "param_idx=" << str_map_ref<true>(param_idx) << std::endl;
735 }
736 std::vector<double> loglike_grads(n_params, 0.);
737
738 size_t idx_obs = 0;
739 for (const auto& grads_obs : _grads) {
740 const auto& values = grads_obs->at(0);
741 const size_t n_cols = values.get_n_cols();
742 if (n_cols != n_params + 1) {
743 throw std::logic_error(this->_data->at(idx_obs).get().str()
744 + " loglike_grads n_cols=" + std::to_string(n_cols)
745 + " != n_params (from offsets) = " + std::to_string(n_params + 1));
746 }
747 for (const auto& [paramref, col] : _offsets_params) {
748 loglike_grads.at(param_idx[paramref]) += values.get_value(0, col);
749 }
750 idx_obs++;
751 }
752 if (include_prior) {
753 for (const auto& prior : _priors) {
754 auto result = prior->evaluate(true);
755
756 // TODO: Confirm that this works with ShapePrior
757 for (const auto& paramref : params_free) {
758 double dll_dx = result.compute_dloglike_dx(paramref);
759 loglike_grads.at(param_idx[paramref]) += dll_dx;
760 }
761 }
762 }
763 if (verify) {
764 std::string errmsg;
765 this->setup_evaluators(g2f::EvaluatorMode::loglike);
766 auto loglike = this->evaluate();
767
768 auto params = this->get_parameters_new(&filter_free);
769 params = nonconsecutive_unique<ParamBaseRef>(params);
770
771 for (size_t idx_param = 0; idx_param < n_params; idx_param++) {
772 auto& param = params[idx_param].get();
773 double value = param.get_value_transformed();
774 double diff = std::copysign(std::abs(value * findiff_frac) + findiff_add,
775 loglike_grads[idx_param]);
776 diff = finite_difference_param(param, diff);
777 std::vector<double> loglike_new_plus;
778 std::vector<double> loglike_new_minus;
779 try {
780 param.set_value_transformed(value + diff / 2.);
781 loglike_new_plus = this->evaluate();
782 param.set_value_transformed(value - diff / 2.);
783 loglike_new_minus = this->evaluate();
784 } catch (std::runtime_error& e) {
785 param.set_value_transformed(value + diff);
786 loglike_new_plus = this->evaluate();
787 loglike_new_minus = loglike;
788 }
789 auto dloglike = (sum_iter(loglike_new_plus) - sum_iter(loglike_new_minus)) / diff;
790 auto close = isclose(dloglike, loglike_grads[idx_param], rtol, atol);
791 param.set_value_transformed(value);
792 if (!close.isclose) {
793 double dll_dx_exact = loglike_grads[idx_param];
794 double dlp_dx_sum = 0;
795 for (const auto& prior : _priors) {
796 double dlp_dx = prior->evaluate(true).compute_dloglike_dx(param, true);
797 dlp_dx_sum += dlp_dx;
798 }
799 double dlp_dx_findiff = (loglike_new_plus.back() - loglike_new_minus.back()) / diff;
800 errmsg += param.str() + " failed loglike_grad verification; isclose=" + close.str()
801 + " from findiff=" + to_string_float(dloglike) + " vs "
802 + to_string_float(dll_dx_exact)
803 + " (diff = " + to_string_float(dll_dx_exact - dloglike)
804 + " , ratio = " + to_string_float(dll_dx_exact / dloglike)
805 + "); dll_dx_prior=" + to_string_float(dlp_dx_sum)
806 + " vs findiff: " + to_string_float(dlp_dx_findiff) + "\n";
807 }
808 }
809 if (errmsg.size() > 0) throw std::logic_error(errmsg);
810 }
811
812 return loglike_grads;
813 }
814
825 std::unique_ptr<Image> compute_hessian(bool transformed = false, bool include_prior = true,
826 std::optional<HessianOptions> options = std::nullopt,
827 bool print = false) {
828 this->setup_evaluators(EvaluatorMode::loglike_grad, {}, {}, {}, nullptr, true, print);
829
830 auto filter_free = g2f::ParamFilter{false, true, true, true};
831 auto params_free = this->get_parameters_new(&filter_free);
832 params_free = nonconsecutive_unique<ParamBaseRef>(params_free);
833 const size_t n_params_free = params_free.size();
834
836 transforms.reserve(n_params_free);
837 if (!transformed) {
838 for (auto& paramref : params_free) {
839 auto& param = paramref.get();
840 transforms.emplace_back(param.get_transform_ptr());
841 double value = param.get_value();
842 param.set_transform(nullptr);
843 if (param.get_value() != value) {
844 throw std::logic_error("Param " + param.str() + " changed value from "
845 + std::to_string(value) + " after dropping transform");
846 }
847 }
848 }
849
850 auto hessian = std::make_unique<Image>(n_params_free, n_params_free);
851 const auto n_obs = this->size();
852 std::vector<size_t> param_idx(n_params_free);
853
854 size_t n_integral = 0;
855 size_t n_frac = 0;
856
857 for (size_t idx_param = 0; idx_param < n_params_free; idx_param++) {
858 const auto param = params_free[idx_param];
859 const auto found = _offsets_params.find(param);
860 if (found == _offsets_params.end()) {
861 throw std::runtime_error(param.get().str() + " not found in _offsets_params\n"
862 + ERRMSG_PARAMS);
863 }
864 param_idx[idx_param] = (*found).second;
865 if (not options) {
866 const auto name = param.get().get_name();
867 n_integral += name == IntegralParameterD::_name;
868 n_frac += name == ProperFractionParameterD::_name;
869 }
870 }
871
872 if (options) {
873 const HessianOptions& opt = *options;
874
875 auto loglike_grad = this->compute_loglike_grad(include_prior, false, false);
876
877 size_t idx_param = 0;
878 for (auto& paramref : params_free) {
879 auto& param = paramref.get();
880 const double value_init = param.get_value();
881 const double value_transformed = param.get_value_transformed();
882 // Make the finite differencing go away from the possible peak likelihood
883 // This is probably a good idea even if return_negative isn't necessary
884 double diff = std::copysign(std::abs(value_transformed * opt.findiff_frac) + opt.findiff_add,
885 -loglike_grad[idx_param]);
886 diff = finite_difference_param(param, diff);
887 double diffabs = std::abs(diff);
888 this->evaluate();
889
890 auto loglike_grad2 = this->compute_loglike_grad(include_prior, false, false);
891 for (size_t idx_col = 0; idx_col < n_params_free; ++idx_col) {
892 double dll_dx = loglike_grad2[idx_col] - loglike_grad[idx_col];
893 dll_dx *= opt.return_negative ? (1 - 2 * (dll_dx > 0)) / diffabs : 1 / diff;
894 hessian->set_value_unchecked(idx_param, idx_col, dll_dx);
895 }
896 param.set_value(value_init);
897 idx_param++;
898 }
899 } else {
900 if ((n_integral > 0) && (n_frac > 0)) {
901 throw std::runtime_error(
902 "Cannot compute_hessian without options for model with free Integral and "
903 "ProperFraction "
904 "parameters (this is a bug); please supply options to use finite differencing.");
905 }
906
908 this->evaluate();
909
910 hessian->fill(0);
911
912 for (size_t idx_obs = 0; idx_obs < n_obs; ++idx_obs) {
913 const auto& jacs = this->_grads.at(idx_obs);
914 for (size_t idx_param1 = 0; idx_param1 < n_params_free; ++idx_param1) {
915 const auto& jac1 = jacs->at(param_idx[idx_param1]);
916 const size_t n_rows = jac1.get_n_rows();
917 const size_t n_cols = jac1.get_n_cols();
918
919 for (size_t idx_param2 = 0; idx_param2 < n_params_free; ++idx_param2) {
920 const auto& jac2 = jacs->at(param_idx[idx_param2]);
921 const size_t n_rows2 = jac2.get_n_rows();
922 const size_t n_cols2 = jac2.get_n_cols();
923
924 if ((n_rows != n_rows2) || (n_cols != n_cols2)) {
925 throw std::logic_error("n_rows,n_cols=" + to_string_float(n_rows) + ","
926 + to_string_float(n_cols)
927 + "!= n_rows2,n_cols2=" + to_string_float(n_rows2) + ","
928 + to_string_float(n_cols2));
929 }
930 double dx = 0;
931 for (size_t row = 0; row < n_rows; ++row) {
932 for (size_t col = 0; col < n_cols; ++col) {
933 // Note the sign convention of negative diagonal values
934 // at maximum likelihood
935 dx -= jac1.get_value(row, col) * jac2.get_value(row, col);
936 }
937 }
938 if (print) {
939 std::cout << "calling hessian->add_value(" << idx_param1 << "," << idx_param2
940 << "," << dx << ")" << std::endl;
941 }
942 hessian->add_value(idx_param1, idx_param2, dx);
943 }
944 }
945 }
946 if (include_prior && (_priors.size() > 0)) {
947 std::map<ParamBaseCRef, size_t> param_idx_map = {};
948 for (size_t idx_param = 0; idx_param < n_params_free; idx_param++) {
949 const auto param = params_free[idx_param];
950 if (_offsets_params.find(param) == _offsets_params.end()) {
951 throw std::runtime_error(param.get().str() + " not found in _offsets_params\n"
952 + ERRMSG_PARAMS);
953 }
954 param_idx_map[param] = idx_param;
955 }
956
957 size_t prior_size = 0;
958 for (const auto& prior : _priors) {
959 prior_size += prior->size();
960 }
961 auto dll_prior = std::make_unique<Image>(n_params_free, prior_size);
962 dll_prior->fill(0);
963
964 size_t idx_prior = 0;
965 for (const auto& prior : _priors) {
966 auto result = prior->evaluate(true);
967
968 for (const auto& [param_cref, values_base] : result.jacobians) {
969 size_t idx_residual = 0;
970 size_t idx_param = param_idx_map[param_cref];
971 for (const auto& value_base : values_base) {
972 dll_prior->add_value(idx_param, idx_prior + idx_residual++, value_base);
973 }
974 }
975 idx_prior += prior->size();
976 }
977
978 for (size_t row = 0; row < n_params_free; ++row) {
979 for (size_t row2 = 0; row2 < n_params_free; ++row2) {
980 double dll_dx = 0;
981 for (size_t col = 0; col < prior_size; ++col) {
982 dll_dx -= dll_prior->get_value(row, col) * dll_prior->get_value(row2, col);
983 }
984 hessian->add_value(row, row2, dll_dx);
985 if (print) {
986 std::cout << "calling hessian->add_value(" << row << "," << row2 << "," << dll_dx
987 << ") for prior" << std::endl;
988 }
989 }
990 }
991 }
992 }
993
994 if (!transformed) {
995 size_t idx_param = 0;
996 for (auto& paramref : params_free) {
997 auto& param = paramref.get();
998 double value = param.get_value();
999 param.set_transform(transforms[idx_param]);
1000 if (param.get_value() != value) {
1001 throw std::logic_error("Param " + param.str() + " changed value from "
1002 + std::to_string(value) + " after dropping transform");
1003 }
1004 idx_param++;
1005 }
1006 }
1007
1008 return hessian;
1009 }
1010
1019 std::vector<double> evaluate(bool print = false, bool normalize_loglike = false) {
1020 if (!_is_setup) throw std::runtime_error("Can't call evaluate before setup_evaluators");
1021
1022 std::vector<double> result(_size + 1);
1023 bool is_loglike_grad = this->_mode == EvaluatorMode::loglike_grad;
1024
1025 if (is_loglike_grad) {
1026 // loglike_grad adds values, and they have to be reset to zero first
1027 // This must be done for all observations in this case, because
1028 // most parameters change grads in all observations
1029 for (size_t idx = 0; idx < _size; ++idx) {
1030 this->_grads[idx]->at(0).fill(0);
1031 }
1032 }
1033
1034 for (size_t idx = 0; idx < _size; ++idx) {
1035 result[idx] = this->_evaluate_observation(idx, print, is_loglike_grad);
1036 }
1037 if ((this->_mode == EvaluatorMode::loglike) || is_loglike_grad
1038 || (this->_mode == EvaluatorMode::loglike_image) || (this->_mode == EvaluatorMode::jacobian)) {
1039 result[_size] = this->_evaluate_priors(print, normalize_loglike);
1040 }
1041
1042 return result;
1043 }
1044
1051 double evaluate_observation(size_t idx) {
1052 _check_obs_idx(idx);
1053 return _evaluate_observation(_evaluators[idx]);
1054 }
1055
1058
1059 EvaluatorMode get_mode() const { return _mode; }
1060
1063 // TODO: Rethink this; it's not sufficient unless sources are single component gaussians
1064 in.reserve(_sources.size());
1065
1066 for (auto& source : _sources) {
1067 auto data = source->get_gaussians(channel)->get_data();
1068 in.emplace_back(data);
1069 }
1070
1071 return std::make_unique<lsst::gauss2d::Gaussians>(in);
1072 }
1073
1080 const size_t n_data = this->size();
1081 if (this->_likelihood_const_terms.empty()) {
1082 this->_likelihood_const_terms.resize(n_data + 1);
1083
1084 for (size_t idx = 0; idx < n_data; ++idx) {
1085 const auto& sigma_inv = this->_data[idx].get_sigma_inverse();
1086 double loglike = 0;
1087 const size_t n_rows = sigma_inv.get_n_rows();
1088 const size_t n_cols = sigma_inv.get_n_cols();
1089 for (size_t col = 0; col < n_cols; ++col) {
1090 for (size_t row = 0; row < n_rows; ++row) {
1091 loglike += LOG_1 - log(sigma_inv._get_value(row, col));
1092 }
1093 }
1094 _likelihood_const_terms[idx] = loglike;
1095 }
1096 }
1097 double loglike = 0;
1098 for (const auto& prior : this->_priors) {
1099 auto values = prior->get_loglike_const_terms();
1100 for (const auto value : values) loglike += value;
1101 }
1102 _likelihood_const_terms[n_data] = loglike;
1103 return _likelihood_const_terms;
1104 }
1105
1106 size_t get_n_gaussians(const Channel& channel) const override {
1107 size_t n_g = 0;
1108 for (auto& source : _sources) n_g += source->get_n_gaussians(channel);
1109 return n_g;
1110 }
1111
1114
1117 for (auto& [param, offset] : _offsets_params) {
1118 offsets.emplace_back(param, offset);
1119 }
1120 return offsets;
1121 }
1122
1123 ParamRefs& get_parameters(ParamRefs& params, ParamFilter* filter = nullptr) const override {
1124 for (auto& source : _sources) source->get_parameters(params, filter);
1125 for (auto& psfmodel : _psfmodels) psfmodel->get_parameters(params, filter);
1126 _data->get_parameters(params, filter);
1127 return params;
1128 }
1129
1130 ParamCRefs& get_parameters_const(ParamCRefs& params, ParamFilter* filter = nullptr) const override {
1131 for (auto& source : _sources) source->get_parameters_const(params, filter);
1132 for (auto& psfmodel : _psfmodels) psfmodel->get_parameters_const(params, filter);
1133 _data->get_parameters_const(params, filter);
1134 return params;
1135 }
1136
1139 ParamFilter* filter = nullptr) const {
1140 _check_obs_idx(idx);
1141 for (auto& source : _sources) source->get_parameters(params, filter);
1142 _psfmodels[idx]->get_parameters(params, filter);
1143 _data->at(idx).get().get_parameters(params, filter);
1144 return params;
1145 }
1146
1149 ParamFilter* filter = nullptr) const {
1150 _check_obs_idx(idx);
1151 for (auto& source : _sources) source->get_parameters_const(params, filter);
1152 _psfmodels[idx]->get_parameters_const(params, filter);
1153 _data->at(idx).get().get_parameters_const(params, filter);
1154 return params;
1155 }
1156
1158 Priors get_priors() const { return _priors; }
1159
1161 PsfModels get_psfmodels() const { return _psfmodels; }
1162
1164 Sources get_sources() const { return _sources; }
1165
1167 size_t index) const override {
1168 for (auto& source : _sources) {
1169 source->set_extra_param_factors(channel, factors, index);
1170 index += source->get_n_gaussians(channel);
1171 }
1172 }
1173
1174 void set_grad_param_factors(const Channel& channel, GradParamFactors& factors,
1175 size_t index) const override {
1176 for (auto& source : _sources) {
1177 source->set_grad_param_factors(channel, factors, index);
1178 index += source->get_n_gaussians(channel);
1179 }
1180 }
1181
1201 std::vector<std::shared_ptr<Image>> residuals = {},
1202 std::vector<std::shared_ptr<Image>> outputs_prior = {},
1203 std::shared_ptr<Image> residuals_prior = nullptr, bool force = false,
1204 bool print = false) {
1205 const size_t n_outputs = outputs.size();
1206 const bool has_outputs = n_outputs > 0;
1207 if (has_outputs) {
1208 if (n_outputs != size()) {
1209 throw std::invalid_argument("outputs.size()=" + std::to_string(n_outputs)
1210 + "!=this->size()=" + std::to_string(size()));
1211 }
1212 }
1213 const size_t n_residuals = residuals.size();
1214 const bool has_residuals = n_residuals > 0;
1215 if (has_residuals) {
1216 if (n_residuals != size()) {
1217 throw std::invalid_argument("residuals.size()=" + std::to_string(n_residuals)
1218 + "!=this->size()=" + std::to_string(size()));
1219 }
1220 }
1221 if (force || (!_is_setup || (mode != _mode))) {
1222 _evaluators.clear();
1223 _grads.clear();
1224 _outputs.clear();
1225 _outputs_prior.clear();
1226 _residuals_prior = nullptr;
1227 _evaluators.reserve(size());
1228 _is_setup = false;
1229
1230 const bool is_jacobian = mode == EvaluatorMode::jacobian;
1231 const bool is_loglike_grad = mode == EvaluatorMode::loglike_grad;
1232
1233 std::vector<ParamBaseCRef> params_free = {};
1234 if (is_jacobian || is_loglike_grad) {
1235 auto filter_free = g2f::ParamFilter{false, true, true, true};
1236 this->get_parameters_const(params_free, &filter_free);
1237 params_free = nonconsecutive_unique<ParamBaseCRef>(params_free);
1238 _n_params_free = params_free.size();
1239 if (print) {
1240 std::cout << "n_params_free=" << _n_params_free << " from params_free:" << std::endl;
1241 std::cout << str_iter_ref<true>(params_free) << std::endl;
1242 }
1243
1244 _grads.reserve(size());
1245 _offsets_params.clear();
1246 }
1247
1248 size_t n_residuals_prior = 0;
1249 bool do_residuals_prior = (_size_priors > 0)
1250 && (is_loglike_grad || is_jacobian || (mode == EvaluatorMode::loglike)
1251 || (_mode == EvaluatorMode::loglike_image));
1252 if (do_residuals_prior) {
1253 for (const auto& prior : this->_priors) {
1254 n_residuals_prior += prior->size();
1255 }
1256
1257 _residuals_prior
1258 = residuals_prior == nullptr
1259 ? (_residuals_prior = std::make_shared<Image>(1, n_residuals_prior))
1260 : std::move(residuals_prior);
1261 if ((_residuals_prior->get_n_rows() != 1)
1262 || (_residuals_prior->get_n_cols() != n_residuals_prior)) {
1263 throw std::invalid_argument("residuals_prior=" + _residuals_prior->str()
1264 + " rows,cols != 1," + std::to_string(n_residuals_prior));
1265 }
1266 if (print) std::cout << "residuals_prior built/validated" << std::endl;
1267 for (size_t col = 0; col < n_residuals_prior; ++col) {
1268 _residuals_prior->set_value(0, col, 0);
1269 }
1270 if (print) std::cout << "residuals_prior reset" << std::endl;
1271
1272 if (is_jacobian) {
1273 if (print) {
1274 std::cout << "setup_evaluators jacobian called for model:" << std::endl;
1275 std::cout << this->str() << std::endl;
1276 }
1277
1278 const size_t n_params_jac = _n_params_free + 1;
1279 if (outputs_prior.size() == 0) {
1280 for (size_t idx_jac = 0; idx_jac < n_params_jac; ++idx_jac) {
1281 _outputs_prior.emplace_back(std::make_shared<Image>(1, n_residuals_prior));
1282 }
1283 if (print) std::cout << "outputs_prior built" << std::endl;
1284 } else if (outputs_prior.size() != n_params_jac) {
1286 "jacobian outputs_prior->size()=" + std::to_string(outputs_prior.size())
1287 + " != (n_params_free + 1 = " + std::to_string(n_params_jac) + ")");
1288 }
1289 _outputs_prior.reserve(n_params_jac);
1290 size_t idx_jac = 0;
1291 for (auto& output_prior : outputs_prior) {
1292 if (output_prior == nullptr) {
1293 throw std::invalid_argument("output_prior[" + std::to_string(idx_jac)
1294 + "] is null");
1295 }
1296 if ((output_prior->get_n_rows() != 1)
1297 || (output_prior->get_n_cols() != n_residuals_prior)) {
1298 throw std::invalid_argument("outputs_prior[" + std::to_string(idx_jac)
1299 + "]=" + output_prior->str() + " rows,cols != 1,"
1300 + std::to_string(n_residuals_prior));
1301 }
1302 for (size_t col = 0; col < n_residuals_prior; ++col) {
1303 output_prior->set_value(0, col, 0);
1304 }
1305 _outputs_prior.emplace_back(std::move(output_prior));
1306 idx_jac++;
1307 }
1308 if (print) std::cout << "outputs_prior validated" << std::endl;
1309 }
1310 }
1311 if (!is_jacobian) {
1312 _outputs.reserve(size());
1313 }
1314
1315 const auto& channels = _data->get_channels();
1316
1317 if (print) std::cout << "making evaluators" << std::endl;
1318
1319 for (size_t idx = 0; idx < _size; ++idx) {
1320 auto result = this->_make_evaluator(idx, mode, outputs,
1321 has_residuals ? residuals[idx] : nullptr, print);
1322 _evaluators.emplace_back(std::move(result.first));
1323 if (is_jacobian || is_loglike_grad) {
1324 _grads.emplace_back(std::move(result.second.second));
1325 } else {
1326 _outputs.emplace_back(std::move(result.second.first));
1327 }
1328 }
1329
1330 if (print) std::cout << "evaluators made" << std::endl;
1331
1332 std::set<ParamBaseCRef> params_prior;
1333 size_t idx_prior = 0;
1334
1335 // Check that the priors are reasonable.
1336 for (const auto& prior : _priors) {
1337 auto eval_prior = prior->evaluate(is_jacobian);
1338 size_t n_resid = prior->size();
1339 size_t n_resid_eval = eval_prior.residuals.size();
1340 if (n_resid != eval_prior.residuals.size()) {
1341 throw std::logic_error(prior->str() + ".size()=" + std::to_string(n_resid)
1342 + " != evaluate(true).residuals.size()="
1343 + std::to_string(n_resid_eval));
1344 }
1345 if (is_jacobian) {
1346 size_t idx_param = 0;
1347 for (const auto& param_jacob : eval_prior.jacobians) {
1348 const auto& param = param_jacob.first;
1349 if (params_prior.find(param) != params_prior.end()) {
1351 "_priors[" + std::to_string(idx_prior) + "]=" + prior->str()
1352 + ".evaluate(true)[" + std::to_string(idx_param)
1353 + "]=" + param.get().str() + " already in prior param set."
1354 + " Did you apply multiple priors to the same parameter?");
1355 }
1356 idx_param++;
1357 }
1358 }
1359 idx_prior++;
1360 }
1361 if (print && (_priors.size() > 0)) std::cout << "priors validated" << std::endl;
1362 if (_size_priors > 0) {
1363 if ((_residuals_prior != nullptr) && (_residuals_prior->get_n_cols() != n_residuals_prior)) {
1364 throw std::invalid_argument("residuals_prior=" + _residuals_prior->str() + " n_residuals="
1365 + std::to_string(n_residuals_prior) + "!= get_n_cols()="
1366 + std::to_string(_residuals_prior->get_n_cols()));
1367 }
1368 }
1369 _is_setup = true;
1370 _mode = mode;
1371 }
1372 }
1373
1375 size_t size() const { return _size; }
1376
1377 std::string repr(bool name_keywords = false,
1378 std::string_view namespace_separator = Object::CC_NAMESPACE_SEPARATOR) const override {
1379 std::string str = type_name_str<Model>(false, namespace_separator) + "("
1380 + (name_keywords ? "data=" : "") + _data->repr(name_keywords, namespace_separator)
1381 + ", " + (name_keywords ? "psfmodels=[" : "[");
1382 for (const auto& x : _psfmodels) str += x->repr(name_keywords, namespace_separator) + ",";
1383 str += (name_keywords ? "], sources=[" : "], [");
1384 for (const auto& s : _sources) str += s->repr(name_keywords, namespace_separator) + ",";
1385 str += (name_keywords ? "], priors=[" : "], [");
1386 return str + "])";
1387 }
1388
1389 std::string str() const override {
1390 std::string str = type_name_str<Model>(true) + "(data=" + _data->str() + ", psfmodels=[";
1391 for (const auto& x : _psfmodels) str += x->str() + ",";
1392 str += "], sources=[";
1393 for (const auto& x : _sources) str += x->str() + ",";
1394 str += "], priors=[";
1395 for (const auto& x : _priors) str += x->str() + ",";
1396 return str + "])";
1397 }
1398
1412 std::vector<std::string> verify_jacobian(double findiff_frac = 1e-5, double findiff_add = 1e-5,
1413 double rtol = 1e-3, double atol = 1e-3, double max_ll_diff = 0) {
1414 if (!(max_ll_diff >= 0)) {
1415 throw std::invalid_argument("max_ll_diff=" + to_string_float(max_ll_diff) + " !> 0");
1416 }
1417 if (_mode != EvaluatorMode::jacobian) {
1419 }
1420
1421 const size_t n_obs = this->size();
1422 ParamFilter filter{false, true};
1424
1425 for (size_t idx = 0; idx < n_obs; ++idx) {
1426 const auto& grads = *(_grads.at(idx));
1427 const auto& observation = _data->at(idx).get();
1428 const auto& sigma_inv = observation.get_sigma_inverse();
1429
1430 const Channel& channel = observation.get_channel();
1431 const size_t n_cols = observation.get_n_cols();
1432 const size_t n_rows = observation.get_n_rows();
1433
1434 filter.channel = channel;
1435
1436 ParamRefs params{};
1437 this->get_parameters_observation(params, idx, &filter);
1438 params = nonconsecutive_unique(params);
1439
1440 size_t idx_param_max = 0;
1441 for (const auto& param : params) {
1442 auto found = _offsets_params.find(param);
1443 if (found == _offsets_params.end()) {
1444 throw std::runtime_error(
1445 param.get().str()
1446 + " not found in offsets; was it freed after setup_evaluators was called?");
1447 }
1448 size_t idx_param = found->second;
1449 if (idx_param > idx_param_max) idx_param_max = idx_param;
1450 }
1451
1452 if (!(idx_param_max < grads.size())) {
1453 throw std::runtime_error("idx_param_max=" + std::to_string(idx_param_max)
1454 + " !< grads.size()=" + std::to_string(grads.size())
1455 + "; is the jacobian array large enough?");
1456 }
1457
1458 double loglike_jac = this->_evaluate_observation(idx);
1459 const auto& grad = grads[0];
1460 size_t n_nonzero = 0;
1461 for (unsigned int i = 0; i < n_cols; ++i) {
1462 for (unsigned int j = 0; j < n_rows; ++j) {
1463 n_nonzero += grad.get_value(j, i) != 0;
1464 }
1465 }
1466 if (n_nonzero > 0) {
1467 errors.push_back("n_nonzero grads[0] entries=" + std::to_string(n_nonzero));
1468 }
1469
1470 auto result = _make_evaluator(idx, EvaluatorMode::loglike_image);
1471 double loglike_img = result.first->loglike_pixel();
1472 auto is_close_ll = isclose(loglike_img, loglike_jac, 0., max_ll_diff);
1473 if (!is_close_ll.isclose) {
1474 errors.push_back("loglike_jac=" + to_string_float(loglike_jac) + ", loglike_img="
1475 + to_string_float(loglike_img) + " !close (isclose=" + is_close_ll.str()
1476 + ") for obs[" + std::to_string(idx) + "]:");
1477 }
1478
1479 const auto& image_current = *(result.second.first);
1480
1481 for (auto& param_ref : params) {
1482 auto& param = param_ref.get();
1483 const size_t idx_param = _offsets_params.find(param_ref)->second;
1484 const auto& grad = grads[idx_param];
1485
1486 const double value_init = param.get_value();
1487 const double value_transformed = param.get_value_transformed();
1488 double diff = value_transformed * findiff_frac;
1489 if (std::abs(diff) < findiff_add) diff = findiff_add;
1490 diff = finite_difference_param(param, diff);
1491
1492 auto result_diff = _make_evaluator(idx, EvaluatorMode::image);
1493
1494 result_diff.first->loglike_pixel();
1495
1496 size_t n_failed = 0;
1497 std::vector<double> ratios = {};
1498 for (unsigned int i = 0; i < n_cols; ++i) {
1499 for (unsigned int j = 0; j < n_rows; ++j) {
1500 double delta = sigma_inv.get_value(j, i) / diff
1501 * (result_diff.second.first->get_value(j, i)
1502 - image_current.get_value(j, i));
1503 double jac = grad.get_value(j, i);
1504
1505 auto close = isclose(jac, delta, rtol, atol);
1506 if (!close.isclose) {
1507 n_failed++;
1508 ratios.push_back(jac / delta);
1509 }
1510 }
1511 }
1512
1513 param.set_value(value_init);
1514 const double value_new = param.get_value();
1515 if (value_new != value_init) {
1516 throw std::logic_error("Could not return param=" + param.str()
1517 + " to value_init=" + to_string_float(value_init) + "; diff="
1518 + to_string_float(value_new - value_init) + "); check limits");
1519 }
1520 if (n_failed > 0) {
1521 std::sort(ratios.begin(), ratios.end());
1522 double median = ratios[ratios.size() / 2];
1523 errors.push_back("Param[" + std::to_string(idx_param) + "]=" + param.str()
1524 + " failed for obs[" + std::to_string(idx)
1525 + "]: n_failed=" + std::to_string(n_failed)
1526 + "; median evaluated/expected=" + std::to_string(median));
1527 }
1528 }
1529 }
1530 filter.channel = std::nullopt;
1531
1532 ParamRefs params{};
1533 this->get_parameters_observation(params, 0, &filter);
1534 params = nonconsecutive_unique(params);
1535
1536 for (const auto& prior : this->_priors) {
1537 auto result_base = prior->evaluate(true);
1538 auto residuals_base = result_base.residuals;
1539 const size_t n_values = residuals_base.size();
1540
1541 for (const auto& [param_cref, values_base] : result_base.jacobians) {
1542 if (values_base.size() != n_values) {
1543 throw std::logic_error("values_base.size()=" + std::to_string(values_base.size())
1544 + " != residuals_base.size()=" + std::to_string(n_values));
1545 }
1546
1547 // TODO: Determine if it's possible to get mutable ref from set.find
1548 // (set.find only ever seems to return cref, so maybe not)
1549 auto found = std::find(params.begin(), params.end(), param_cref);
1550 if (found == params.end()) {
1551 throw std::logic_error("Couldn't find " + param_cref.get().str() + " in all params");
1552 }
1553 auto& param = (*found).get();
1554
1555 const double value_init = param.get_value();
1556 const double value_transformed = param.get_value_transformed();
1557 double delta = value_transformed * findiff_frac;
1558 if (std::abs(delta) < findiff_add) delta = findiff_add;
1559 delta = finite_difference_param(param, delta);
1560 auto result_new = prior->evaluate(true);
1561
1562 const size_t idx_param = _offsets_params.at(param);
1563
1564 std::vector<double> ratios = {};
1565 size_t n_failed = 0;
1566 size_t idx_value = 0;
1567 for (const double jac_base : values_base) {
1568 double jac_findiff
1569 = (result_new.residuals[idx_value] - result_base.residuals[idx_value]) / delta;
1570 auto close = isclose(jac_base, jac_findiff, rtol, atol);
1571 if (!close.isclose) {
1572 n_failed++;
1573 prior->evaluate(true);
1574 ratios.push_back(jac_base / jac_findiff);
1575 }
1576 idx_value++;
1577 }
1578 param.set_value(value_init);
1579 const double value_new = param.get_value();
1580 if (value_new != value_init) {
1581 throw std::logic_error("Could not return param=" + param.str()
1582 + " to value_init=" + to_string_float(value_init) + "; diff="
1583 + to_string_float(value_new - value_init) + "); check limits");
1584 }
1585 if (n_failed > 0) {
1586 std::sort(ratios.begin(), ratios.end());
1587 double median = ratios[ratios.size() / 2];
1588 errors.push_back("Param[" + std::to_string(idx_param) + "]=" + param.str()
1589 + " failed for prior=" + prior->str()
1590 + ": n_failed=" + std::to_string(n_failed)
1591 + "; median evaluated/expected=" + std::to_string(median));
1592 }
1593 }
1594 }
1595 return errors;
1596 }
1597};
1598
1599} // namespace lsst::gauss2d::fit
1600
1601#endif
py::object result
Definition _schema.cc:429
char * data
Definition BaseRecord.cc:61
std::shared_ptr< RecordT > src
Definition Match.cc:48
ArrayKeyVector arrays
T at(T... args)
T back(T... args)
T begin(T... args)
std::vector< std::shared_ptr< ConvolvedGaussian > > Data
Definition gaussian.h:251
A class that evaluates 2D Gaussians and renders them in images.
Definition evaluate.h:668
static constexpr std::string_view CC_NAMESPACE_SEPARATOR
The C++ namespace separator.
Definition object.h:45
An observational channel, usually representing some range of wavelengths of light.
Definition channel.h:29
A list of Observation instances that can be modelled.
Definition data.h:32
A collection of Sources comprising a ParametricModel of Data.
Definition model.h:78
size_t get_n_gaussians(const Channel &channel) const override
Return the number of Gaussian sub-components controlled by this model.
Definition model.h:1106
std::vector< std::pair< ParamBaseCRef, size_t > > get_offsets_parameters() const
Definition model.h:1115
std::string repr(bool name_keywords=false, std::string_view namespace_separator=Object::CC_NAMESPACE_SEPARATOR) const override
Return a full, callable string representation of this.
Definition model.h:1377
ParamCRefs & get_parameters_const(ParamCRefs &params, ParamFilter *filter=nullptr) const override
Same as get_parameters(), but for const refs.
Definition model.h:1130
void add_grad_param_factors(const Channel &channel, GradParamFactors &factors) const override
Add Parameter gradient factors to an existing map.
Definition model.h:687
Data< T, Image, Mask > ModelData
Definition model.h:83
void setup_evaluators(EvaluatorMode mode=EvaluatorMode::image, std::vector< std::vector< std::shared_ptr< Image > > > outputs={}, std::vector< std::shared_ptr< Image > > residuals={}, std::vector< std::shared_ptr< Image > > outputs_prior={}, std::shared_ptr< Image > residuals_prior=nullptr, bool force=false, bool print=false)
Setup Evaluator instances for every Observation in _data using the given EvaluatorMode.
Definition model.h:1199
ParamRefs & get_parameters(ParamRefs &params, ParamFilter *filter=nullptr) const override
Add Parameter refs matching the filter to a vector, in order.
Definition model.h:1123
Priors get_priors() const
Return _priors, the list of Prior instances.
Definition model.h:1158
void add_extra_param_factors(const Channel &channel, ExtraParamFactors &factors) const override
Add extra Parameter gradient factors to an existing vector.
Definition model.h:681
void set_grad_param_factors(const Channel &channel, GradParamFactors &factors, size_t index) const override
Set Parameter gradient factors in an existing map.
Definition model.h:1174
void add_grad_param_map(const Channel &channel, GradParamMap &map, ParameterMap &offsets) const override
Add Parameter gradient indices to an existing map.
Definition model.h:684
std::string str() const override
Return a brief, human-readable string representation of this.
Definition model.h:1389
PsfModels get_psfmodels() const
Return _psfmodels, the list of PsfModel instances for each Observation in _data.
Definition model.h:1161
ParamCRefs & get_parameters_observation_const(ParamCRefs &params, size_t idx, ParamFilter *filter=nullptr) const
Same as get_parameters_const(), but for a single Observation with index idx in _data.
Definition model.h:1148
Model(std::shared_ptr< const ModelData > data, PsfModels &psfmodels, Sources &sources, Priors &priors)
Construct a Model instance from Data, PsfModels, Sources and Priors.
Definition model.h:94
std::vector< std::string > verify_jacobian(double findiff_frac=1e-5, double findiff_add=1e-5, double rtol=1e-3, double atol=1e-3, double max_ll_diff=0)
Verify that the Jacobian is correct by comparing to finite differences.
Definition model.h:1412
GaussianEvaluator< T, Image, Indices > Evaluator
Definition model.h:80
EvaluatorMode get_mode() const
Definition model.h:1059
std::vector< std::shared_ptr< Image > > get_outputs() const
Return _outputs (output Image instances for each Observation in _data)
Definition model.h:1113
std::unique_ptr< Image > compute_hessian(bool transformed=false, bool include_prior=true, std::optional< HessianOptions > options=std::nullopt, bool print=false)
Compute the Hessian matrix (second order partial derivatives) of the log likehood.
Definition model.h:825
std::map< std::reference_wrapper< const Channel >, std::vector< std::unique_ptr< const Gaussians > > > GaussiansMap
Definition model.h:82
void set_extra_param_factors(const Channel &channel, ExtraParamFactors &factors, size_t index) const override
Set extra Parameter gradient factors in an existing map.
Definition model.h:1166
std::vector< double > evaluate(bool print=false, bool normalize_loglike=false)
Evaluate the model for every Observation in _data.
Definition model.h:1019
ModelData::Observation Observation
Definition model.h:84
std::shared_ptr< const ModelData > get_data() const
Return _data.
Definition model.h:1057
std::unique_ptr< const lsst::gauss2d::Gaussians > get_gaussians(const Channel &channel) const override
Return the vector of Gaussian sub-components controlled by this model.
Definition model.h:1061
Sources get_sources() const
Return _sources, the list of Source instances for each Observation in _data.
Definition model.h:1164
ParamRefs & get_parameters_observation(ParamRefs &params, size_t idx, ParamFilter *filter=nullptr) const
Same as get_parameters(), but for a single Observation with index idx in _data.
Definition model.h:1138
std::vector< double > compute_loglike_grad(bool include_prior=true, bool print=false, bool verify=false, double findiff_frac=1e-5, double findiff_add=1e-5, double rtol=1e-3, double atol=1e-8)
Compute the gradient (partial first derivative) of the log-likelihood for each free parameter.
Definition model.h:704
size_t size() const
Get the size of this->_data.
Definition model.h:1375
double evaluate_observation(size_t idx)
Evaluate a single observation with the given index in _data.
Definition model.h:1051
std::vector< double > get_loglike_const_terms()
Get the constant (variance-dependent) terms of the log likelihood for each observation.
Definition model.h:1079
void add_extra_param_map(const Channel &channel, ExtraParamMap &map_extra, const GradParamMap &map_grad, ParameterMap &offsets) const override
Add extra Parameter indices to a map.
Definition model.h:677
An observed single-channel image with an associated variance and mask.
Definition observation.h:35
ParamRefs get_parameters_new(ParamFilter *filter=nullptr) const
Same as get_parameters(), but returning a new vector.
Definition parametric.h:27
ParamCRefs get_parameters_const_new(ParamFilter *filter=nullptr) const
Same as get_parameters_const(), but returning a new vector.
Definition parametric.h:33
A Parametric that can manage Parameter gradients and return Gaussians.
T clear(T... args)
T copysign(T... args)
T emplace_back(T... args)
T empty(T... args)
T end(T... args)
T endl(T... args)
T expired(T... args)
T fill(T... args)
T find(T... args)
T get(T... args)
T lock(T... args)
T move(T... args)
const double LOG_1
Definition math.h:8
std::vector< std::array< double, lsst::gauss2d::N_PARAMS_GAUSS2D > > GradParamFactors
std::vector< std::shared_ptr< Prior > > Priors
Definition model.h:54
IsCloseResult< T > isclose(T a, T b, T rtol=1e-5, T atol=1e-8)
Check if two values are close, within some tolerances.
Definition util.h:48
double finite_difference_param(ParamBase &param, double delta)
Definition param_defs.cc:6
std::vector< std::array< size_t, lsst::gauss2d::N_PARAMS_GAUSS2D > > GradParamMap
std::vector< ExtraParamFactorValues > ExtraParamFactors
std::vector< std::array< size_t, lsst::gauss2d::N_EXTRA_MAP > > ExtraParamMap
EvaluatorMode
Valid modes to initialize lsst::gauss2d::GaussianEvaluator instances for.
Definition model.h:32
@ image
Compute the model images only.
@ loglike_image
Compute the model images and log likelihood.
@ loglike
Compute the log likelihood only.
@ jacobian
Compute the model Jacobian.
@ loglike_grad
Compute the gradients of the log likelihood.
std::vector< std::shared_ptr< PsfModel > > PsfModels
Definition model.h:52
std::vector< T > nonconsecutive_unique(const std::vector< T > &vec)
Definition util.h:92
Value sum_iter(const Container< Value > &container)
Definition math.h:23
std::map< ParamBaseCRef, size_t > ParameterMap
std::vector< std::shared_ptr< Source > > Sources
Definition model.h:53
void stream_iter_ref(const T &container, std::ostream &stream)
Definition object.h:177
std::string to_string_float(const T value, const int precision=6, const bool scientific=true)
Definition to_string.h:15
const size_t N_EXTRA_FACTOR
Definition evaluate.h:60
const size_t N_EXTRA_MAP
Definition evaluate.h:59
bool images_compatible(const Image< T1, C1 > &img1, const Image< T2, C2 > &img2, bool compare_coordsys=true, std::string *msg=nullptr)
Return if two images are compatible.
Definition image.h:70
const size_t N_PARAMS_GAUSS2D
Definition evaluate.h:61
STL namespace.
T push_back(T... args)
T reserve(T... args)
T resize(T... args)
T size(T... args)
T sort(T... args)
int row
Definition CR.cc:145
int col
Definition CR.cc:144
static const std::string _name
Definition parameters.h:63
Options for filtering Parameter instances.
T to_string(T... args)