50 const std::size_t n = cov.
rows();
51 if (n != cov.
cols()) {
52 throw std::runtime_error(
"inverse_covariance_with_ridge: covariance must be square");
55 std::vector<double> sigma(n);
57 for (std::size_t i = 0; i < n; ++i) {
58 const double vii = cov.
at(i, i);
60 if (!std::isfinite(vii) || vii <= 0.0) {
61 std::ostringstream oss;
62 oss <<
"inverse_covariance_with_ridge: non-positive variance at i="
63 << i <<
", variance=" << vii;
64 throw std::runtime_error(oss.str());
67 sigma[i] = std::sqrt(vii);
72 for (std::size_t i = 0; i < n; ++i) {
73 for (std::size_t j = 0; j < n; ++j) {
74 corr.
at(i, j) = cov.
at(i, j) / (sigma[i] * sigma[j]);
81 const double ridge = std::max(ridge_rel, ridge_abs);
83 for (std::size_t i = 0; i < n; ++i) {
84 corr.
at(i, i) += ridge;
93 for (std::size_t i = 0; i < n; ++i) {
94 for (std::size_t j = 0; j < n; ++j) {
95 cov_inv.
at(i, j) = corr_inv.
at(i, j) / (sigma[i] * sigma[j]);
104 const std::vector<BinnedObservableId>& ids,
109 throw std::invalid_argument(
"covariance_from_obs_samples: no samples");
112 throw std::invalid_argument(
"covariance_from_obs_samples: no observable ids");
115 throw std::invalid_argument(
"covariance_from_obs_samples: need at least two samples");
118 const std::size_t N = S.size();
119 const std::size_t D = ids.size();
121 std::vector<double> mean(D, 0.0);
122 for (
const auto& row : S) {
123 for (std::size_t d = 0; d < D; ++d) {
124 mean[d] += row.at(ids[d]);
127 for (
double& v : mean) {
128 v /=
static_cast<double>(N);
132 for (std::size_t i = 0; i < D; ++i) {
133 for (std::size_t j = 0; j < D; ++j) {
135 for (
const auto& row : S) {
136 s += (row.at(ids[i]) - mean[i]) * (row.at(ids[j]) - mean[j]);
138 cov.
at(i, j) = s /
static_cast<double>(N - 1);
143 for (std::size_t i = 0; i < D; ++i) {
144 for (std::size_t j = i + 1; j < D; ++j) {
145 const double v = 0.5 * (cov.
at(i, j) + cov.
at(j, i));
181 out.reserve(cfg_.
draws);
184 accepted_samples.reserve(cfg_.
draws);
186 std::size_t accepted = 0;
187 std::size_t failures = 0;
188 std::size_t attempts = 0;
201 model_->prepare_for_prediction();
203 while (accepted < cfg_.
draws) {
206 std::map<ParamId, double> s = sampler_.
sample();
209 auto res = model_->predict_optimized(p, s);
210 auto unzipped_res =
flatten(res);
211 std::map<BinnedObservableId, double> value =
212 zip(unzipped_res.ids, unzipped_res.vals);
215 for (
const auto& [oid, v] : value) {
216 if (!std::isfinite(v)) {
223 throw std::runtime_error(
"MC prediction contains non-finite observable");
226 out.emplace_back(std::move(value));
227 accepted_samples.emplace_back(std::move(s));
229 progress.
accepted(accepted, attempts, failures);
231 }
catch (
const std::exception& e) {
235 "Rejected MC nuisance sample",
237 "while trying to fill accepted sample",
255 "Rejected MC nuisance sample",
257 "with unknown exception while trying to fill accepted sample",
272 progress.
finish(accepted, attempts, failures);
276 "MC sampling finished with",
278 "rejected nuisance samples over",
305 const std::size_t n_workers = std::max<std::size_t>(1, std::min(cfg_.
n_threads, cfg_.
draws));
307 std::unique_ptr<IModelThreadGuard> decay_thread_guard;
312 struct WorkerOutput {
315 std::size_t failures = 0;
316 std::size_t attempts = 0;
319 std::vector<WorkerOutput> worker_outputs(n_workers);
320 std::vector<std::thread> workers;
321 workers.reserve(n_workers);
323 std::mutex sampler_mutex;
324 std::mutex progress_mutex;
325 std::mutex exception_mutex;
326 std::exception_ptr first_exception =
nullptr;
328 std::atomic<std::size_t> accepted_total {0};
329 std::atomic<std::size_t> failures_total {0};
330 std::atomic<std::size_t> attempts_total {0};
331 std::atomic<bool> stop {
false};
345 auto set_exception_once = [&](std::exception_ptr eptr) {
346 std::lock_guard<std::mutex> lock(exception_mutex);
347 if (!first_exception) {
348 first_exception = eptr;
350 stop.store(
true, std::memory_order_release);
353 const std::size_t base_target = cfg_.
draws / n_workers;
354 const std::size_t remainder = cfg_.
draws % n_workers;
356 for (std::size_t worker_id = 0; worker_id < n_workers; ++worker_id) {
357 const std::size_t target = base_target + (worker_id < remainder ? 1 : 0);
359 workers.emplace_back([&, worker_id, target]() {
360 auto& local = worker_outputs[worker_id];
361 local.obss.reserve(target);
362 local.params.reserve(target);
368 auto worker_model = model_->clone_for_worker();
370 throw std::runtime_error(
"MC worker could not clone model");
373 std::unique_ptr<IModelThreadGuard> worker_decay_thread_guard;
378 worker_model->prepare_for_prediction();
380 while (local.obss.size() < target && !stop.load(std::memory_order_acquire)) {
384 std::map<ParamId, double> s;
386 std::lock_guard<std::mutex> lock(sampler_mutex);
391 auto res = worker_model->predict_optimized(p, s);
392 auto unzipped_res =
flatten(res);
393 std::map<BinnedObservableId, double> value =
394 zip(unzipped_res.ids, unzipped_res.vals);
397 for (
const auto& [oid, v] : value) {
398 if (!std::isfinite(v)) {
405 throw std::runtime_error(
"MC prediction contains non-finite observable");
408 local.obss.emplace_back(std::move(value));
409 local.params.emplace_back(std::move(s));
411 const std::size_t accepted_now =
412 accepted_total.fetch_add(1, std::memory_order_acq_rel) + 1;
414 std::lock_guard<std::mutex> lock(progress_mutex);
415 progress.
accepted(accepted_now, attempts_total.load(), failures_total.load());
417 }
catch (
const std::exception& e) {
419 const std::size_t failures_now =
420 failures_total.fetch_add(1, std::memory_order_acq_rel) + 1;
423 "Rejected MC nuisance sample",
427 "while trying to fill local accepted sample",
428 local.obss.size() + 1,
437 set_exception_once(std::current_exception());
442 const std::size_t failures_now =
443 failures_total.fetch_add(1, std::memory_order_acq_rel) + 1;
446 "Rejected MC nuisance sample",
448 "with unknown exception in worker",
454 set_exception_once(std::current_exception());
460 set_exception_once(std::current_exception());
465 for (
auto& worker : workers) {
466 if (worker.joinable()) {
471 if (first_exception) {
472 std::rethrow_exception(first_exception);
476 out.reserve(cfg_.
draws);
478 accepted_samples.reserve(cfg_.
draws);
480 for (
auto& local : worker_outputs) {
481 for (
auto& row : local.obss) {
482 out.emplace_back(std::move(row));
484 for (
auto& row : local.params) {
485 accepted_samples.emplace_back(std::move(row));
489 progress.
finish(accepted_total.load(), attempts_total.load(), failures_total.load());
491 if (failures_total.load() > 0) {
493 "MC sampling finished with",
494 failures_total.load(),
495 "rejected nuisance samples over",
496 attempts_total.load(),
510 throw std::runtime_error(
515 const auto write_csv_field = [&fs](
const std::string& field) {
517 for (
const char c : field) {
518 if (c ==
'"') fs <<
'"';
525 for (
const auto& [oid, value] : smpl.sampled_obss.front()) {
526 static_cast<void>(value);
527 if (!first) fs <<
',';
528 write_csv_field(oid.str());
533 fs << std::setprecision(std::numeric_limits<double>::max_digits10);
534 for (
const auto& observable_values : smpl.sampled_obss) {
536 for (
const auto& [oid, value] : observable_values) {
537 static_cast<void>(oid);
538 if (!first) fs <<
',';
545 throw std::runtime_error(
561 return MCResult {smpl, summary, covariance};
Per-thread parameter runtime used to isolate Monte-Carlo workers.
RAII installer for a ParameterRuntimeContext on the current thread.