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 __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 get_ei_ff(name, system, charges, scales, radii=None, average=True, pbc=[0,0,0]): ''' A routine to construct a Yaff force field for the electrostatics **Arguments** name A string for the name of the force field. This name will show in possible plots visualizing contributions along perturbation trajectories system A Yaff System instance representing the system charges A numpy array containing the charge of each separate atom scales A list of 4 floats, representing scale1, scale2, scale3, scale4, i.e. the electrostatic scaling factors **Optional Arguments** radii A numpy array containing the gaussian radii of each separate atom. If this argument is omitted, point charges are used. average If set to True, the charges and radii will first be averaged over atom types. This is True by default. ''' if not (np.array(pbc)==0).all(): raise NotImplementedError('Periodic system not implemented in get_ei_ff') if average: qs = {} rs = {} ffatypes = [system.ffatypes[i] for i in system.ffatype_ids] for i, atype in enumerate(ffatypes): if atype in qs: qs[atype].append(charges[i]) else: qs[atype] = [charges[i]] if radii is not None: if atype in rs: rs[atype].append(radii[i]) else: rs[atype] = [radii[i]] for i, atype in enumerate(ffatypes): charges[i] = np.array(qs[atype]).mean() if radii is not None: radii[i] = np.array(rs[atype]).mean() if radii is None: radii = np.zeros(len(system.ffatype_ids), float) pair_pot = PairPotEI(charges.astype(np.float), 0.0, 50*angstrom, None, 1.0, radii.astype(np.float)) nlist = NeighborList(system, 0) scalings = Scalings(system, scale1=scales[0], scale2=scales[1], scale3=scales[2], scale4=scales[3]) part = ForcePartPair(system, nlist, scalings, pair_pot) ff = ForceField(system, [part], nlist=nlist) return YaffForceField(name, ff)
def get_hessian_contrib(self, index, fc=None): ''' Get the contribution to the covalent hessian of term with given index (and its slaves). If fc is given, set the fc of the master and its slave to the given fc. ''' val = ForcePartValence(self.system) kind = self.vlist.vtab[index]['kind'] masterslaves = [index] + self.terms[index].slaves if kind == 4: #Cosine m, k, rv = self.get_params(index) if fc is not None: k = fc for jterm in masterslaves: ics = self.terms[jterm].ics args = (m, k, rv) + tuple(ics) val.add_term(Cosine(*args)) elif kind == 3: #cross k, rv0, rv1 = self.get_params(index) if fc is not None: k = fc for jterm in masterslaves: ics = self.terms[jterm].ics args = (k, rv0, rv1) + tuple(ics) val.add_term(Cross(*args)) elif kind == 1: #Polyfour a0, a1, a2, a3 = list(self.get_params(index)) if fc is not None: a3 = 2.0 * fc a1 = -4.0 * fc * np.cos(a0)**2 for jterm in masterslaves: ics = self.terms[jterm].ics args = ([0.0, a1, 0.0, a3], ) + tuple(ics) val.add_term(PolyFour(*args)) elif kind == 0: #Harmonic: k, rv = self.get_params(index) if fc is not None: k = fc for jterm in masterslaves: ics = self.terms[jterm].ics args = (k, rv) + tuple(ics) val.add_term(Harmonic(*args)) else: raise ValueError('Term kind %i not supported' % kind) ff = ForceField(self.system, [val]) hcov = estimate_cart_hessian(ff) return hcov
def __call__(self, parameters): # prepare force field ff = ForceField.generate(self.system, parameters, **self.kwargs) # run actual simulation return self.run(ff)
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
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
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
def write_raspa_input(guests, parameters, host=None, workdir='.', guestdata=None, hostname='host'): """ Prepare input files that can be used to run RASPA simulations. Only a small subset of the full capabilities of RASPA can be explored. **Arguments:** guests A list specifying the guest molecules. Each entry can be one of two types: (i) the filename of a system file describing one guest molecule, (ii) a System instance of one guest molecule parameters Force-field parameters describing guest-guest and optionally host-guest interaction. Three types are accepted: (i) the filename of the parameter file, which is a text file that adheres to YAFF parameter format, (ii) a list of such filenames, or (iii) an instance of the Parameters class. **Optional arguments:** host Two types are accepted: (i) the filename of a system file describing the host system, (ii) a System instance of the host workdir The directory where output files will be placed. guestdata List with the same length as guests. Each entry contains a tuple either looking as (name, Tc, Pc, omega) or (name). In the latter case, the parameters Tc, Pc, and omega will be loaded from a data file based on the name. Otherwise, these will be left blank in the input file. """ # Load the guest Systems guests = [ System.from_file(guest) if isinstance(guest, str) else guest for guest in guests ] for guest in guests: assert isinstance(guest, System) if guestdata is None: guestdata = [("guest%03d" % iguest, ) for iguest in range(len(guests))] assert len(guests) == len(guestdata) # Load the host System if host is not None: if isinstance(host, str): host = System.from_file(host) assert isinstance(host, System) complex = host # Merge all systems for guest in guests: complex = complex.merge(guest) # Generate the ForceField, we don't really care about settings such as # cutoffs etc. We only need the parameters in the end ff = ForceField.generate(complex, parameters) # Write the force-field parameters write_raspa_forcefield(ff, workdir) # Write masses and charges of all atoms ffatypes, ffatype_ids = write_pseudo_atoms(ff, workdir) # Write the guest information if host == None: counter = 0 else: counter = host.natom for iguest, (guest, data) in enumerate(zip(guests, guestdata)): write_guest(guest, data, workdir, [ ffatypes[iffa] for iffa in ffatype_ids[counter:counter + guest.natom] ]) counter += guest.natom # Write the host coordinates to a cif file if host is not None: dump_cif( host, os.path.join(workdir, '%s.cif' % hostname), ffatypes=[ffatypes[iffa] for iffa in ffatype_ids[:host.natom]]) # A fairly standard input file for GCMC simulations dump_input(workdir, hostname, [data[0] for data in guestdata])
def ff_generator(system, guest): return ForceField.generate(system, parameters, nlow=max(0,system.natom-guest.natom), nhigh=max(0,system.natom-guest.natom), **kwargs)
def from_files(cls, guest, parameters, **kwargs): """Automated setup of GCMC simulation **Arguments:** guest Two types are accepted: (i) the filename of a system file describing one guest molecule, (ii) a System instance of one guest molecule parameters Force-field parameters describing guest-guest and optionally host-guest interaction. Three types are accepted: (i) the filename of the parameter file, which is a text file that adheres to YAFF parameter format, (ii) a list of such filenames, or (iii) an instance of the Parameters class. **Optional arguments:** hooks A list of MCHooks host Two types are accepted: (i) the filename of a system file describing the host system, (ii) a System instance of the host All other keyword arguments are passed to the ForceField constructor See the constructor of the :class:`yaff.pes.generator.FFArgs` class for the available optional arguments. """ # Load the guest System if isinstance(guest, str): guest = System.from_file(guest) assert isinstance(guest, System) # We want to control nlow and nhigh here ourselves, so remove it from the # optional arguments if the user provided it. kwargs.pop('nlow', None) kwargs.pop('nhigh', None) # Rough guess for number of adsorbed guests nguests = kwargs.pop('nguests', 10) # Load the host if it is present as a keyword host = kwargs.pop('host', None) # Extract the hooks hooks = kwargs.pop('hooks', []) # Efficient treatment of reciprocal ewald contribution if not 'reci_ei' in kwargs.keys(): kwargs['reci_ei'] = 'ewald_interaction' if host is not None: if isinstance(host, str): host = System.from_file(host) assert isinstance(host, System) # If the guest molecule is currently an isolated molecule, than put # it in the same periodic box as the host if guest.cell is None or guest.cell.nvec==0: guest.cell = Cell(host.cell.rvecs) # Construct a complex of host and one guest and the corresponding # force field excluding host-host interactions hostguest = host.merge(guest) external_potential = ForceField.generate(hostguest, parameters, nlow=host.natom, nhigh=host.natom, **kwargs) else: external_potential = None # # Compare the energy of the guest, once isolated, once in a periodic box # guest_isolated = guest.subsystem(np.arange(guest.natom)) # guest_isolated.cell = Cell(np.zeros((0,3))) # optional_arguments = {} # for key in kwargs.keys(): # if key=='reci_ei': continue # optional_arguments[key] = kwargs[key] # ff_guest_isolated = ForceField.generate(guest_isolated, parameters, **optional_arguments) # e_isolated = ff_guest_isolated.compute() # guest_periodic = guest.subsystem(np.arange(guest.natom)) # ff_guest_periodic = ForceField.generate(guest_periodic, parameters, **optional_arguments) # e_periodic = ff_guest_periodic.compute() # if np.abs(e_isolated-e_periodic)>1e-4: # if log.do_warning: # log.warn("An interaction energy of %s of the guest with its periodic " # "images was detected. The interaction of a guest with its periodic " # "images will however NOT be taken into account in this simulation. " # "If the energy difference is large compared to k_bT, you should " # "consider using a supercell." % (log.energy(e_isolated-e_periodic))) # By making use of nlow=nhigh, we automatically discard intramolecular energies eguest = 0.0 # Generator of guest-guest force fields, excluding interactions # between the first N-1 guests def ff_generator(system, guest): return ForceField.generate(system, parameters, nlow=max(0,system.natom-guest.natom), nhigh=max(0,system.natom-guest.natom), **kwargs) return cls(guest, ff_generator, external_potential=external_potential, eguest=eguest, hooks=hooks, nguests=nguests)