Skip to content

Conversation

@mhovd
Copy link
Collaborator

@mhovd mhovd commented Dec 30, 2025

We should only calculate and use the log likelihood for numerical stability

Copilot AI review requested due to automatic review settings December 30, 2025 22:40
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR removes the regular likelihood calculation API in favor of using only log-likelihood for numerical stability. The change affects the core Equation trait by removing the estimate_likelihood method and standardizing on estimate_log_likelihood. This improves numerical stability when dealing with many observations or extreme parameter values that could cause regular likelihoods to underflow to zero.

Key changes:

  • Removed estimate_likelihood method from the Equation trait and all implementations
  • Removed helper functions for regular likelihood calculation (normpdf, normcdf, likelihood methods)
  • Updated the psi function to compute log-likelihoods directly (previously named log_psi)
  • Modified optimizer and test code to work with log-likelihoods

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/simulator/likelihood/mod.rs Removed regular likelihood methods, kept only log-likelihood implementations; removed psi function that returned likelihoods and renamed log_psi to psi
src/simulator/equation/mod.rs Removed estimate_likelihood from Equation trait; updated default simulate_subject to sum log-likelihoods
src/simulator/equation/ode/mod.rs Removed estimate_likelihood implementation and helper function; updated to push log-likelihoods in simulate_subject
src/simulator/equation/analytical/mod.rs Removed estimate_likelihood implementation and helper function; updated to push log-likelihoods
src/simulator/equation/sde/mod.rs Updated particle filter to compute log-likelihoods; modified estimate_log_likelihood to take log of final result
src/optimize/spp.rs Updated optimizer to work with log-likelihoods using exp(log_psi - log_pyl) for likelihood ratios
tests/ode_optimizations.rs Updated tests to compare log-likelihoods using absolute differences instead of relative differences
examples/pf.rs Updated example to call estimate_log_likelihood instead of estimate_likelihood
examples/one_compartment.rs Updated example to display log-likelihoods with adjusted formatting
src/lib.rs Updated public API exports to only expose psi (which now returns log-likelihoods)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +331 to +333
// Push the average likelihood (in regular space) for final computation
// log(mean(likelihood)) = log(sum(exp(log_lik))) - log(n)
likelihood.push((log_sum_q - (self.nparticles as f64).ln()).exp());
Copy link

Copilot AI Dec 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SDE implementation is computing regular likelihoods (not log-likelihoods) in the particle filter, but then these values are being summed in the default simulate_subject method (line 264 in mod.rs). This is mathematically incorrect. Likelihoods should be multiplied (product) across observations, not summed. Since you're working in regular likelihood space here, you should either: (1) change line 333 to push log-likelihoods and keep the sum, or (2) override simulate_subject for SDE to compute the product instead of sum, then take the log in estimate_log_likelihood. The first approach is recommended for consistency with ODE and Analytical implementations.

Suggested change
// Push the average likelihood (in regular space) for final computation
// log(mean(likelihood)) = log(sum(exp(log_lik))) - log(n)
likelihood.push((log_sum_q - (self.nparticles as f64).ln()).exp());
// Push the average log-likelihood for final computation
// log(mean(likelihood)) = log(sum(exp(log_lik))) - log(n)
likelihood.push(log_sum_q - (self.nparticles as f64).ln());

Copilot uses AI. Check for mistakes.
}
}
let ll = error_models.map(|_| likelihood.iter().product::<f64>());
// Return log-likelihood (sum of log-likelihoods) when error_models is provided
Copy link

Copilot AI Dec 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The inline comment correctly describes returning log-likelihood, which matches the implementation. However, the function's documentation comment at line 231 (outside the changed region) still says "optional likelihood" and should be updated in a follow-up to say "optional log-likelihood" for consistency.

Copilot uses AI. Check for mistakes.
let lik = if cache {
_estimate_likelihood(self, subject, support_point, error_models)
} else {
_estimate_likelihood_no_cache(self, subject, support_point, error_models)
Copy link

Copilot AI Dec 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function _estimate_likelihood_no_cache is called but never defined in this module or elsewhere in the codebase. This will cause a compilation error. You need to either define this function or change the implementation to use simulate_subject directly without caching, similar to how it's done for the cached version.

Copilot uses AI. Check for mistakes.
@mhovd mhovd linked an issue Jan 8, 2026 that may be closed by this pull request
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Use log-likelihood instead of the likelihood

2 participants