Skip to content

Fix broken WHAMing if an InterfaceSet has more than 10 interfaces - #967

Merged
dwhswenson merged 2 commits into
openpathsampling:masterfrom
sroet:fix_whamming
Jan 29, 2021
Merged

Fix broken WHAMing if an InterfaceSet has more than 10 interfaces#967
dwhswenson merged 2 commits into
openpathsampling:masterfrom
sroet:fix_whamming

Conversation

@sroet

@sroet sroet commented Jan 28, 2021

Copy link
Copy Markdown
Member

This fixes an error that took a long time to track down and manifests itself with the same error as #920 (or with a wrong answer in more extreme cases).

TLDR: WHAMing with more than 10 interfaces in an interfaceset was horribly broken, should now work

14bcc2f fixes the issue and adds a regression test
bf8241c updates the test file to use pytest instead (incremental update for #756)

How this PR came to be:

This manifested itself as the same error as #920 (partial stacktrace):

IndexError                                Traceback (most recent call last)
...

~/github_files/openpathsampling/openpathsampling/analysis/tis/crossing_probability.py in from_ensemble_histograms(self, hists)
    128         print(df.index)
    129         # TODO: remove WHAM-specific name here
--> 130         tcp = self.combiner.wham_bam_histogram(df).to_dict()
    131         return LookupFunction(tcp.keys(), tcp.values())

~/github_files/openpathsampling/openpathsampling/numerics/wham.py in wham_bam_histogram(self, input_df)
    503         """
    504         cleaned = self.prep_reverse_cumulative(input_df)
--> 505         self.check_cleaned_overlaps(cleaned)
    506         guess = self.guess_lnZ_crossing_probability(cleaned)
    507         sum_k_Hk_Q = self.sum_k_Hk_Q(cleaned)

~/github_files/openpathsampling/openpathsampling/numerics/wham.py in check_cleaned_overlaps(self, cleaned_df)
    477             prev_data = cleaned_df[prev_col]
    478 
--> 479             first_nonzero = col_data[col_data != 0.0].index[0]
    480             if not prev_data[first_nonzero] > 0.0:
    481                 # use not and > to account for NaNs

~/miniconda3/envs/tstis/lib/python3.7/site-packages/pandas/core/indexes/base.py in __getitem__(self, key)
   4099         if is_scalar(key):
   4100             key = com.cast_scalar_indexer(key, warn_float=True)
-> 4101             return getitem(key)
   4102 
   4103         if isinstance(key, slice):

IndexError: index 0 is out of bounds for axis 0 with size 0

From that issue we know that that happens if everything before the interface is set to 0 and everything after that interface is also 0. So the first thing I did was setting cutoff to 0 to circumvent the same issue as in #920.

This did not solve the error so I decided to add print functions to the prep_reverse_cumulative function of numerics.wham.py (sidenote: %autoreload is great and makes it so that I can debug this without waiting an hour to reload my data).

This is is the first block of print(df), before any cleaning, notice anything peculiar about the column ordering?

           0         1        10       11        12        13        14  \
-2.6  1.000000  1.000000  1.000000  1.00000  1.000000  1.000000  1.000000   
-2.5  1.000000  1.000000  1.000000  1.00000  1.000000  1.000000  1.000000   
-2.4  1.000000  1.000000  1.000000  1.00000  1.000000  1.000000  1.000000   
-2.3  1.000000  1.000000  1.000000  1.00000  1.000000  1.000000  1.000000   
-2.2  1.000000  1.000000  1.000000  1.00000  1.000000  1.000000  1.000000   
-2.1  1.000000  1.000000  1.000000  1.00000  1.000000  1.000000  1.000000   
-2.0  1.000000  1.000000  1.000000  1.00000  1.000000  1.000000  1.000000   
-1.9  1.000000  1.000000  1.000000  1.00000  1.000000  1.000000  1.000000   
-1.8  1.000000  1.000000  1.000000  1.00000  1.000000  1.000000  1.000000   
-1.7  1.000000  1.000000  1.000000  1.00000  1.000000  1.000000  1.000000   
-1.6  1.000000  1.000000  1.000000  1.00000  1.000000  1.000000  1.000000   
-1.5  1.000000  1.000000  1.000000  1.00000  1.000000  1.000000  1.000000   
-1.4  1.000000  1.000000  1.000000  1.00000  1.000000  1.000000  1.000000   
-1.3  1.000000  1.000000  1.000000  1.00000  1.000000  1.000000  1.000000   
-1.2  1.000000  1.000000  1.000000  1.00000  1.000000  1.000000  1.000000   
-1.1  1.000000  1.000000  1.000000  1.00000  1.000000  1.000000  1.000000   
-1.0  1.000000  1.000000  1.000000  1.00000  1.000000  1.000000  1.000000   
-0.9  0.739261  1.000000  1.000000  1.00000  1.000000  1.000000  1.000000   
-0.8  0.548452  0.826174  1.000000  1.00000  1.000000  1.000000  1.000000   
-0.7  0.221778  0.286713  1.000000  1.00000  1.000000  1.000000  1.000000   
-0.6  0.038961  0.022977  1.000000  1.00000  1.000000  1.000000  1.000000   
-0.5  0.000000  0.000000  1.000000  1.00000  1.000000  1.000000  1.000000   
-0.4  0.000000  0.000000  1.000000  1.00000  1.000000  1.000000  1.000000   
-0.3  0.000000  0.000000  1.000000  1.00000  1.000000  1.000000  1.000000   
-0.2  0.000000  0.000000  1.000000  1.00000  1.000000  1.000000  1.000000   
-0.1  0.000000  0.000000  1.000000  1.00000  1.000000  1.000000  1.000000   
0.0  0.000000  0.000000  1.000000  1.00000  1.000000  1.000000  1.000000   
0.1  0.000000  0.000000  1.000000  1.00000  1.000000  1.000000  1.000000   
0.2  0.000000  0.000000  1.000000  1.00000  1.000000  1.000000  1.000000   
0.3  0.000000  0.000000  1.000000  1.00000  1.000000  1.000000  1.000000   
0.4  0.000000  0.000000  1.000000  1.00000  1.000000  1.000000  1.000000   
0.5  0.000000  0.000000  1.000000  1.00000  1.000000  1.000000  1.000000   
0.6  0.000000  0.000000  1.000000  1.00000  1.000000  1.000000  1.000000   
0.7  0.000000  0.000000  1.000000  1.00000  1.000000  1.000000  1.000000   
0.8  0.000000  0.000000  1.000000  1.00000  1.000000  1.000000  1.000000   
0.9  0.000000  0.000000  1.000000  1.00000  1.000000  1.000000  1.000000   
1.0  0.000000  0.000000  1.000000  1.00000  1.000000  1.000000  1.000000   
1.1  0.000000  0.000000  1.000000  1.00000  1.000000  1.000000  1.000000   
1.2  0.000000  0.000000  1.000000  1.00000  1.000000  1.000000  1.000000   
1.3  0.000000  0.000000  1.000000  1.00000  1.000000  1.000000  1.000000   
1.4  0.000000  0.000000  1.000000  1.00000  1.000000  1.000000  1.000000   
1.5  0.000000  0.000000  1.000000  1.00000  1.000000  1.000000  1.000000   
1.6  0.000000  0.000000  1.000000  1.00000  1.000000  1.000000  1.000000   
1.7  0.000000  0.000000  1.000000  1.00000  1.000000  1.000000  1.000000   
1.8  0.000000  0.000000  1.000000  1.00000  1.000000  1.000000  1.000000   
1.9  0.000000  0.000000  1.000000  1.00000  1.000000  1.000000  1.000000   
2.0  0.000000  0.000000  1.000000  1.00000  1.000000  1.000000  1.000000   
2.1  0.000000  0.000000  1.000000  1.00000  1.000000  1.000000  1.000000   
2.2  0.000000  0.000000  1.000000  1.00000  1.000000  1.000000  1.000000   
2.3  0.000000  0.000000  1.000000  1.00000  1.000000  1.000000  1.000000   
2.4  0.000000  0.000000  1.000000  1.00000  1.000000  1.000000  1.000000   
2.5  0.000000  0.000000  1.000000  1.00000  1.000000  1.000000  1.000000   
2.6  0.000000  0.000000  0.325674  0.52048  0.340659  0.416583  0.643357   

Now here is where things go horribly wrong: line 137-142 of wham.py are:

          if self.interfaces is not None:                                         
              # use the interfaces values to set anything before that value to    
              # zero                                                              
              if type(self.interfaces) is not pd.Series:                          
                  self.interfaces = pd.Series(data=self.interfaces,                  
                                              index=df.columns)  

Because this is how self.interfaces looks before converting it to a series:

[-1.0, -0.9, -0.8, -0.7000000000000001, -0.6000000000000001, -0.5000000000000001, -0.40000000000000013, -0.30000000000000016, -0.20000000000000018, -0.1000000000000002, -2.220446049250313e-16, 0.09999999999999964, 0.19999999999999973, 0.2999999999999998, 0.3999999999999997, 0.49999999999999956, 0.5999999999999996, 0.6999999999999997, 0.7999999999999996, 0.8999999999999995, 0.9999999999999996]

and this is after

0    -1.000000e+00
1    -9.000000e-01
10   -8.000000e-01
11   -7.000000e-01
12   -6.000000e-01
13   -5.000000e-01
14   -4.000000e-01
15   -3.000000e-01
16   -2.000000e-01
17   -1.000000e-01
18   -2.220446e-16
19    1.000000e-01
2     2.000000e-01
20    3.000000e-01
3     4.000000e-01
4     5.000000e-01
5     6.000000e-01
6     7.000000e-01
7     8.000000e-01
8     9.000000e-01
9     1.000000e+00

So this now maps the 3rd interface to the data of the 10th, but even worse is the 13th interface is mapped to the data of the second! The data from my 2nd interface does not have any data that is past the 13th, so this results in a column of 0s after cleaning (everything up to the 13th interface is set to 0).

Now, this PR does not actually alter wham.py because the real issue is the sorting of the columns of the dataframe.
This is done at line 124-127 of openpathsampling/analysis/tis/crossing_probability.py

        df = paths.numerics.histograms_to_pandas_dataframe(                     
            input_hists,                                                        
            fcn="reverse_cumulative"                                               
        ).sort_index(axis=1)  

So this sorts axis=1, and what we see is that th columns are sorted, but as if they are strs, as those are sorted character by character, which is how you end up with 0, 1, 10, 11, ..., 19, 2, 20, 3, ... . Now where do these strings come from?

That is what is actually fixed in this PR
If we look at the called function (histograms_to_pandas_dataframe) on line 432-433 the follwing code exists:

      if hist.name is None:                                                   
              hist.name = str(hists.index(hist))     

and these str indices end up in the dataframe. Hence this PR to convert them to an int instead.

@codecov

codecov Bot commented Jan 28, 2021

Copy link
Copy Markdown

Codecov Report

Merging #967 (bf8241c) into master (1279f23) will increase coverage by 0.00%.
The diff coverage is 100.00%.

Impacted file tree graph

@@           Coverage Diff           @@
##           master     #967   +/-   ##
=======================================
  Coverage   80.25%   80.26%           
=======================================
  Files         136      136           
  Lines       14449    14449           
=======================================
+ Hits        11596    11597    +1     
+ Misses       2853     2852    -1     
Impacted Files Coverage Δ
openpathsampling/numerics/histogram.py 83.49% <100.00%> (ø)
openpathsampling/netcdfplus/cache.py 61.53% <0.00%> (+0.34%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 1279f23...bf8241c. Read the comment docs.

@dwhswenson dwhswenson left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Huh... no idea how that happened, but that code dates back to 2015. My guess is that there was some weirdness with pandas that forced me to name things as strings, and then that fact got forgotten in the new TIS analysis (written in 2017, IIRC).

@dwhswenson
dwhswenson merged commit f43339c into openpathsampling:master Jan 29, 2021
@sroet

sroet commented Jan 29, 2021

Copy link
Copy Markdown
Member Author

@dwhswenson Quick question; nothing got published with OPS and more than 10 interfaces in an interface set, right? As this wrong ordering can lead to wrong answers (for me it was 7 orders of magnitude) if (by sheer luck) there is data from the inner interface that is valid for outer interfaces.

@sroet
sroet deleted the fix_whamming branch January 29, 2021 12:59
@dwhswenson

Copy link
Copy Markdown
Member

@arjunwadhawan You might want to check on this. Note that this should only be relevant for the new TIS analysis. This will only happen if you set the cutoff very small (or have interfaces that overlap way more than is reasonable). If you use the rule of thumb that successive interfaces should have 20% overlap, then fewer than 1 in a million trajectories from interface 1 should overlap with interface 10, and that would be ignored by cutoff.

@dwhswenson

Copy link
Copy Markdown
Member

@sroet : With the error, how did plots of the total crossing probability look? I would hope that this problem would have been evident in the TCP, compared to the per-interface crossing probability.

@sroet

sroet commented Jan 29, 2021

Copy link
Copy Markdown
Member Author

You might want to check on this. Note that this should only be relevant for the new TIS analysis. This will only happen if you set the cutoff very small (or have interfaces that overlap way more than is reasonable). If you use the rule of thumb that successive interfaces should have 20% overlap, then fewer than 1 in a million trajectories from interface 1 should overlap with interface 10, and that would be ignored by cutoff

The reason why it happened for me was because I started sampling with an A->B path in all interfaces, assuming it wouldn't matter for convergence if I ran 100_000 MC steps (not unreasonable for real systems), followed by the error described in #920 which was why I set the cutoff to (essentially) 0 (which would could have prevented the "wrong" result.

@dwhswenson so for me it looked reasonable as my toy system had a reasonable cliff (10 KbT triangle barrier between -1 and 1, with stable states at -2 and 2.5), but the TCP should have (ofcourse) been -10 instead of the -3 found.

tcp_RETIS-0-langevin_100000

And from the tcp this was not entirely clear (to me) why is was 7 orders of magnitude off (the BA one is should be mirrored for this plot). Looking at it with what I know now, you indeed see the giant cliff that is way earlier than you would expect and then recovers again.

chist_RETIS-0-langevin_100000

The current plots looks like this (1/10 th of newly sampled data (that came from a run that decorrelated properly before running production)):

tcp_RETIS-0-langevin-100000_10001_new

chist_RETIS-0-langevin-100000_10001_new

@dwhswenson

Copy link
Copy Markdown
Member

I'm having trouble interpreting the plots from the old data. Is the first one the total crossing prob? The standard thing is to plot TCP on the same plot as the ensemble crossing probabilities, as you do at the end for the current data. TCP should look exactly like the innermost crossing probability until noise in the innermost crossing prob starts to matter. It's hard to tell, but it doesn't look like that would be the case if you put the first plot on the first per-ensemble plot.

@dwhswenson dwhswenson added the bugfix PRs fixing bugs label Jan 29, 2021
@sroet

sroet commented Jan 29, 2021

Copy link
Copy Markdown
Member Author

I'm having trouble interpreting the plots from the old data. Is the first one the total crossing prob? The standard thing is to plot TCP on the same plot as the ensemble crossing probabilities, as you do at the end for the current data. TCP should look exactly like the innermost crossing probability until noise in the innermost crossing prob starts to matter. It's hard to tell, but it doesn't look like that would be the case if you put the first plot on the first per-ensemble plot.

Yeah that is the TCP, and I started getting better at plotting these graphs. It is indeed better do plot them together with the ensemble crossings.

Looking at them side by side (on my monitor) it is not the case that the total tcp follow the innermost probability, it drops more rapid than the innermost probability

@dwhswenson

Copy link
Copy Markdown
Member

Looking at them side by side (on my monitor) it is not the case that the total tcp follow the innermost probability, it drops more rapid than the innermost probability

Okay, I'll hope that this has therefore been noticed by anyone who has tried it before. At least @arjunwadhawan and anyone else in the @bolhuis group would have made such plots, and noticed if something was way off. But this is a critical bug. Cutting 1.4.2 release ASAP.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix PRs fixing bugs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants