Repository navigation
Return the final Newton iterate as the Laplace latent mean, and fix the solver-3 log determinant - #3438
Return the final Newton iterate as the Laplace latent mean, and fix the solver-3 log determinant#3438avehtari wants to merge 10 commits into
Conversation
2d8cec3 to
697dce2
Compare
…nt-mean-lu-logdet' into bugfix/laplace-latent-mean-lu-logdet
Jenkins Console Log Machine informationDistributor ID: Ubuntu Description: Ubuntu 20.04.3 LTS Release: 20.04 Codename: focal CPU: Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian Address sizes: 52 bits physical, 57 bits virtual CPU(s): 192 On-line CPU(s) list: 0-191 Thread(s) per core: 2 Core(s) per socket: 48 Socket(s): 2 NUMA node(s): 2 Vendor ID: AuthenticAMD CPU family: 25 Model: 17 Model name: AMD EPYC 9474F 48-Core Processor Stepping: 1 Frequency boost: enabled CPU MHz: 1497.097 CPU max MHz: 4114.4229 CPU min MHz: 1500.0000 BogoMIPS: 7189.39 Virtualization: AMD-V L1d cache: 3 MiB L1i cache: 3 MiB L2 cache: 96 MiB L3 cache: 512 MiB NUMA node0 CPU(s): 0-47,96-143 NUMA node1 CPU(s): 48-95,144-191 Vulnerability Gather data sampling: Not affected Vulnerability Indirect target selection: Not affected Vulnerability Itlb multihit: Not affected Vulnerability L1tf: Not affected Vulnerability Mds: Not affected Vulnerability Meltdown: Not affected Vulnerability Mmio stale data: Not affected Vulnerability Old microcode: Not affected Vulnerability Reg file data sampling: Not affected Vulnerability Retbleed: Not affected Vulnerability Spec rstack overflow: Mitigation; Safe RET Vulnerability Spec store bypass: Mitigation; Speculative Store Bypass disabled via prctl Vulnerability Spectre v1: Mitigation; usercopy/swapgs barriers and __user pointer sanitization Vulnerability Spectre v2: Mitigation; Enhanced / Automatic IBRS; IBPB conditional; STIBP always-on; PBRSB-eIBRS Not affected; BHI Not affected Vulnerability Srbds: Not affected Vulnerability Tsa: Mitigation; Clear CPU buffers Vulnerability Tsx async abort: Not affected Vulnerability Vmscape: Mitigation; IBPB before exit to userspace Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm constant_tsc rep_good amd_lbr_v2 nopl xtopology nonstop_tsc cpuid extd_apicid aperfmperf rapl pni pclmulqdq monitor ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c rdrand lahf_lm cmp_legacy svm extapic cr8_legacy abm sse4a misalignsse 3dnowprefetch osvw ibs skinit wdt tce topoext perfctr_core perfctr_nb bpext perfctr_llc mwaitx cpb cat_l3 cdp_l3 hw_pstate ssbd mba perfmon_v2 ibrs ibpb stibp ibrs_enhanced vmmcall fsgsbase bmi1 avx2 smep bmi2 erms invpcid cqm rdt_a avx512f avx512dq rdseed adx smap avx512ifma clflushopt clwb avx512cd sha_ni avx512bw avx512vl xsaveopt xsavec xgetbv1 xsaves cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local user_shstk avx512_bf16 clzero irperf xsaveerptr rdpru wbnoinvd amd_ppin cppc arat npt lbrv svm_lock nrip_save tsc_scale vmcb_clean flushbyasid decodeassists pausefilter pfthreshold avic v_vmsave_vmload vgif x2avic v_spec_ctrl vnmi avx512vbmi umip pku ospke avx512_vbmi2 gfni vaes vpclmulqdq avx512_vnni avx512_bitalg avx512_vpopcntdq la57 rdpid overflow_recov succor smca fsrm flush_l1d debug_swap G++: g++ (Ubuntu 9.4.0-1ubuntu1~20.04) 9.4.0 Copyright (C) 2019 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Clang: clang version 10.0.0-4ubuntu1 Target: x86_64-pc-linux-gnu Thread model: posix InstalledDir: /usr/bin |
|
An update after #3469 (embedded Laplace returns the value at the initial guess) and stan-dev/stan#3442 (the Newton optimizer's Hessian is 1e-6 too small). I ran the example of #3469 with develop (e4745c8) and with this PR (3c5e80e), This PR does not fix part 1 of #3469: the μ = −30, α ≥ 1e5 rows still return the value at θ = 0. #3469 also reports a GP-model point where develop gives NaN and this PR gives a finite value, which is again the value at θ = 0. The log determinant is correct there (det(B) > 0 with LU pivots of both signs); the wrong value comes from part 1. Before this PR, the NaN made the samplers and optimizers reject such a point. So it would be good to fix part 1 soon after this PR.
|
…n_alpha >= 1, match the log-det message shared with stan-dev#3438
|
Just a heads up, #3473 fixes a somewhat related issue and should probably me merged only after this one. (no conflict) |
Jenkins Console Log Machine informationDistributor ID: Ubuntu Description: Ubuntu 20.04.3 LTS Release: 20.04 Codename: focal CPU: Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian Address sizes: 43 bits physical, 48 bits virtual CPU(s): 256 On-line CPU(s) list: 0-255 Thread(s) per core: 2 Core(s) per socket: 64 Socket(s): 2 NUMA node(s): 2 Vendor ID: AuthenticAMD CPU family: 23 Model: 49 Model name: AMD EPYC 7742 64-Core Processor Stepping: 0 Frequency boost: enabled CPU MHz: 1497.408 CPU max MHz: 3416.0681 CPU min MHz: 1500.0000 BogoMIPS: 4491.85 Virtualization: AMD-V L1d cache: 4 MiB L1i cache: 4 MiB L2 cache: 64 MiB L3 cache: 512 MiB NUMA node0 CPU(s): 0-63,128-191 NUMA node1 CPU(s): 64-127,192-255 Vulnerability Gather data sampling: Not affected Vulnerability Indirect target selection: Not affected Vulnerability Itlb multihit: Not affected Vulnerability L1tf: Not affected Vulnerability Mds: Not affected Vulnerability Meltdown: Not affected Vulnerability Mmio stale data: Not affected Vulnerability Old microcode: Not affected Vulnerability Reg file data sampling: Not affected Vulnerability Retbleed: Mitigation; untrained return thunk; SMT enabled with STIBP protection Vulnerability Spec rstack overflow: Mitigation; Safe RET Vulnerability Spec store bypass: Mitigation; Speculative Store Bypass disabled via prctl Vulnerability Spectre v1: Mitigation; usercopy/swapgs barriers and __user pointer sanitization Vulnerability Spectre v2: Mitigation; Retpolines; IBPB conditional; STIBP always-on; RSB filling; PBRSB-eIBRS Not affected; BHI Not affected Vulnerability Srbds: Not affected Vulnerability Tsa: Not affected Vulnerability Tsx async abort: Not affected Vulnerability Vmscape: Mitigation; IBPB before exit to userspace Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc cpuid extd_apicid aperfmperf rapl pni pclmulqdq monitor ssse3 fma cx16 sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c rdrand lahf_lm cmp_legacy svm extapic cr8_legacy abm sse4a misalignsse 3dnowprefetch osvw ibs skinit wdt tce topoext perfctr_core perfctr_nb bpext perfctr_llc mwaitx cpb cat_l3 cdp_l3 hw_pstate ssbd mba ibrs ibpb stibp vmmcall fsgsbase bmi1 avx2 smep bmi2 cqm rdt_a rdseed adx smap clflushopt clwb sha_ni xsaveopt xsavec xgetbv1 xsaves cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local clzero irperf xsaveerptr rdpru wbnoinvd amd_ppin arat npt lbrv svm_lock nrip_save tsc_scale vmcb_clean flushbyasid decodeassists pausefilter pfthreshold avic v_vmsave_vmload vgif v_spec_ctrl umip rdpid overflow_recov succor smca sev sev_es G++: g++ (Ubuntu 9.4.0-1ubuntu1~20.04) 9.4.0 Copyright (C) 2019 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Clang: clang version 10.0.0-4ubuntu1 Target: x86_64-pc-linux-gnu Thread model: posix InstalledDir: /usr/bin |
A section in authoring.md (spelling, emitted Stan, priors, the do block, the options with Stan Math 5.3.0's `_tol` defaults, plate cells, the unconditioned paths, exactness for a Gaussian likelihood and the upstream `NaN` boundary, stan-dev/math#3438) and a row in the authoring-surface table. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G6HGgv4ntEaTZx1hyvsfP2
|
Action: Not Ready for Human Review Commit checked: SummaryThank you for this PR and for the detailed examples and tests. The PR says that I read the changed code at 3c5e80e and ran the PR's tests in a container without network access, at the PR head and at the merge base e4745c8 with the PR's test files copied in. The output of each run is in this gist.
Claims and EvidenceEach section below lists facts from the PR summary and discussion that an agent was able to confirm, partially confirm, or not confirm at all, or that a run of the code in the PR contradicted. Confirmed FactsFACT: The new tests fail on develop and pass with the PRQuote:
STATUS: ConfirmedAll three new tests fail at the merge base with the PR's test files copied in, and pass at the head. Evidence:The three new tests in the PR. What I ran: FACT: The other Laplace test files passQuote:
STATUS: ConfirmedAll 19 test files in Evidence:The existing test files in What I ran: FACT: Solver 3 returned NaN on develop for a Gaussian likelihoodQuote:
STATUS: ConfirmedAt the base, Evidence:Example B as the test What I read: The base log determinant at L909, and the solver-3 branch that runs directly or after a fallthrough at base L1282-L1286. What I ran: The base run of the test prints FACT: Example B, solver 3: NaN on develop, the exact value with the PRQuote:
STATUS: ConfirmedThe test compares the result with Evidence:Example B as the test What I ran: The same test runs as in the fact check "The new tests fail on develop and pass with the PR", and the R script, at the head and at the base. FACT: Example B, solvers 1 and 2: the exact value before and afterQuote:
STATUS: ConfirmedThe same test loops over solvers 1, 2, and 3. Evidence:Example B as the test What I ran: The same test runs as in the fact check "The new tests fail on develop and pass with the PR", and the R script, at the head and at the base. FACT: The latent solve and the RNGs throw the same message at a saddle pointQuote:
STATUS: Confirmed
Evidence:Example C as the test What I read: The call from What I ran: The head run of the test passes. FACT: The #3469 example gives the same values except at mu = 1, alpha = 1e9Quote:
STATUS: ConfirmedI built the example program from #3469 with the command in the issue ( Evidence:The example program in #3469, which the comment cites. What I ran: FACT: newton_step() rejects only std::domain_error and accepts +inf and NaNQuote:
STATUS: ConfirmedIn Evidence:The code of What I read: newton.hpp L53-L67 on stan-dev/stan develop. FACT: With the PR, the Newton optimizer rejects points where det(I + Sigma W) is not positiveQuote:
STATUS: ConfirmedThe new check throws Evidence:The new check in the PR, the code of What I read: The new check at L913-L923 and the What I ran: The #3469 example at the head. FACT: Example A: the posterior sd is 5.6 to 9.4 times smaller than the prior sdQuote:
STATUS: ConfirmedAt both commits, the R script prints Evidence:The R script in the PR description (section A). What I ran: The R script at the head and the base, as in the fact check above. FACT: Table, example A with tolerance 1e-6Quote:
STATUS: ConfirmedThe R script prints 0.53 sd and 7.12 lower at the base, and 0.0012 sd and 2.28e-06 lower at the head. Evidence:The R script in the PR description (section A). What I ran: The R script at the head and the base. FACT: Table, example A with the default toleranceQuote:
STATUS: ConfirmedThe R script runs the default tolerance as 1.49012e-08. Evidence:The R script in the PR description (section A). What I ran: The R script at the head and the base. FACT: Table, example C with laplace_marginal_tol(): NaN on develop, the new exception with the PRQuote:
STATUS: ConfirmedFrom the start Evidence:The R script in the PR description (section C) and the test What I ran: The R script at the head and the base, and the test runs in the fact check "The new tests fail on develop and pass with the PR". FACT: Table, example C with laplace_latent_solve_tol(): cholesky_decompose exception on developQuote:
STATUS: ConfirmedAt the base, the R script prints Evidence:The R script in the PR description (section C) and the test What I ran: The R script at the head and the base, and the head test run in the fact check "The new tests fail on develop and pass with the PR". FACT: Where the table values come fromQuote:
STATUS: ConfirmedMy base run uses CmdStan develop at e4510f7726, which reports version 2.40.0, with Stan Math at the merge base e4745c8. Evidence:The R script in the PR description. What I ran: The R script at the head and the base. FACT: Checklist: the basic tests are passingQuote:
STATUS: ConfirmedAll 34 CI checks at the reviewed commit pass, including the GitHub Actions unit test groups and the Jenkins run, which reports "This commit looks good". Evidence:The repository's unit tests, and the CI checks on the PR at the reviewed commit. What I read: The CI checks listed by What I ran: FACT: Checklist: the new changes are testedQuote:
STATUS: ConfirmedEach of the two source changes has a new test that fails at the merge base and passes at the head. Evidence:The three new tests in the PR. What I ran: The same test runs as in the fact check "The new tests fail on develop and pass with the PR", at the head and at the base. Partially Confirmed FactsFACT: Example C converges to the saddle point from (0.01, 3)Quote:
STATUS: Partially ConfirmedAt the head, solver 3 throws the Evidence:Example C as the test What I ran: The head run of the test passes. Request: If you have code that shows the iteration from FACT: Old and new means agree up to the convergence error and move toward the modeQuote:
STATUS: Partially ConfirmedIn example A, the base mean is 0.53 posterior sd from a tolerance-1e-14 solve, and the head mean is within 0.01 sd. Evidence:Example A as the test What I ran: The same test runs as in the fact check "The new tests fail on develop and pass with the PR", and the R script, at the head and at the base. Request: Could you say that this is shown for example A and the closed-form normal test, or link a broader comparison if you ran one? FACT: Where det(B) <= 0, develop returned NaN or a finite valueQuote:
STATUS: Partially ConfirmedAt the head, the saddle point of example C throws, and so does the #3469 example at mu = 1, alpha = 1e9. Evidence:Examples B and C as tests, and the example program of #3469 that your comment cites. What I read: The base log determinant at L909 and the new check at L913-L923. What I ran: The new tests at the head and the base, and the #3469 example program, built with the command in the issue and run at the head and the base. Request: Could you add +inf to the list of earlier results, as for a zero pivot in the #3469 example? FACT: NaN and an exception both reject the proposal in the model blockQuote:
STATUS: Partially ConfirmedThe generated-quantities part is shown by section C of the R script, which calls Evidence:Section C of the R script in the PR description, and the stan-dev/stan code that handles the NaN and the exception. What I read: base_hamiltonian.hpp L52-L58, base_nuts.hpp L258-L260, and mcmc_writer.hpp L108-L132 on stan-dev/stan develop at 33ebe6fc9e. What I ran: The R script at the head and the base. Request: Could you link a run for the wrong-value case, where the permutation is odd and all pivots are positive? FACT: The mu = -30, alpha >= 1e5 rows still return the value at theta = 0Quote:
STATUS: Partially ConfirmedIn the #3469 example, the head prints the same values as the base for mu = -30, alpha = 1e5, 1e7, and 1e9 (-429.759639, -431.6906935, and -436.2852909). Evidence:The example program in #3469. What I ran: The #3469 example at the head and the base. Request: Could you link the code for the value at theta = 0, or say that the run shows these rows are unchanged from develop? FACT: Before this PR, the NaN made the samplers and optimizers reject such a pointQuote:
STATUS: Partially ConfirmedThe stan-dev/stan code on develop agrees for NUTS and BFGS but not for the Newton optimizer. Evidence:The stan-dev/stan code for NUTS, BFGS, and the Newton optimizer. What I read: The three permalinks above, on stan-dev/stan develop at 33ebe6fc9e. Request: Could you say which samplers and optimizers reject a NaN, since the next paragraph says the Newton optimizer accepts it? Not Verified FactsFACT: A grid of 105 points: +inf on develop and an exception with the PR at the same 87 pointsQuote:
STATUS: Not VerifiedThe comment does not include or link the code for the grid. Evidence:There is no code for the grid. Request: Could you post the program for the grid of 105 points? FACT: A GP-model point gives NaN on develop and a finite value at theta = 0 with the PRQuote:
STATUS: Not Verified#3469 gives the point (rho = 1, mu = 1, alpha = 1e8) and the value -866.131. Evidence:There is no code for the GP model or the data. Request: Could you post the GP model and the data? Description vs CodeEach section below lists facts from the PR summary and discussion that an agent was able to confirm, partially confirm, or not confirm at all, or that a run of the code in the PR contradicted. Confirmed FactsFACT: The latent mean is now the final Newton iterateQuote:
STATUS: ConfirmedAt the base, FACT: The stopping rule bounds the change of the objectiveQuote:
STATUS: ConfirmedThe Newton loop stops when FACT: The change affects the latent solve and the latent RNGsQuote:
STATUS: Confirmed
FACT: The solver-3 log determinant uses |U_ii| and checks the signQuote:
STATUS: Confirmed
FACT: A non-positive det(B) throws std::domain_error that names the causeQuote:
STATUS: ConfirmedWhen the sign is negative or a diagonal entry is zero, the function throws FACT: Solvers 1 and 2 are not affected by the log-determinant changeQuote:
STATUS: ConfirmedIn FACT: The test for example AQuote:
STATUS: Confirmed
FACT: The test for example BQuote:
STATUS: Confirmed
FACT: The test for example CQuote:
STATUS: Confirmed
FACT: Checklist: the changes are documented in the doxygenQuote:
STATUS: ConfirmedThe doxygen of Partially Confirmed FactsFACT: The PR only makes the error at a saddle point name the causeQuote:
STATUS: Partially ConfirmedThe source diff changes two places, the mean in Request: Could the sentence say that Not Checked
|
|
Reply to @stevieb-bot. I changed the PR text and the tests (one new test, one changed test). The new error message is shorter and names a second cause. The scripts behind the numbers of the PR text and of my comment of 2026-10-18 are now in a public repository. The new head is 8cdc7b2. In this reply, ScriptsThe scripts are in https://github.com/avehtari/stan-pr-checks/tree/21a207bd2a00e9c61e3a62d0a3f71edf09afc876/pr3438. The README has one section per claim, with the commands and the expected output. The directory has:
I ran the C++ programs again on Partially confirmed and not verified facts
Description vs code
Other changes
|
Jenkins Console Log Machine informationDistributor ID: Ubuntu Description: Ubuntu 20.04.3 LTS Release: 20.04 Codename: focal CPU: Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian Address sizes: 43 bits physical, 48 bits virtual CPU(s): 256 On-line CPU(s) list: 0-255 Thread(s) per core: 2 Core(s) per socket: 64 Socket(s): 2 NUMA node(s): 2 Vendor ID: AuthenticAMD CPU family: 23 Model: 49 Model name: AMD EPYC 7742 64-Core Processor Stepping: 0 Frequency boost: enabled CPU MHz: 1496.823 CPU max MHz: 3416.0681 CPU min MHz: 1500.0000 BogoMIPS: 4491.56 Virtualization: AMD-V L1d cache: 4 MiB L1i cache: 4 MiB L2 cache: 64 MiB L3 cache: 512 MiB NUMA node0 CPU(s): 0-63,128-191 NUMA node1 CPU(s): 64-127,192-255 Vulnerability Gather data sampling: Not affected Vulnerability Indirect target selection: Not affected Vulnerability Itlb multihit: Not affected Vulnerability L1tf: Not affected Vulnerability Mds: Not affected Vulnerability Meltdown: Not affected Vulnerability Mmio stale data: Not affected Vulnerability Old microcode: Not affected Vulnerability Reg file data sampling: Not affected Vulnerability Retbleed: Mitigation; untrained return thunk; SMT enabled with STIBP protection Vulnerability Spec rstack overflow: Mitigation; Safe RET Vulnerability Spec store bypass: Mitigation; Speculative Store Bypass disabled via prctl Vulnerability Spectre v1: Mitigation; usercopy/swapgs barriers and __user pointer sanitization Vulnerability Spectre v2: Mitigation; Retpolines; IBPB conditional; STIBP always-on; RSB filling; PBRSB-eIBRS Not affected; BHI Not affected Vulnerability Srbds: Not affected Vulnerability Tsa: Not affected Vulnerability Tsx async abort: Not affected Vulnerability Vmscape: Mitigation; IBPB before exit to userspace Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc cpuid extd_apicid aperfmperf rapl pni pclmulqdq monitor ssse3 fma cx16 sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c rdrand lahf_lm cmp_legacy svm extapic cr8_legacy abm sse4a misalignsse 3dnowprefetch osvw ibs skinit wdt tce topoext perfctr_core perfctr_nb bpext perfctr_llc mwaitx cpb cat_l3 cdp_l3 hw_pstate ssbd mba ibrs ibpb stibp vmmcall fsgsbase bmi1 avx2 smep bmi2 cqm rdt_a rdseed adx smap clflushopt clwb sha_ni xsaveopt xsavec xgetbv1 xsaves cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local clzero irperf xsaveerptr rdpru wbnoinvd amd_ppin arat npt lbrv svm_lock nrip_save tsc_scale vmcb_clean flushbyasid decodeassists pausefilter pfthreshold avic v_vmsave_vmload vgif v_spec_ctrl umip rdpid overflow_recov succor smca sev sev_es G++: g++ (Ubuntu 9.4.0-1ubuntu1~20.04) 9.4.0 Copyright (C) 2019 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Clang: clang version 10.0.0-4ubuntu1 Target: x86_64-pc-linux-gnu Thread model: posix InstalledDir: /usr/bin |
EDIT: 2026-10-29 updated after revision
Summary
This PR makes two independent fixes in the embedded Laplace approximation.
This PR was Claude assisted
In this text,
developis the merge base e4745c8, unless a sentence names another version.1. The latent mean is the final Newton iterate (
stan/math/mix/functor/laplace_base_rng.hpp).laplace_base_rng()computed the mean ascovariance_train * md_est.theta_grad, that isK * grad log p(y | theta_hat). This equalstheta_hatonly at an exact mode. Before exact convergence the two differ byK * grad log p(theta | y). The stopping rule bounds only the change of the objective. The factorKmakes the difference large when the posterior is much narrower than the prior. In example A with tolerance 1e-6, the final iterate is 0.0012 posterior sd from the mode. The old mean is 0.53 sd from the mode, at a log density 7.1 lower. The mean is nowmd_est.theta. This affectslaplace_latent_solve[_tol]()and the mean of the draws oflaplace_latent[_tol]_rng()andlaplace_latent[_tol]_{bernoulli_logit,poisson_log,neg_binomial_2_log}_rng().2. The log determinant of solver 3 uses
|U_ii|and checks the sign (LUSolver::compute_log_determinant()instan/math/mix/functor/laplace_marginal_density_estimator.hpp). Solver 3 factorizes the non-symmetricB = I + K Wwith partial pivoting. The diagonal entriesU_iiofU(the pivots) can then be negative whendet(B) > 0. The oldsum(log(diag(U)))was NaN for such pivots.laplace_marginal*()with solver 3, or after a fallthrough to solver 3, then returned NaN without a message. A Gaussian likelihood can give such pivots too (example B). The log determinant is nowsum(log(abs(diag(U)))), and the sign ofdet(B)is the permutation sign times the signs of the pivots.det(B) = det(K) det(K^-1 + W)anddet(K) > 0. Thus a non-positivedet(B)means thatK^-1 + Wis not positive definite at the final iterate, for example at a saddle point. At such a point the Laplace approximation is not defined. In floating point,det(B) <= 0can also mean thatBis numerically singular, as in the example of #3469, whereKis close to singular.compute_log_determinant()then throwsstd::domain_errorwith the messagelaplace_marginal_density: det(I + Sigma * W) <= 0 (saddle point or singular), whereSigmaisK. (A positivedet(B)does not prove positive definiteness: the check finds only an odd number of negative eigenvalues ofK^-1 + W.) This function also runs inlaplace_latent_solve*()andlaplace_latent*_rng(), so they throw the same exception (example C).Examples
y ~ normal(m, sigma * m)withm(t) = bsld * (phi * exp(-d * t) + (1 - phi) * exp(g * t)). The 4 latent variables are on the log or logit scale of(bsld, d, g, phi), with diagonalK. The prior sd is 5.6 to 9.4 times the posterior sd. Call:laplace_latent_solve_tol()with solver 3. The reference mode is fromoptim()followed by Newton steps.y ~ normal(theta, s)withy = (0.3, -0.2),s = sqrt(0.1),K = [1 1.5; 1.5 4].det(B) = 226, but the pivots are(15, -15.07)because partial pivoting swaps the rows. The exact log marginal ismulti_normal_lpdf(y | 0, K + s^2 I).K = [4 1; 1 1].theta[1]has two Cauchy observationsy = (-3, 3),s = 0.3;theta[2]has one Gaussian observationz = 0,sz = 0.1. The log posterior has two maxima and a saddle point at(0, 0). From the start(0.01, 3), the Newton iteration of solver 3 ends at(1.8e-6, 5.9e-9), near the saddle point. At that iterate,K^-1 + Whas the eigenvalues -0.099 and 101.3.K = [1 -1.5; -1.5 4]andy = (-0.3 sqrt(3), 0.3 sqrt(3)). The default start(0, 0)is a saddle point withdet(B) = -577. At this point, the LU factorization swaps the rows and both pivots are positive, so the old log determinant is finite. The log posterior has two maxima of equal height. From the start(-0.3, 0), the iteration ends at the maximum(-0.3504, 0.0030), wheredet(B) > 0and one pivot is negative.developlaplace_latent_solve_tol()laplace_latent_solve_tol()laplace_marginal_tol(), solver 3laplace_marginal_tol(), solvers 1, 2laplace_marginal_tol(), solver 3laplace_latent_solve_tol(), solver 3cholesky_decomposeexception(0, 0)laplace_marginal_tol(), solver 3(-0.3, 0)laplace_marginal_tol(), solver 3For A, "sd" is the largest distance from the reference mode in posterior sd units. "Lower" is the drop in log density from the mode. For A, B and C, the
developcolumn is from CmdStan 2.40.0. The "this PR" column is from the CmdStan develop branch, built with this branch of Stan Math. A and B give the samedevelopvalues with Stan Math 43bc568. The C final iterate and the D rows are fromprobe_saddle.cppondevelopand on this branch at 3c5e80e (g++ 11.4,-O1). The C final iterate uses tolerance 1e-10 and 100 steps. The D rows use tolerance 1e-12 and 1000 steps, as in the unit test. The D reference is from mpmath at 50 digits.The scripts are in https://github.com/avehtari/stan-pr-checks/tree/21a207bd2a00e9c61e3a62d0a3f71edf09afc876/pr3438. They are the R script below,
probe_saddle.cpp(C and D) and the mpmath references inrefs/.issue3469/has the programs of my comment of 2026-10-18 in this PR.R script (cmdstanr; the Stan programs are inline)
Tests
I tested this branch merged into
developat 510d263, on Triton with g++ 14.3.0.New tests:
laplace_latent_solve.mean_is_final_iterate_tumor_size(test/unit/math/laplace/laplace_latent_solve_test.cpp), example A. With tolerance 1e-6, the returned mean must be within 0.01 posterior sd of a solve with tolerance 1e-14.laplace_marginal_tol.all_solvers_exact_with_negative_lu_pivot(new filetest/unit/math/laplace/laplace_lu_log_determinant_test.cpp), example B. The test asserts thatdet(B) > 0and thatBhas a negative pivot. Solvers 1, 2 and 3 must return the exact log marginal.laplace_marginal_tol.solver_3_saddle_point_throws(same new file), example C. From(0.01, 3),laplace_marginal_tol()andlaplace_latent_solve_tol()must throwstd::domain_errorwith the new message. From(0.3, 3),laplace_latent_solve_tol()with solver 3 must return the maximum(2.9385, 0.0097).laplace_marginal_tol.solver_3_odd_permutation_saddle_point_throws(same new file), example D. At(0, 0)the test asserts an odd permutation, two positive pivots anddet(B) < 0. From(0, 0),laplace_marginal_tol()andlaplace_latent_solve_tol()must throw. From(-0.3, 0), thelaplace_marginal_tol()value and thelaplace_latent_solve_tol()mean must match the mpmath reference within 1e-8.With the
developversions of the two changed headers, all four tests fail:developheadersmean_is_final_iterate_tumor_sizeall_solvers_exact_with_negative_lu_pivotsolver_3_saddle_point_throwscholesky_decomposeexceptionsolver_3_odd_permutation_saddle_point_throwscholesky_decomposeexception; NaNWith the changes, all 19 test files in
test/unit/math/laplace/pass (254 tests), including the completelaplace_disease_map_testsuite.clang-format10,cpplint,make test-math-dependencies,make test-headersandmake doxygenalso pass.Side Effects
laplace_latent_solve*()andlaplace_latent*_rng(): results change only where the iteration stopped before exact convergence (Summary item 1). In example A, the mean moves toward the mode. In the existing testall_solvers_match_closed_form_normal, the old and new means both match the exact posterior mean within 1e-8.laplace_marginal*()with solver 3, or after a fallthrough to solver 3: a negative pivot gave NaN. Now the function returns the log marginal (examples B and D). Wheredet(B) <= 0in floating point, it now throwsstd::domain_error. Before, it returned NaN (example C) or +inf for a zero pivot (the example of Embedded Laplace: when the first Newton step is rejected, the log density at the initial guess is returned as the log marginal (wrong by a lot) #3469 at μ = 1, α = 1e9). It returned a finite value when the permutation was odd and all pivots were positive (example D).laplace_latent_solve*()threw fromcholesky_decomposeondevelop, and now throws the newstd::domain_error.Release notes
Fixed the mean of
laplace_latent_solve()andlaplace_latent_rng(), and the NaN oflaplace_marginal()with solver 3.Checklist
Copyright holder: Aalto University
The copyright holder is typically you or your assignee, such as a university or company. By submitting this pull request, the copyright holder is agreeing to the license the submitted work under the following licenses:
- Code: BSD 3-clause (https://opensource.org/licenses/BSD-3-Clause)
- Documentation: CC-BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
the basic tests are passing
./runTests.py test/unit)make test-headers)make test-math-dependencies)make doxygen)make cpplint)the code is written in idiomatic C++ and changes are documented in the doxygen
the new changes are tested