class Strain(ForceField): def __init__(self, system, term, other_terms, cart_penalty=1e-3*angstrom): ''' A class deriving from the Yaff ForceField class to implement the strain of a molecular geometry associated with the term defined by term_index. **Arguments** system A Yaff System instance containing all system information. term a Term instance representing the term of the perturbation trajectory of the current strain other_terms a list of Term instances representing all other terms for ICs for which a strain contribution should be added **Keyword Arguments** cart_penalty Magnitude of an extra term added to the strain that penalises a deviation of the cartesian coordinates of each atom with respect to the equilibrium coordinates. This penalty is equal to norm(R-R_eq)**2/(2.0*3*Natoms*cart_penalty**2) and prevents global translations, global rotations as well as rotations of molecular fragments far from the IC under consideration. ''' self.coords0 = system.pos.copy() self.ndof = np.prod(self.coords0.shape) self.cart_penalty = cart_penalty self.cons_ic_atindexes = term.get_atoms() #construct main strain strain = ForcePartValence(system) for other in other_terms: if other.kind == 3: continue #no cross terms strain.add_term(Harmonic(1.0, None, other.ics[0])) #set the rest values to the equilibrium values strain.dlist.forward() strain.iclist.forward() for iterm in range(strain.vlist.nv): vterm = strain.vlist.vtab[iterm] ic = strain.iclist.ictab[vterm['ic0']] vterm['par1'] = ic['value'] ForceField.__init__(self, system, [strain]) #Abuse the Chebychev1 polynomial to simply get the value of q-1 and #implement the contraint constraint = ForcePartValence(system) constraint.add_term(Chebychev1(-2.0,term.ics[0])) self.constraint = ForceField(system, [constraint]) self.constrain_target = None self.constrain_value = None self.value = None def gradient(self, X): ''' Compute the gradient of the strain w.r.t. Cartesian coordinates of the system. For the ic that needs to be constrained, a Lagrange multiplier is included. ''' #initialize return value grad = np.zeros((len(X),)) #compute strain gradient gstrain = np.zeros(self.coords0.shape) self.update_pos(self.coords0 + X[:self.ndof].reshape((-1,3))) self.value = self.compute(gpos=gstrain) #compute constraint gradient gconstraint = np.zeros(self.coords0.shape) self.constraint.update_pos(self.coords0 + X[:self.ndof].reshape((-1,3))) self.constrain_value = self.constraint.compute(gpos=gconstraint) + 1.0 #construct gradient grad[:self.ndof] = gstrain.reshape((-1,)) + X[self.ndof]*gconstraint.reshape((-1,)) grad[self.ndof] = self.constrain_value - self.constrain_target #cartesian penalty, i.e. extra penalty for deviation w.r.t. cartesian equilibrium coords indices = np.array([[3*i,3*i+1,3*i+2] for i in range(self.ndof//3) if i not in self.cons_ic_atindexes]).ravel() if len(indices)>0: grad[indices] += X[indices]/(self.ndof*self.cart_penalty**2) with log.section('PTGEN', 4, timer='PT Generate'): log.dump(' Gradient: rms = %.3e max = %.3e cnstr = %.3e' %(np.sqrt((grad[:self.ndof]**2).mean()), max(grad[:self.ndof]), grad[self.ndof])) return grad
class Strain(ForceField): def __init__(self, system, cons_ic, cons_ic_atindexes, ics, cart_penalty=1e-3 * angstrom): ''' A class deriving from the Yaff ForceField class to implement the strain of a molecular geometry associated with the term defined by term_index. **Arguments** system A Yaff System instance containing all system information. cons_ic An instance of Yaff Internal Coordinate representing the constrained term in the strain. cons_ic_atindexes A list of the atoms involved in the constrained IC. This is required for the implementation of the cartesian penalty. In principle this could be extracted from the information stored in cons_ic, but this is the lazy solution. ics A list of Yaff Internal Coordinate instances for which the strain needs to be minimized. cart_penalty Magnitude of an extra term added to the strain that penalises a deviation of the cartesian coordinates of each atom with respect to the equilibrium coordinates. This penalty is equal to norm(R-R_eq)**2/(2.0*3*Natoms*cart_penalty**2) and prevents global translations, global rotations as well as rotations of molecular fragments far from the IC under consideration. ''' self.coords0 = system.pos.copy() self.ndof = np.prod(self.coords0.shape) self.cart_penalty = cart_penalty self.cons_ic_atindexes = cons_ic_atindexes part = ForcePartValence(system) for ic in ics: part.add_term(Harmonic(1.0, None, ic)) #set the rest values to the equilibrium values part.dlist.forward() part.iclist.forward() for iterm in xrange(part.vlist.nv): term = part.vlist.vtab[iterm] ic = part.iclist.ictab[term['ic0']] term['par1'] = ic['value'] ForceField.__init__(self, system, [part]) #Abuse the Chebychev1 polynomial to simply get the value of q-1 and #implement the contraint part = ForcePartValence(system) part.add_term(Chebychev1(-2.0, cons_ic)) self.constraint = ForceField(system, [part]) self.constrain_target = None self.constrain_value = None self.value = None def gradient(self, X): ''' Compute the gradient of the strain wrt Cartesian coordinates of the system. For every ic that needs to be constrained, a Lagrange multiplier is included. ''' #small check #assert X.shape[0] == self.ndof + 1 #initialize return value grad = np.zeros((len(X), )) #compute strain gradient gstrain = np.zeros(self.coords0.shape) self.update_pos(self.coords0 + X[:self.ndof].reshape((-1, 3))) self.value = self.compute(gpos=gstrain) #compute constraint gradient gconstraint = np.zeros(self.coords0.shape) self.constraint.update_pos(self.coords0 + X[:self.ndof].reshape((-1, 3))) self.constrain_value = self.constraint.compute(gpos=gconstraint) + 1.0 #construct gradient grad[:self.ndof] = gstrain.reshape( (-1, )) + X[self.ndof] * gconstraint.reshape((-1, )) grad[self.ndof] = self.constrain_value - self.constrain_target #cartesian penalty, i.e. extra penalty for deviation w.r.t. cartesian equilibrium coords indices = np.array([[3 * i, 3 * i + 1, 3 * i + 2] for i in xrange(self.ndof / 3) if i not in self.cons_ic_atindexes]).ravel() if len(indices) > 0: grad[indices] += X[indices] / (self.ndof * self.cart_penalty**2) with log.section('PTGEN', 4, timer='PT Generate'): log.dump(' Gradient: rms = %.3e max = %.3e cnstr = %.3e' % (np.sqrt( (grad[:self.ndof]**2).mean()), max( grad[:self.ndof]), grad[self.ndof])) return grad