Skip to content

PathMoveAcceptors and detailed balance #294

Description

@dwhswenson

This is a continuation of discussion from #251. Where that left off, I think we'd agreed that we should separate acceptance from generation of trials in our Monte Carlo. Here we should discuss details on the implementation of that.

One thing we'll have to keep coming back to is the question of detailed balance. Maintaining detailed balance MUST by the job of the PathMoveAcceptor. New tools implemented as part of MoveScheme should make that easier to accomplish.

Detailed balance

Fundamentally, we need to know the probability of generating the trial state from the original state and vice versa. Call these P_o_n and P_n_o. At each decision point in the move decision tree, we modify these probabilities. Note that we can mark some level of the move tree as the "official mover", and these probabilities split into a probability of choosing a given "official mover" and the probability of making the change within that "official mover." I will call the probability of choosing a specific "official mover" the "choice probability". I will call the probability of generating a given trial from a given initial condition using that official mover the "trial generation probability". The product of these two gives us P_o_n or P_n_o.

Since we're usually interested in the ratio of P_o_n and P_n_o, we can often obtain shortcuts by directly calculating that ratio. However, we should leave other possibilities open as well.

Each path through the move decision tree has a "balance partner". These partners are pairs that can satisfy microscopic reversibility. If a given mover is used to calculate P_o_n, then you must use its balance partner to calculate P_n_o. Note that in many cases (shooting, replica exchange) a mover is its own balance partner: basically, any time the input ensembles equal the output ensembles. However, in cases like ensemble hopping, a different mover is the balance partner.

In principle, a mover can have more than one balance partner. For now, we restrict it to only one.

Proposal: where to put the "official movers" split

In MoveScheme, I've defined groups of movers based on what we'd normally consider the move type, such as "shooting", "replica exchange", "path reversal", "minus move", etc. I propose that the movers included in these lists be the "official movers" in the sense discussed above.

Proposal: how to find the "choice probability" and "balance partners"

For the root mover

Using the "official mover" split I describe above, the MoveScheme automatically determines the choice probability and the balance partners for the root mover.

For bundled moves

If creating what I have previously termed a "bundled" move, we again need to define some "official submove" point on the subsequent subtree. My suggestion is that by default each (topmost) submove of the bundled move is to be considered the "official submove". In that case, each submove must be its own balance partner, and we only need to calculate the trial generation probability.

Note that if the user wants to create a much more complicated subtree within one of these moves, they can use the MoveScheme/MoveStrategy framework to do so. The you would end up with an acceptor from the bundler wrapping an acceptor from the MoveScheme: technically a bit wasteful, but it simplifies a lot. In the acceptor from the MoveScheme, all the choice probabilities and balance partners for the subtree would be set.

Without MoveSchemes

The MoveScheme provides an easy way to set the variables acceptor.choice_probability and acceptor.balance_partners. The user could also manually set these if desired. The only rules are to make it quack the same way MoveScheme does. Primarily:

  • choice_probability is a dictionary of { mover : probablity}, where probability is normalized
  • balance_partners is a dictionary of { mover : list_of_partners}, where currently list_of_partners must be length 1

Note that this does place the restriction that a given mover can only appear once at the level of "official mover" in the subtree. If that's a problem, you'll have to redefine where you set your "official mover". (And any user who is trying to do something that complicated, when we have all these easier approaches available, should be advanced enough to grasp the intricacies here.)

Proposal: how to find the trial generation probability

General case

The trial generation probability is just the product of the probability of each "decision" made along the way. Some of these probabilities can't be normalized to 1, so this isn't a true probability, but the key is that any two "probabilities" from the same function must have the same normalization constant, even if it isn't 1.

My suggestion is that this is implemented by ensuring that sufficient information is stored in the pmc to determine the probability. The process looks like this: from the root mover, we go down a path of the tree, picking a correct move. Then we return the pmc back up the same path (popping stack frames) until we hit an PathMoveAcceptor. From the acceptor, we can call official_mover.trial_probability(pmc, reverse=False) and self.balance_partners[official_mover][0].trial_probability(pmc, reverse=True), which, when multiplying the former by self.choice_probability[official_mover] and the latter by self.choice_probability[self.balance_partners[official_mover][0]], gives P_o_n and P_n_o, respectively.

Shortcut for trial_probability_ratio

In Metropolis MC, we really need the ratio of the two probabilities. And in some cases, the ratio might be easier to calculate than the individual values, since some terms may cancel out. We implement a function called pathmover.trial_probability_ratio(pmc) to do this. This acts the same as trial_probability in usage, except we only have the one call, and the individual parts can benefit from shortcuts.

The default implementation is, of course, to just call the top and bottom parts of the ratio independently, as described in the general case.

Since the acceptor decides whether to use the ratio version or not, we need to go back up the tree to it before figuring out the trial_probability stuff.

Required implementation for the proposal

  • For every path mover, a function to calculate trial_probability(pmc)
  • Shortcuts for the trial_probability_ratio(pmc) in all path movers we've implemented, using a direct call to trial_probability
  • PathMoveAcceptor (and separation of acceptance from trials in current)
  • MetropolisAcceptor as a specific subclass of PathMoveAcceptor: calls trial_probability_ratio of its root mover
  • Modify MoveScheme to take an acceptor=MetropolisAcceptor() argument; and scheme.move_decision_tree() should change root to wrap the current root in the acceptor

I haven't implemented any of this (except the helper tools in MoveScheme) but I think something like this will be the way to, with relative ease, implement this capability. The only part I haven't fully worked out is exactly how the trial_probability calculations work in each mover. This will require some changes to the shooting mover to handle this correctly; I think all the others only need relatively trivial additions.

Activity

  1. self-assigned this
    on Nov 16, 2015
  2. added this to the Soon milestone on Nov 16, 2015
  3. modified the milestones: 2.0, Soon on Jun 23, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions