Пример #1
0
class Explorer():

    topology = None
    context = None
    pbc = False
    n_atoms = 0
    n_dof = 0
    md = None
    quench = None
    move = None

    def __init__(self, topology=None, system=None, pbc=False, platform='CUDA'):

        from .md import MD
        from .quench import Quench
        from .move import Move
        from .distance import Distance
        from .acceptance import Acceptance

        if topology is None:
            raise ValueError('topology is needed')

        if system is None:
            raise ValueError('system is needed')

        integrator = LangevinIntegrator(0 * u.kelvin, 1.0 / u.picoseconds,
                                        2.0 * u.femtoseconds)
        #integrator.setConstraintTolerance(0.00001)

        if platform == 'CUDA':
            platform = Platform.getPlatformByName('CUDA')
            properties = {'CudaPrecision': 'mixed'}
        elif platform == 'CPU':
            platform = Platform.getPlatformByName('CPU')
            properties = {}

        self.topology = topology
        self.context = Context(system, integrator, platform, properties)
        self.n_atoms = msm.get(self.context, target='system', n_atoms=True)

        self.n_dof = 0
        for i in range(system.getNumParticles()):
            if system.getParticleMass(i) > 0 * u.dalton:
                self.n_dof += 3
        for i in range(system.getNumConstraints()):
            p1, p2, distance = system.getConstraintParameters(i)
            if system.getParticleMass(
                    p1) > 0 * u.dalton or system.getParticleMass(
                        p2) > 0 * u.dalton:
                self.n_dof -= 1
        if any(
                type(system.getForce(i)) == CMMotionRemover
                for i in range(system.getNumForces())):
            self.n_dof -= 3

        self.pbc = pbc

        if self.pbc:
            raise NotImplementedError

        self.md = MD(self)
        self.quench = Quench(self)
        self.move = Move(self)
        self.distance = Distance(self)
        self.acceptance = Acceptance(self)

    def _copy(self):

        topology = self.topology
        coordinates = self.get_coordinates()
        system = self.context.getSystem()
        platform = self.context.getPlatform().getName()
        pbc = self.pbc

        tmp_explorer = Explorer(topology, system, pbc, platform)
        tmp_explorer.set_coordinates(coordinates)

        for ii, jj in vars(tmp_explorer.md).items():
            if not ii.startswith('_'):
                if jj._initialized:
                    jj.replicate_parameters(self)

        for ii, jj in vars(tmp_explorer.quench).items():
            if not ii.startswith('_'):
                if jj._initialized:
                    jj.replicate_parameters(self)

        for ii, jj in vars(tmp_explorer.move).items():
            if not ii.startswith('_'):
                if jj._initialized:
                    jj.replicate_parameters(self)

        return tmp_explorer

    def replicate(self, times=1):

        from copy import deepcopy

        if times == 1:

            output = self._copy()

        else:

            output = [self._copy() for ii in range(times)]

        return output

    def set_coordinates(self, coordinates):

        self.context.setPositions(coordinates)

    def get_coordinates(self):

        return self.context.getState(getPositions=True).getPositions(
            asNumpy=True)

    def set_velocities(self, velocities):

        self.context.setVelocities(velocities)

    def set_velocities_to_temperature(self, temperature):

        self.context.setVelocitiesToTemperature(temperature)

    def get_velocities(self):

        return self.context.getState(getVelocities=True).getVelocities(
            asNumpy=True)

    def get_temperature(self):

        return (2 * self.context.getState(getEnergy=True).getKineticEnergy() /
                (self.n_dof * u.MOLAR_GAS_CONSTANT_R)).in_units_of(u.kelvin)

    def get_potential_energy(self):

        energy = self.context.getState(getEnergy=True).getPotentialEnergy()
        return energy

    def get_potential_energy_gradient(self):

        gradient = -self.context.getState(getForces=True).getForces(
            asNumpy=True)
        gradient = gradient.ravel() * gradient.unit
        return gradient

    def get_potential_energy_hessian(self,
                                     mass_weighted=False,
                                     symmetric=True):
        """OpenMM single frame hessian evaluation
        Since OpenMM doesnot provide a Hessian evaluation method, we used finite difference on forces

        from: https://leeping.github.io/forcebalance/doc/html/api/openmmio_8py_source.html

        Returns
        -------
        hessian: np.array with shape 3N x 3N, N = number of "real" atoms
            The result hessian matrix.
            The row indices are fx0, fy0, fz0, fx1, fy1, ...
            The column indices are x0, y0, z0, x1, y1, ..
            The unit is kilojoule / (nanometer^2 * mole * dalton) => 10^24 s^-2
        """

        n_dof = self.n_atoms * 3
        pos = self.get_coordinates()
        hessian = np.empty(
            (n_dof, n_dof),
            dtype=float) * u.kilojoules_per_mole / (u.nanometers**2)
        # finite difference step size
        diff = 0.0001 * u.nanometer
        coef = 1.0 / (2.0 * diff)  # 1/2h

        for i in range(self.n_atoms):
            # loop over the x, y, z coordinates
            for j in range(3):
                # plus perturbation
                pos[i][j] += diff
                self.set_coordinates(pos)
                grad_plus = self.get_potential_energy_gradient()
                # minus perturbation
                pos[i][j] -= 2 * diff
                self.set_coordinates(pos)
                grad_minus = self.get_potential_energy_gradient()
                # set the perturbation back to zero
                pos[i][j] += diff
                # fill one row of the hessian matrix
                hessian[i * 3 + j] = (grad_plus - grad_minus) * coef

        if mass_weighted:
            mass = np.array([
                self.context.getSystem().getParticleMass(k).value_in_unit(
                    u.dalton) for k in range(self.n_atoms)
            ]) * u.dalton
            mass_weight = 1.0 / np.sqrt(mass) * (mass.unit**-0.5)
            mass_weight = np.repeat(mass_weight, 3) * mass_weight.unit
            hessian = np.multiply(
                hessian, mass_weight) * hessian.unit * mass_weight.unit
            hessian = np.multiply(
                hessian,
                mass_weight[:, np.newaxis]) * hessian.unit * mass_weight.unit

        # make hessian symmetric by averaging upper right and lower left
        if symmetric:
            hessian += hessian.T * hessian.unit
            hessian *= 0.5

        # recover the original position
        self.set_coordinates(pos)
        return hessian