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
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
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
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
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
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
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