Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle undetermined energies in BAR calculations #1098

Merged
merged 42 commits into from
Jul 28, 2023

Conversation

mcwitt
Copy link
Collaborator

@mcwitt mcwitt commented Jul 25, 2023

Since #1084, we detect numerical overflows during the evaluation of potentials and represent undetermined energies as NaNs. This has exposed some cases where we previously returned invalid energies (for example, overflows appear to occur frequently during the initial evaluation of the BAR df error and overlap between the end states, before the first iteration of bisection).

Now, when we encounter an energy overflow, the resulting NaN in the u_kln matrix causes pymbar.MBAR (used to compute overlap) to fail with a LinAlgError, crashing the simulation. Also, pymbar.BAR warns and returns zeros for the $\Delta f$ and uncertainty estimates when there are NaN work values.

This PR addresses the latter issues by:

  1. Detecting NaNs in u_kln. When we detect a NaN, we raise a warning and replace NaNs with np.inf (representing a configuration with zero probability).
  2. Replacing usage of the pymbar.BAR estimator with pymbar.MBAR (on a 2-state u_kn matrix). The latter interprets np.inf as a configuration with zero weight (as desired), while the former returns (0.0, 0.0) if there are any infs or NaNs.
  3. Switching to a cost function for bisection based on MBAR overlap (rather than bootstrapped $\Delta f$ error). See Handle undetermined energies in BAR calculations #1098 (comment)

This uncovered a related issue where the timeout parameter in bootstrap_bar, intended to cap computational cost, can lead to nondeterminism. This was addressed by

  1. Removing timeout logic and the timeout parameter from bootstrap_bar and bar_with_bootstrapped_uncertainty
  2. Modifying MBAR relative_tolerance and maximum_iterations to reduce cost
  3. Upgrading pymbar from 3.0.5 to 3.1.0 (3.0.6 fixed a bug that prevented maximum_iterations from being respected)

Todo:

  • Add test for case with many NaNs, e.g. completely non-overlapping states
  • Is bootstrapped $\Delta f$ error still reliable for bisection?
    • seems robust for non-overlapping states in testing: uncertainty estimate produced by MBAR is finite and large
    • EDIT: point above was wrong; MBAR-estimated $\Delta f$ errors are finite and large for pairs with zero overlap, but bootstrapped errors are zero in this case (since MBAR estimates $\Delta f \approx 0$ for each bootstrap sample). Addressed by switching to bisection cost function based on MBAR overlap.

@mcwitt mcwitt marked this pull request as ready for review July 26, 2023 15:16
@mcwitt mcwitt requested review from maxentile and jkausrelay July 26, 2023 15:16
tests/test_bar.py Outdated Show resolved Hide resolved
timemachine/fe/bar.py Outdated Show resolved Hide resolved
timemachine/fe/bar.py Outdated Show resolved Hide resolved
tests/test_bar.py Show resolved Hide resolved
@mcwitt
Copy link
Collaborator Author

mcwitt commented Jul 26, 2023

Even after preventing BAR calculations from failing when there are undetermined energies, there is a separate issue that bootstrapped $\Delta f$ errors are no longer useful for bisection: in the extreme case of zero overlap between states, pymbar's MBAR returns a $\Delta f$ estimate of 0.0 for every bootstrap sample, so the resulting bootstrapped error is 0. (See the failing test added in 7495063)

In 54e4b5c I modified the bisection cost function to bisect pairs of states with lowest MBAR overlap, rather than maximum bootstrapped $\Delta f$ error. This works in the case of non-overlapping distributions, where the overlap is zero. We've also observed the MBAR overlap estimate to be more robust numerically compared with MBAR's $\Delta f$ error estimate.

@mcwitt mcwitt requested a review from maxentile July 28, 2023 06:04
timemachine/fe/bar.py Outdated Show resolved Hide resolved
timemachine/fe/bar.py Outdated Show resolved Hide resolved

bar_result = df_from_u_kln(
u_kln_sample,
initial_f_k=mbar.f_k, # warm start
Copy link
Collaborator

Choose a reason for hiding this comment

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

q: would it make sense to add bootstrap_maximum_iterations to signature of bootstrap_bar, then forward it here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good call, it seems like useful flexibility to be able to specify max iterations here. Added in f7a1ab4

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Unsure whether it would be useful to be able to specify max iterations separately for the point estimate and the bootstrap samples, but this can probably be added later if useful.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Unsure whether it would be useful to be able to specify max iterations separately for the point estimate and the bootstrap samples, but this can probably be added later if useful.

Separately makes sense to me (to control expense), but can be added later if needed.

Copy link
Collaborator

@jkausrelay jkausrelay left a comment

Choose a reason for hiding this comment

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

Minor comments, overall LGTM.

@@ -102,7 +102,7 @@ def build_extension(self, ext):
"jaxlib>0.4.1",
"networkx",
"numpy",
"pymbar>3.0.4,<4",
"pymbar>=3.0.6,<4",
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: Should this be 3.1.0 or higher? Not sure why this is different from requirements

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

<3.0.6 definitely won't work because of choderalab/pymbar#425, which was merged in 3.0.6. In general I prefer to use version constraints in setup.py only to exclude known-incompatible versions.

When upgrading, I opted to use the latest release <4.

print(f"bootstrap uncertainty = {bootstrap_sigma}, pymbar.MBAR uncertainty = {df_err_ref}")
assert df_0 == df_ref
assert df_1 == df_ref
assert len(bootstrap_samples) == n_bootstrap, "timed out on default problem size!"
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: Is this still relevant?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Kept assertion but removed misleading message in 28c6d9f

# should give the same result with inf
result_with_inf = estimate_free_energy_bar(np.array([u_kln_with_inf]), DEFAULT_TEMP)
assert result_with_nan.dG == result_with_inf.dG
assert result_with_nan.dG_err == result_with_inf.dG_err
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: assert finite

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Added in e52fd30

# As of pymbar 3.1.0, computation of the covariance matrix can raise an exception on incomplete convergence.
# In this case, return the unconverged estimate with NaN as uncertainty.
df = mbar.getFreeEnergyDifferences(compute_uncertainty=False)[0]
return df[0, 1], np.nan
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: nan or inf?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Leaning toward NaN as more appropriate here, since we can't be sure of the failure reason. The choice here doesn't affect bisection, since we now use overlap.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Makes sense.

timemachine/fe/bar.py Outdated Show resolved Hide resolved
@mcwitt mcwitt enabled auto-merge (squash) July 28, 2023 16:06
@mcwitt mcwitt merged commit dbababb into master Jul 28, 2023
@mcwitt mcwitt deleted the fix/handle-energy-overflow-bisection branch July 28, 2023 17:24
badisa added a commit that referenced this pull request Aug 9, 2023
* Change to MBAR in #1098
produces much more significant differences than BAR
badisa added a commit that referenced this pull request Aug 9, 2023
* Change to MBAR in #1098
produces much more significant differences than BAR in cases of poor convergence
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.

4 participants