Hyperiso 1.0.3
Modular flavour-physics calculations, Wilson coefficients and statistical inference
Loading...
Searching...
No Matches
test_contour_method.cpp
Go to the documentation of this file.
1
2#include <algorithm>
3#include <cmath>
4#include <cstdlib>
5#include <fstream>
6#include <iomanip>
7#include <iostream>
8#include <memory>
9#include <random>
10#include <stdexcept>
11#include <string>
12#include <vector>
13
14#include "BaseLikelihood.h"
15#include "GradientHelper.h"
16#include "GaussianCopula.h"
17#include "GaussianMarginal.h"
18
19using Vec = std::vector<double>;
20using Mat = std::vector<Vec>;
21
22static Vec matvec_std(const Mat& A, const Vec& x) {
23 if (A.empty()) return {};
24 if (A[0].size() != x.size()) {
25 throw std::runtime_error("matvec_std: dimension mismatch");
26 }
27
28 Vec out(A.size(), 0.0);
29 for (std::size_t i = 0; i < A.size(); ++i) {
30 for (std::size_t j = 0; j < x.size(); ++j) {
31 out[i] += A[i][j] * x[j];
32 }
33 }
34 return out;
35}
36
37static Vec add_std(const Vec& a, const Vec& b) {
38 if (a.size() != b.size()) {
39 throw std::runtime_error("add_std: dimension mismatch");
40 }
41
42 Vec out(a.size(), 0.0);
43 for (std::size_t i = 0; i < a.size(); ++i) {
44 out[i] = a[i] + b[i];
45 }
46 return out;
47}
48
49static Vec sub_std(const Vec& a, const Vec& b) {
50 if (a.size() != b.size()) {
51 throw std::runtime_error("sub_std: dimension mismatch");
52 }
53
54 Vec out(a.size(), 0.0);
55 for (std::size_t i = 0; i < a.size(); ++i) {
56 out[i] = a[i] - b[i];
57 }
58 return out;
59}
60
61static double dot_std(const Vec& a, const Vec& b) {
62 if (a.size() != b.size()) {
63 throw std::runtime_error("dot_std: dimension mismatch");
64 }
65
66 double out = 0.0;
67 for (std::size_t i = 0; i < a.size(); ++i) {
68 out += a[i] * b[i];
69 }
70 return out;
71}
72
73static Mat transpose_std(const Mat& A) {
74 if (A.empty()) return {};
75
76 Mat T(A[0].size(), Vec(A.size(), 0.0));
77
78 for (std::size_t i = 0; i < A.size(); ++i) {
79 for (std::size_t j = 0; j < A[0].size(); ++j) {
80 T[j][i] = A[i][j];
81 }
82 }
83
84 return T;
85}
86
87static Mat matmul_std(const Mat& A, const Mat& B) {
88 if (A.empty() || B.empty()) return {};
89 if (A[0].size() != B.size()) {
90 throw std::runtime_error("matmul_std: dimension mismatch");
91 }
92
93 Mat C(A.size(), Vec(B[0].size(), 0.0));
94
95 for (std::size_t i = 0; i < A.size(); ++i) {
96 for (std::size_t k = 0; k < B.size(); ++k) {
97 for (std::size_t j = 0; j < B[0].size(); ++j) {
98 C[i][j] += A[i][k] * B[k][j];
99 }
100 }
101 }
102
103 return C;
104}
105
106static Mat matadd_std(const Mat& A, const Mat& B) {
107 if (A.size() != B.size() || A[0].size() != B[0].size()) {
108 throw std::runtime_error("matadd_std: dimension mismatch");
109 }
110
111 Mat C = A;
112
113 for (std::size_t i = 0; i < A.size(); ++i) {
114 for (std::size_t j = 0; j < A[0].size(); ++j) {
115 C[i][j] += B[i][j];
116 }
117 }
118
119 return C;
120}
121
122static Vec solve_linear_std(Mat A, Vec b) {
123 const std::size_t n = b.size();
124
125 if (A.size() != n || A[0].size() != n) {
126 throw std::runtime_error("solve_linear_std: matrix must be square");
127 }
128
129 for (std::size_t col = 0; col < n; ++col) {
130 std::size_t pivot = col;
131 double best = std::abs(A[col][col]);
132
133 for (std::size_t row = col + 1; row < n; ++row) {
134 const double candidate = std::abs(A[row][col]);
135 if (candidate > best) {
136 best = candidate;
137 pivot = row;
138 }
139 }
140
141 if (best < 1e-14) {
142 throw std::runtime_error("solve_linear_std: singular matrix");
143 }
144
145 if (pivot != col) {
146 std::swap(A[pivot], A[col]);
147 std::swap(b[pivot], b[col]);
148 }
149
150 const double diag = A[col][col];
151
152 for (std::size_t j = col; j < n; ++j) {
153 A[col][j] /= diag;
154 }
155 b[col] /= diag;
156
157 for (std::size_t row = 0; row < n; ++row) {
158 if (row == col) continue;
159
160 const double factor = A[row][col];
161
162 for (std::size_t j = col; j < n; ++j) {
163 A[row][j] -= factor * A[col][j];
164 }
165 b[row] -= factor * b[col];
166 }
167 }
168
169 return b;
170}
171
172static RealMatrix identity_real_matrix(std::size_t n) {
173 RealMatrix R(n, n);
174
175 for (std::size_t i = 0; i < n; ++i) {
176 for (std::size_t j = 0; j < n; ++j) {
177 R.at(i, j) = (i == j) ? 1.0 : 0.0;
178 }
179 }
180
181 return R;
182}
183
184static std::unique_ptr<JointDistribution> make_independent_gaussian_joint(
185 const Vec& means,
186 const Vec& sigmas
187) {
188 if (means.size() != sigmas.size()) {
189 throw std::runtime_error("make_independent_gaussian_joint: dimension mismatch");
190 }
191
192 std::vector<std::unique_ptr<IMarginalDistribution>> marginals;
193 marginals.reserve(means.size());
194
195 for (std::size_t i = 0; i < means.size(); ++i) {
196 marginals.emplace_back(
197 std::make_unique<GaussianMarginal>(means[i], sigmas[i])
198 );
199 }
200
201 RealMatrix R = identity_real_matrix(means.size());
202
203 std::unique_ptr<ICopula> copula =
204 std::make_unique<GaussianCopula>(std::random_device{}(), R);
205
206 return std::make_unique<JointDistribution>(
207 std::move(marginals),
208 std::move(copula)
209 );
210}
211
212static fit_app::ParameterDefinition make_param_def(
213 const std::string& name,
214 double value,
215 double step_hint
216) {
218 def.name = name;
219 def.value = value;
220 def.step_hint = step_hint;
221 def.fixed = false;
222 return def;
223}
224
226 Mat A = {
227 {1.0, 2.0},
228 {0.5, -1.0},
229 {1.5, 0.0},
230 {0.0, 1.0}
231 };
232
233 Mat B = {
234 {1.0, 0.0, 0.5},
235 {0.0, 1.0, -0.2},
236 {0.3, -0.4, 1.0},
237 {1.0, 0.2, 0.0}
238 };
239
240 Vec y = {1.0, -0.5, 0.7, 0.2};
241
242 Vec p0 = {0.0, 0.0};
243 Vec eta0 = {0.1, -0.2, 0.05};
244
245 Vec obs_sigmas = {0.5, 1.2, 0.8, 1.5};
246 Vec eta_sigmas = {1.0, 0.7, 1.3};
247
248 Vec predict(const Vec& p, const Vec& eta) const {
249 return add_std(matvec_std(A, p), matvec_std(B, eta));
250 }
251
252 Mat W_obs_std() const {
253 Mat W(obs_sigmas.size(), Vec(obs_sigmas.size(), 0.0));
254 for (std::size_t i = 0; i < obs_sigmas.size(); ++i) {
255 W[i][i] = 1.0 / (obs_sigmas[i] * obs_sigmas[i]);
256 }
257 return W;
258 }
259
260 Mat W_eta_std() const {
261 Mat W(eta_sigmas.size(), Vec(eta_sigmas.size(), 0.0));
262 for (std::size_t i = 0; i < eta_sigmas.size(); ++i) {
263 W[i][i] = 1.0 / (eta_sigmas[i] * eta_sigmas[i]);
264 }
265 return W;
266 }
267
268 Mat exact_H_eta() const {
269 return matadd_std(
270 matmul_std(matmul_std(transpose_std(B), W_obs_std()), B),
271 W_eta_std()
272 );
273 }
274
275 Vec exact_g_eta(const Vec& p) const {
276 const Vec r0 = sub_std(predict(p, eta0), y);
277 return matvec_std(transpose_std(B), matvec_std(W_obs_std(), r0));
278 }
279
280 Vec exact_eta_hat(const Vec& p) const {
281 const Mat H = exact_H_eta();
282 const Vec g = exact_g_eta(p);
283 const Vec Hinv_g = solve_linear_std(H, g);
284
285 Vec out = eta0;
286 for (std::size_t i = 0; i < out.size(); ++i) {
287 out[i] -= Hinv_g[i];
288 }
289 return out;
290 }
291
293 const IProfileableLikelihood& like,
294 const Vec& p
295 ) const {
296 const double nll0_from_base = like.nll_from_split(p, eta0);
297 const Mat H = exact_H_eta();
298 const Vec g = exact_g_eta(p);
299 const Vec Hinv_g = solve_linear_std(H, g);
300 return nll0_from_base - 0.5 * dot_std(g, Hinv_g);
301 }
302};
303
304static std::shared_ptr<BaseLikelihood> build_test_likelihood(
305 const KnownLinearGaussianModel& kgm
306) {
307 auto ctx = std::make_shared<LikelihoodContext>();
308
309 ctx->exp_obs_values = kgm.y;
310
311 ctx->exp_obs_dist = make_independent_gaussian_joint(
312 Vec(kgm.y.size(), 0.0),
313 kgm.obs_sigmas
314 );
315
316 ctx->nuisance_dist = make_independent_gaussian_joint(
317 kgm.eta0,
318 kgm.eta_sigmas
319 );
320
321 ctx->fp_defs.push_back(make_param_def("p0", kgm.p0[0], 1e-2));
322 ctx->fp_defs.push_back(make_param_def("p1", kgm.p0[1], 1e-2));
323
324 ctx->nuis_defs.push_back(make_param_def("eta0", kgm.eta0[0], kgm.eta_sigmas[0]));
325 ctx->nuis_defs.push_back(make_param_def("eta1", kgm.eta0[1], kgm.eta_sigmas[1]));
326 ctx->nuis_defs.push_back(make_param_def("eta2", kgm.eta0[2], kgm.eta_sigmas[2]));
327
328 ModelFn model_fn = [kgm](const Vec& p, const Vec& eta) {
329 return kgm.predict(p, eta);
330 };
331
332 return std::make_shared<BaseLikelihood>(model_fn, ctx, 2);
333}
334
335static double max_abs_diff(const Vec& a, const Vec& b) {
336 if (a.size() != b.size()) {
337 throw std::runtime_error("max_abs_diff: dimension mismatch");
338 }
339
340 double out = 0.0;
341
342 for (std::size_t i = 0; i < a.size(); ++i) {
343 out = std::max(out, std::abs(a[i] - b[i]));
344 }
345
346 return out;
347}
348
349int main(int argc, char** argv) {
350 std::string csv_name = "classes_laplace_contour.csv";
351 if (argc >= 2) {
352 csv_name = argv[1];
353 }
354
356 std::shared_ptr<BaseLikelihood> like = build_test_likelihood(kgm);
357
358 const int n0 = 161;
359 const int n1 = 161;
360
361 const double p0_min = -1.5;
362 const double p0_max = 1.5;
363 const double p1_min = -1.5;
364 const double p1_max = 1.5;
365
366 std::ofstream csv(csv_name);
367 if (!csv) {
368 throw std::runtime_error("Could not open output CSV");
369 }
370
371 csv << std::setprecision(17);
372
373 csv << "p0,p1,"
374 << "nll0_from_base,"
375 << "profiled_laplace_classes,profiled_exact,diff_profiled,"
376 << "eta0_laplace,eta1_laplace,eta2_laplace,"
377 << "eta0_exact,eta1_exact,eta2_exact,"
378 << "eta_max_abs_diff,"
379 << "direct_nll_at_eta_laplace,direct_nll_at_eta_exact\n";
380
381 double min_laplace = std::numeric_limits<double>::infinity();
382 double min_exact = std::numeric_limits<double>::infinity();
383 double max_profile_diff = 0.0;
384 double max_eta_diff = 0.0;
385
386 for (int i = 0; i < n0; ++i) {
387 const double p0 =
388 p0_min + (p0_max - p0_min) * static_cast<double>(i) / static_cast<double>(n0 - 1);
389
390 for (int j = 0; j < n1; ++j) {
391 const double p1 =
392 p1_min + (p1_max - p1_min) * static_cast<double>(j) / static_cast<double>(n1 - 1);
393
394 const Vec p = {p0, p1};
395
396 const LaplaceProfileComputation lap = laplace_profile_eta(*like, p);
397
398 const double exact_profiled =
400
401 const Vec exact_eta = kgm.exact_eta_hat(p);
402
403 const double nll0_from_base = like->nll_from_split(p, kgm.eta0);
404 const double diff_profiled = lap.nll_hat - exact_profiled;
405 const double eta_diff = max_abs_diff(lap.eta_hat, exact_eta);
406
407 const double direct_lap = like->nll_from_split(p, lap.eta_hat);
408 const double direct_exact = like->nll_from_split(p, exact_eta);
409
410 min_laplace = std::min(min_laplace, lap.nll_hat);
411 min_exact = std::min(min_exact, exact_profiled);
412 max_profile_diff = std::max(max_profile_diff, std::abs(diff_profiled));
413 max_eta_diff = std::max(max_eta_diff, eta_diff);
414
415 csv << p0 << "," << p1 << ","
416 << nll0_from_base << ","
417 << lap.nll_hat << "," << exact_profiled << "," << diff_profiled << ","
418 << lap.eta_hat[0] << "," << lap.eta_hat[1] << "," << lap.eta_hat[2] << ","
419 << exact_eta[0] << "," << exact_eta[1] << "," << exact_eta[2] << ","
420 << eta_diff << ","
421 << direct_lap << "," << direct_exact << "\n";
422 }
423 }
424
425 std::cout << "Wrote: " << csv_name << "\n";
426 std::cout << "min_laplace=" << std::setprecision(17) << min_laplace << "\n";
427 std::cout << "min_exact=" << std::setprecision(17) << min_exact << "\n";
428 std::cout << "max_abs_diff_profiled=" << max_profile_diff << "\n";
429 std::cout << "max_abs_diff_eta=" << max_eta_diff << "\n";
430
431 if (max_profile_diff > 1e-6 || max_eta_diff > 1e-6) {
432 std::cerr << "[FAIL] Laplace profiling through project classes does not match exact reference.\n";
433 return 1;
434 }
435
436 std::cout << "[OK] BaseLikelihood + JointDistribution + GradientHelper match the exact reference.\n";
437 return 0;
438}
Concrete profileable likelihood built from a model and joint distributions.
std::function< std::vector< double >(const std::vector< double > &p, const std::vector< double > &eta)> ModelFn
Model function signature used by BaseLikelihood.
Gaussian copula implementation.
Numerical derivative and Laplace profiling helpers for nuisance parameters.
RealMatrix diag(const gsl_vector *X)
Builds a diagonal matrix from a GSL vector.
Definition Matrix.cpp:464
Extension of ILikelihood with explicit parameter-block access.
virtual double nll_from_split(const std::vector< double > &p, const std::vector< double > &eta) const =0
Evaluates the negative log-likelihood from split parameters.
constexpr double g
double T(double x)
Wilson coefficient T(x).
double exact_profiled_nll_using_base_constant(const IProfileableLikelihood &like, const Vec &p) const
Vec exact_g_eta(const Vec &p) const
Vec exact_eta_hat(const Vec &p) const
Vec predict(const Vec &p, const Vec &eta) const
Result of a Laplace nuisance-profile computation.
double nll_hat
Profiled or approximate profiled NLL value.
std::vector< double > eta_hat
Estimated profiled nuisance vector.
std::vector< double > Vec
std::vector< Vec > Mat