Example #1
0
class ICGroup(object):
    natom = None

    def __init__(self, system, rules=None, cases=None):
        self.system = system
        self.cases = cases

        # Compile the rules if they are present
        if cases is None:
            if rules is None:
                rules = ['!0'] * self.natom
            compiled_rules = []
            for rule in rules:
                if isinstance(rule, str):
                    rule = atsel_compile(rule)
                compiled_rules.append(rule)
            self.rules = compiled_rules
            self.cases = list(self._iter_cases())
        elif rules is not None:
            raise ValueError(
                'Either rules are cases must be provided, not both.')

        # Construct a fake system, a dlist and an iclist for just one ic
        self.fake_system = System(numbers=np.zeros(self.natom, int),
                                  pos=np.zeros((self.natom, 3), float),
                                  rvecs=self.system.cell.rvecs)
        self.dlist = DeltaList(self.fake_system)
        self.iclist = InternalCoordinateList(self.dlist)
        self.tangent = np.zeros((self.natom, 3), float)

    def _iter_cases(self):
        raise NotImplementedError

    def compute_ic(self, pos, indexes):
        # Load coordinates in fake system
        self.fake_system.pos[:] = pos[indexes]
        # Compute internal coordinate
        self.dlist.forward()
        self.iclist.forward()
        # Pick the return value from the ictab
        return self.iclist.ictab[0]['value']

    def compute_tangent(self, pos, indexes, tangent):
        # Load coordinates in fake system
        self.fake_system.pos[:] = pos[indexes]
        # Compute the internal coordinate
        self.dlist.forward()
        self.iclist.forward()
        # Back propagate 1, to get the partial derivatives
        self.iclist.ictab[0]['grad'] = 1
        self.iclist.back()
        self.tangent[:] = 0
        self.dlist.back(self.tangent, None)
        # Assign the derivates to certain values in the 3N vector
        tangent[:] = 0
        tangent[indexes] = self.tangent
Example #2
0
class ICGroup(object):
    natom = None

    def __init__(self, system, rules=None, cases=None):
        self.system = system
        self.cases = cases

        # Compile the rules if they are present
        if cases is None:
            if rules is None:
                rules = ['!0'] * self.natom
            compiled_rules = []
            for rule in rules:
                if isinstance(rule, basestring):
                    rule = atsel_compile(rule)
                compiled_rules.append(rule)
            self.rules = compiled_rules
            self.cases = list(self._iter_cases())
        elif rules is not None:
            raise ValueError('Either rules are cases must be provided, not both.')

        # Construct a fake system, a dlist and an iclist for just one ic
        self.fake_system = System(numbers=np.zeros(self.natom, int), pos=np.zeros((self.natom, 3), float), rvecs=self.system.cell.rvecs)
        self.dlist = DeltaList(self.fake_system)
        self.iclist = InternalCoordinateList(self.dlist)
        self.tangent = np.zeros((self.natom, 3), float)

    def _iter_cases(self):
        raise NotImplementedError

    def compute_ic(self, pos, indexes):
        # Load coordinates in fake system
        self.fake_system.pos[:] = pos[indexes]
        # Compute internal coordinate
        self.dlist.forward()
        self.iclist.forward()
        # Pick the return value from the ictab
        return self.iclist.ictab[0]['value']

    def compute_tangent(self, pos, indexes, tangent):
        # Load coordinates in fake system
        self.fake_system.pos[:] = pos[indexes]
        # Compute the internal coordinate
        self.dlist.forward()
        self.iclist.forward()
        # Back propagate 1, to get the partial derivatives
        self.iclist.ictab[0]['grad'] = 1
        self.iclist.back()
        self.tangent[:] = 0
        self.dlist.back(self.tangent, None)
        # Assign the derivates to certain values in the 3N vector
        tangent[:] = 0
        tangent[indexes] = self.tangent
Example #3
0
class CVInternalCoordinate(CollectiveVariable):
    '''
       An InternalCoordinate disguised as a CollectiveVariable so that it can
       be used together with a BiasPotential.
       This is less efficient than using the InternalCoordinate with a
       ValenceTerm, so the latter is preferred if it is possible.
    '''
    def __init__(self, system, ic, comlist=None):
        raise NotImplementedError
        self.system = system
        self.ic = ic
        self.comlist = comlist
        self.dlist = DeltaList(system if comlist is None else comlist)
        self.iclist = InternalCoordinateList(self.dlist)
        self.iclist.add_ic(ic)

    def get_conversion(self):
        return self.ic.get_conversion()

    def compute(self, gpos=None, vtens=None):
        if self.comlist is not None:
            self.comlist.forward()
        self.dlist.forward()
        self.iclist.forward()
        self.value = self.iclist.ictab[0]['value']
        if gpos is not None: gpos[:] = 0.0
        if vtens is not None: vtens[:] = 0.0
        if not ((gpos is None) and (vtens is None)):
            self.iclist.ictab[0]['grad'] = 1.0
            self.iclist.back()
            if self.comlist is None:
                self.dlist.back(gpos, vtens)
            else:
                self.comlist.gpos[:] = 0.0
                self.dlist.back(self.comlist.gpos, vtens)
                self.comlist.back(gpos)
        return self.value
Example #4
0
class ForcePartValence(ForcePart):
    '''The covalent part of a force-field model.

       The covalent force field is implemented in a three-layer approach,
       similar to the implementation of a neural network:

       1. The first layer consists of a :class:`yaff.pes.dlist.DeltaList` object
          that computes all the relative vectors needed for the internal
          coordinates in the covalent energy terms. This list is automatically
          built up as energy terms are added with the ``add_term`` method. This
          list also takes care of transforming `derivatives of the energy
          towards relative vectors` into `derivatives of the energy towards
          Cartesian coordinates and the virial tensor`.

       2. The second layer consist of a
          :class:`yaff.pes.iclist.InternalCoordinateList` object that computes
          the internal coordinates, based on the ``DeltaList``. This list is
          also automatically built up as energy terms are added. The same list
          is also responsible for transforming `derivatives of the energy
          towards internal coordinates` into `derivatives of the energy towards
          relative vectors`.

       3. The third layers consists of a :class:`yaff.pes.vlist.ValenceList`
          object. This list computes the covalent energy terms, based on the
          result in the ``InternalCoordinateList``. This list also computes the
          derivatives of the energy terms towards the internal coordinates.

       The computation of the covalent energy is the so-called `forward code
       path`, which consists of running through steps 1, 2 and 3, in that order.
       The derivatives of the energy are computed in the so-called `backward
       code path`, which consists of taking steps 1, 2 and 3 in reverse order.
       This basic idea of back-propagation for the computation of derivatives
       comes from the field of neural networks. More details can be found in the
       chapter, :ref:`dg_sec_backprop`.
    '''
    def __init__(self, system):
        '''
           **Arguments:**

           system
                An instance of the ``System`` class.
        '''
        ForcePart.__init__(self, 'valence', system)
        self.dlist = DeltaList(system)
        self.iclist = InternalCoordinateList(self.dlist)
        self.vlist = ValenceList(self.iclist)
        if log.do_medium:
            with log.section('FPINIT'):
                log('Force part: %s' % self.name)
                log.hline()

    def add_term(self, term):
        '''Add a new term to the covalent force field.

           **Arguments:**

           term
                An instance of the class :class:`yaff.pes.ff.vlist.ValenceTerm`.

           In principle, one should add all energy terms before calling the
           ``compute`` method, but with the current implementation of Yaff,
           energy terms can be added at any time. (This may change in future.)
        '''
        if log.do_high:
            with log.section('VTERM'):
                log('%7i&%s %s' % (self.vlist.nv, term.get_log(), ' '.join(ic.get_log() for ic in term.ics)))
        self.vlist.add_term(term)

    def _internal_compute(self, gpos, vtens):
        with timer.section('Valence'):
            self.dlist.forward()
            self.iclist.forward()
            energy = self.vlist.forward()
            if not ((gpos is None) and (vtens is None)):
                self.vlist.back()
                self.iclist.back()
                self.dlist.back(gpos, vtens)
            return energy
Example #5
0
class ForcePartValenceCOM(ForcePartValence):
    '''
    Part of a force-field model with interactions that act on centers of mass
    At this moment, only covalent interactions are supported.
    '''
    def __init__(self, comsystem, scaling=None):
        ForcePart.__init__(self, 'valence_com', comsystem)
        #ForcePartValence.__init__(self, system)
        self.comlist = comsystem.comlist
        self.gpos = np.zeros((comsystem.gpos_dim, 3), float)
        self.dlist = DeltaList(self.comlist)
        self.iclist = InternalCoordinateList(self.dlist)
        self.vlist = ValenceList(self.iclist)
        self.scaling = scaling
        if log.do_medium:
            with log.section('FPINIT'):
                log('Force part: %s' % self.name)
                log.hline()
        self.term = None  # volume term

    def _internal_compute(self, gpos, vtens):
        with timer.section('Valence'):
            self.comlist.forward()
            self.dlist.forward()
            self.iclist.forward()
            energy = 0
            energy += self.vlist.forward()
            if self.term is not None:
                energy += self.term.compute()
            if not ((gpos is None) and (vtens is None)):
                #print('AA gpos before bias: ', gpos[:3])
                self.vlist.back()
                self.iclist.back()
                self.comlist.gpos[:] = 0.0
                self.dlist.back(self.comlist.gpos, vtens)
                if self.term is not None and vtens is not None:
                    my_vtens = np.zeros((3, 3))
                    self.term.compute(np.zeros((3, 3)), my_vtens)
                    vtens += my_vtens
                energy = self._scale(self.comlist.gpos, vtens, energy)
                #print('COM bias energy: ', energy / molmod.units.kjmol)
                self.comlist.back(gpos)
                #print('ValenceCOM gpos after bias: ', gpos[:3])
            else:
                energy = self._scale(None, None, energy)
            #print('compos 0: ', self.comlist.pos[0, :])
            #print('vtab: ', self.vlist.vtab)
            #print('ValenceCOM energy: ', energy)
            return energy

    def _scale(self, gpos, vtens, energy):
        '''
        Scales the gpos and energy
        '''
        #print('energy before: ', energy / molmod.units.kjmol)
        if self.scaling is not None:
            thres = self.scaling[0]
            curve = self.scaling[1]
            #print('threshold: ', thres / molmod.units.kjmol)
            delta = energy - thres
            #print('delta: ', delta / molmod.units.kjmol)
            #print('energy: ', energy, '  thres: ', thres)
            if curve * delta < 40:
                #print(curve * delta)
                a = np.exp(curve * delta)
                b = 1
                N = (a + b)
                energy = np.log(N) / curve + thres
                #print('scaled  energy: ', energy)
                if gpos is not None:
                    scale = a / (N)
                    #print('scale: ', scale)
                    gpos *= scale
                if vtens is not None:
                    scale = a / (N)
                    vtens *= scale
            else:
                scale = 1
        #print('energy after: ', energy / molmod.units.kjmol)
        return energy
Example #6
0
class ForcePartValence(ForcePart):
    '''The covalent part of a force-field model.

       The covalent force field is implemented in a three-layer approach,
       similar to the implementation of a neural network:

       (0. Optional, not used by default. A layer that computes centers of mass for groups
           of atoms.)

       1. The first layer consists of a :class:`yaff.pes.dlist.DeltaList` object
          that computes all the relative vectors needed for the internal
          coordinates in the covalent energy terms. This list is automatically
          built up as energy terms are added with the ``add_term`` method. This
          list also takes care of transforming `derivatives of the energy
          towards relative vectors` into `derivatives of the energy towards
          Cartesian coordinates and the virial tensor`.

       2. The second layer consist of a
          :class:`yaff.pes.iclist.InternalCoordinateList` object that computes
          the internal coordinates, based on the ``DeltaList``. This list is
          also automatically built up as energy terms are added. The same list
          is also responsible for transforming `derivatives of the energy
          towards internal coordinates` into `derivatives of the energy towards
          relative vectors`.

       3. The third layers consists of a :class:`yaff.pes.vlist.ValenceList`
          object. This list computes the covalent energy terms, based on the
          result in the ``InternalCoordinateList``. This list also computes the
          derivatives of the energy terms towards the internal coordinates.

       The computation of the covalent energy is the so-called `forward code
       path`, which consists of running through steps 1, 2 and 3, in that order.
       The derivatives of the energy are computed in the so-called `backward
       code path`, which consists of taking steps 1, 2 and 3 in reverse order.
       This basic idea of back-propagation for the computation of derivatives
       comes from the field of neural networks. More details can be found in the
       chapter, :ref:`dg_sec_backprop`.
    '''
    def __init__(self, system):
        '''
           Parameters
           ----------

           system
                An instance of the ``System`` class.
        '''
        ForcePart.__init__(self, 'valence', system)

        # override self.gpos to the correct size!
        # natom of COMSystem object will return number of beads
        # but gpos has to have the size (n_atoms, 3), to be consisten
        # with the other parts of the force field
        self.dlist = DeltaList(system)
        self.iclist = InternalCoordinateList(self.dlist)
        self.vlist = ValenceList(self.iclist)
        if log.do_medium:
            with log.section('FPINIT'):
                log('Force part: %s' % self.name)
                log.hline()

    def add_term(self, term):
        '''Add a new term to the covalent force field.

           **Arguments:**

           term
                An instance of the class :class:`yaff.pes.ff.vlist.ValenceTerm`.

           In principle, one should add all energy terms before calling the
           ``compute`` method, but with the current implementation of Yaff,
           energy terms can be added at any time. (This may change in future.)
        '''
        if log.do_high:
            with log.section('VTERM'):
                log('%7i&%s %s' % (self.vlist.nv, term.get_log(), ' '.join(
                    ic.get_log() for ic in term.ics)))
        self.vlist.add_term(term)

    def _internal_compute(self, gpos, vtens):
        with timer.section('Valence'):
            self.dlist.forward()
            self.iclist.forward()
            energy = self.vlist.forward()
            if not ((gpos is None) and (vtens is None)):
                self.vlist.back()
                self.iclist.back()
                self.dlist.back(gpos, vtens)
            return energy
Example #7
0
class CVLinCombIC(CollectiveVariable):
    '''
       A linear combination of InternalCoordinates:
        cv = w0*ic0 + w1*ic1 + ...
    '''
    def __init__(self, system, ics, weights, comlist=None):
        '''
           **Arguments:**

           system
                An instance of the ``System`` class.

           ics
                A list of InternalCoordinate instances.

           weights
                A list defining the weight of each InternalCoordinate that is
                used when computing the linear combination.

           **Optional arguments:**

           comlist

                An instance COMList; if provided, this is used instead of the
                normal DeltaList to compute the InternalCoordinates

        '''
        raise NotImplementedError
        assert len(weights) == len(ics)
        self.system = system
        self.ics = ics
        self.comlist = comlist
        self.dlist = DeltaList(system if comlist is None else comlist)
        self.iclist = InternalCoordinateList(self.dlist)
        for ic in self.ics:
            self.iclist.add_ic(ic)
        self.weights = weights

    def get_conversion(self):
        # Units depend on the particular linear combination of internal
        # coordinates
        return 1.0

    def compute(self, gpos=None, vtens=None):
        if self.comlist is not None:
            self.comlist.forward()
        self.dlist.forward()
        self.iclist.forward()
        self.value = 0.0
        for iic in range(len(self.ics)):
            self.value += self.weights[iic] * self.iclist.ictab[iic]['value']
        if gpos is not None: gpos[:] = 0.0
        if vtens is not None: vtens[:] = 0.0
        if not ((gpos is None) and (vtens is None)):
            for iic in range(len(self.ics)):
                # Derivative of the linear combination to this particular
                # internal coordinate
                self.iclist.ictab[iic]['grad'] = self.weights[iic]
            self.iclist.back()
            if self.comlist is None:
                self.dlist.back(gpos, vtens)
            else:
                self.comlist.gpos[:] = 0.0
                self.dlist.back(self.comlist.gpos, vtens)
                self.comlist.back(gpos)
        return self.value