import math
import logging
import numpy as np
from openpathsampling.netcdfplus import StorableNamedObject
logger = logging.getLogger(__name__)
init_log = logging.getLogger('openpathsampling.initialization')
class ShootingPointSelector(StorableNamedObject):
    def __init__(self):
        super(ShootingPointSelector, self).__init__()
    @property
    def identifier(self):
        if hasattr(self, 'json'):
            return self.json
        else:
            return None
    def f(self, snapshot, trajectory):
        '''
        Returns the unnormalized proposal probability of a snapshot
        
        Notes
        -----
        In principle this is an collectivevariable so we could easily add
        caching if useful
        '''
        return 1.0
    def probability(self, snapshot, trajectory):
        sum_bias = self.sum_bias(trajectory)
        if sum_bias > 0.0:
            return self.f(snapshot, trajectory) / sum_bias
        else:
            return 0.0
    def probability_ratio(self, snapshot, old_trajectory, new_trajectory):
        p_old = self.probability(snapshot, old_trajectory)
        p_new = self.probability(snapshot, new_trajectory)
        return p_new / p_old
    def _biases(self, trajectory):
        '''
        Returns a list of unnormalized proposal probabilities for all
        snapshots in trajectory
        '''
        return [self.f(s, trajectory) for s in trajectory]
    def sum_bias(self, trajectory):
        '''
        Returns the unnormalized probability probability of a trajectory.
        This is just the sum of all proposal probabilities in a trajectory.
        
        Notes
        -----
        For a uniform distribution this is proportional to the length of the
        trajectory. In this case we can estimate the maximal accepted
        trajectory length for a given acceptance probability.
        
        After we have generated a new trajectory the acceptance probability
        only for the non-symmetric proposal of different snapshots is given
        by `probability(old_trajectory) / probability(new_trajectory)`
        '''
        return sum(self._biases(trajectory))
    def pick(self, trajectory):
        '''
        Returns the index of the chosen snapshot within `trajectory`
        
        Notes
        -----
        The native implementation is very slow. Simple picking algorithm
        should override this function.
        '''
        prob_list = self._biases(trajectory)
        sum_bias = sum(prob_list)
        rand = np.random.random() * sum_bias
        idx = 0
        prob = prob_list[0]
        while prob <= rand and idx < len(prob_list):
            idx += 1
            prob += prob_list[idx]
        return idx
class GaussianBiasSelector(ShootingPointSelector):
    def __init__(self, collectivevariable, alpha=1.0, l_0=0.5):
        '''
        A Selector that biases according to a specified CollectiveVariable
        using a mean l_0 and a variance alpha
        '''
        super(GaussianBiasSelector, self).__init__()
        self.collectivevariable = collectivevariable
        self.alpha = alpha
        self.l_0 = l_0
    def f(self, snapshot, trajectory):
        l_s = self.collectivevariable(snapshot)
        return math.exp(-self.alpha * (l_s - self.l_0) ** 2)
[docs]class FinalFrameSelector(ShootingPointSelector):
    '''
    Pick final trajectory frame as shooting point.
    This is used for "forward" extension in, e.g., the minus move.
    '''
    def f(self, frame, trajectory):
        if trajectory.index(frame) == len(trajectory) - 1:
            return 1.0
        else:
            return 0.0
    def pick(self, trajectory):
        return len(trajectory)-1
    def probability(self, snapshot, trajectory):
        return 1.0 # there's only one choice
    def probability_ratio(self, snapshot, old_trajectory, new_trajectory):
        # must be matched by a final-frame selector somewhere
        return 1.0 
[docs]class FirstFrameSelector(ShootingPointSelector):
    '''
    Pick first trajectory frame as shooting point.
    This is used for "backward" extension in, e.g., the minus move.
    '''
    def f(self, frame, trajectory):
        if trajectory.index(frame) == 0:
            return 1.0
        else:
            return 0.0
    def pick(self, trajectory):
        return 0
    def probability(self, snapshot, trajectory):
        return 1.0 # there's only one choice
    def probability_ratio(self, snapshot, old_trajectory, new_trajectory):
        # must be matched by a first-frame selector somewhere
        return 1.0