19using Vec = std::vector<double>;
20using Mat = std::vector<Vec>;
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");
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];
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");
42 Vec out(a.size(), 0.0);
43 for (std::size_t i = 0; i < a.size(); ++i) {
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");
54 Vec out(a.size(), 0.0);
55 for (std::size_t i = 0; i < a.size(); ++i) {
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");
67 for (std::size_t i = 0; i < a.size(); ++i) {
73static Mat transpose_std(
const Mat& A) {
74 if (A.empty())
return {};
76 Mat T(A[0].size(),
Vec(A.size(), 0.0));
78 for (std::size_t i = 0; i < A.size(); ++i) {
79 for (std::size_t j = 0; j < A[0].size(); ++j) {
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");
93 Mat C(A.size(),
Vec(
B[0].size(), 0.0));
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];
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");
113 for (std::size_t i = 0; i < A.size(); ++i) {
114 for (std::size_t j = 0; j < A[0].size(); ++j) {
122static Vec solve_linear_std(
Mat A,
Vec b) {
123 const std::size_t
n = b.size();
125 if (A.size() != n || A[0].size() != n) {
126 throw std::runtime_error(
"solve_linear_std: matrix must be square");
129 for (std::size_t col = 0; col <
n; ++col) {
130 std::size_t pivot = col;
131 double best = std::abs(A[col][col]);
133 for (std::size_t row = col + 1; row <
n; ++row) {
134 const double candidate = std::abs(A[row][col]);
135 if (candidate > best) {
142 throw std::runtime_error(
"solve_linear_std: singular matrix");
146 std::swap(A[pivot], A[col]);
147 std::swap(b[pivot], b[col]);
150 const double diag = A[col][col];
152 for (std::size_t j = col; j <
n; ++j) {
157 for (std::size_t row = 0; row <
n; ++row) {
158 if (row == col)
continue;
160 const double factor = A[row][col];
162 for (std::size_t j = col; j <
n; ++j) {
163 A[row][j] -= factor * A[col][j];
165 b[row] -= factor * b[col];
172static RealMatrix identity_real_matrix(std::size_t n) {
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;
184static std::unique_ptr<JointDistribution> make_independent_gaussian_joint(
188 if (
means.size() != sigmas.size()) {
189 throw std::runtime_error(
"make_independent_gaussian_joint: dimension mismatch");
192 std::vector<std::unique_ptr<IMarginalDistribution>> marginals;
193 marginals.reserve(
means.size());
195 for (std::size_t i = 0; i <
means.size(); ++i) {
196 marginals.emplace_back(
197 std::make_unique<GaussianMarginal>(means[i], sigmas[i])
203 std::unique_ptr<ICopula> copula =
204 std::make_unique<GaussianCopula>(std::random_device{}(), R);
206 return std::make_unique<JointDistribution>(
207 std::move(marginals),
213 const std::string& name,
249 return add_std(matvec_std(
A, p), matvec_std(
B, eta));
254 for (std::size_t i = 0; i <
obs_sigmas.size(); ++i) {
262 for (std::size_t i = 0; i <
eta_sigmas.size(); ++i) {
270 matmul_std(matmul_std(transpose_std(
B),
W_obs_std()),
B),
277 return matvec_std(transpose_std(
B), matvec_std(
W_obs_std(), r0));
283 const Vec Hinv_g = solve_linear_std(H,
g);
286 for (std::size_t i = 0; i < out.size(); ++i) {
299 const Vec Hinv_g = solve_linear_std(H,
g);
300 return nll0_from_base - 0.5 * dot_std(
g, Hinv_g);
304static std::shared_ptr<BaseLikelihood> build_test_likelihood(
307 auto ctx = std::make_shared<LikelihoodContext>();
309 ctx->exp_obs_values = kgm.
y;
311 ctx->exp_obs_dist = make_independent_gaussian_joint(
312 Vec(kgm.
y.size(), 0.0),
316 ctx->nuisance_dist = make_independent_gaussian_joint(
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));
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]));
332 return std::make_shared<BaseLikelihood>(model_fn, ctx, 2);
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");
342 for (std::size_t i = 0; i < a.size(); ++i) {
343 out = std::max(out, std::abs(a[i] - b[i]));
349int main(
int argc,
char** argv) {
350 std::string csv_name =
"classes_laplace_contour.csv";
356 std::shared_ptr<BaseLikelihood> like = build_test_likelihood(kgm);
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;
366 std::ofstream csv(csv_name);
368 throw std::runtime_error(
"Could not open output CSV");
371 csv << std::setprecision(17);
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";
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;
386 for (
int i = 0; i < n0; ++i) {
388 p0_min + (p0_max - p0_min) *
static_cast<double>(i) /
static_cast<double>(n0 - 1);
390 for (
int j = 0; j < n1; ++j) {
392 p1_min + (p1_max - p1_min) *
static_cast<double>(j) /
static_cast<double>(n1 - 1);
394 const Vec p = {p0, p1};
398 const double exact_profiled =
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);
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);
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);
415 csv << p0 <<
"," << p1 <<
","
416 << nll0_from_base <<
","
417 << lap.
nll_hat <<
"," << exact_profiled <<
"," << diff_profiled <<
","
419 << exact_eta[0] <<
"," << exact_eta[1] <<
"," << exact_eta[2] <<
","
421 << direct_lap <<
"," << direct_exact <<
"\n";
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";
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";
436 std::cout <<
"[OK] BaseLikelihood + JointDistribution + GradientHelper match the exact reference.\n";
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.
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.
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