def _process_nbfix_line(self, line, equivalents=None): try: a1, a2, rmin1, eps1, rmin2, eps2 = line.split()[:6] except ValueError: raise ParameterError('Could not understand LJEDIT line [%s]' % line) try: rmin1 = float(rmin1) eps1 = float(eps1) rmin2 = float(rmin2) eps2 = float(eps2) except ValueError: raise ParameterError('Could not convert LJEDIT parameters ' 'to floats.') self.nbfix_types[(min(a1, a2), max(a1, a2))] = \ (math.sqrt(eps1*eps2), rmin1+rmin2) if equivalents is not None: # We need to add the same nbfixes to all atom types that are # equivalent to the atom types defined in the LJEDIT line for oa1 in equivalents[a1]: self.nbfix_types[(min(oa1, a2), max(oa1, a2))] = \ (math.sqrt(eps1*eps2), rmin1+rmin2) for oa2 in equivalents[a2]: self.nbfix_types[(min(a1, oa2), max(a1, oa2))] = \ (math.sqrt(eps1*eps2), rmin1+rmin2) # Now do the equivalenced of atom 1 with the equivalenced of atom 2 for oa1 in equivalents[a1]: for oa2 in equivalents[a2]: self.nbfix_types[(min(oa1, oa2), max(oa1, oa2))] = \ (math.sqrt(eps1*eps2), rmin1+rmin2)
def _process_nonbond_line(self, line): try: atyp, rmin, eps = line.split()[:3] except ValueError: raise ParameterError('Could not understand nonbond parameter line ' '[%s]' % line) try: self.atom_types[atyp].set_lj_params(float(eps), float(rmin)) except KeyError: raise ParameterError('Atom type %s not present in the database.' % atyp) except ValueError: raise ParameterError('Could not convert nonbond parameters to ' 'floats [%s, %s]' % (rmin, eps))
def _process_mass_line(self, line): words = line.split() try: mass = float(words[1]) except ValueError: raise ParameterError('Could not convert mass to float [%s]' % words[1]) except IndexError: raise ParameterError('Error parsing MASS line. Not enough tokens') if words[0] in self.atom_types: self.atom_types[words[0]].mass = mass elif words[0] in ('EP', 'LP'): atype = AtomType(words[0], len(self.atom_types)+1, mass, 0) self.atom_types[words[0]] = atype else: atype = AtomType(words[0], len(self.atom_types)+1, mass, AtomicNum[element_by_mass(mass)]) self.atom_types[words[0]] = atype
def _process_angle_line(self, line): rematch = _anglere.match(line) if not rematch: raise ParameterError('Could not understand ANGLE line [%s]' % line) a1, a2, a3, k, eq = rematch.groups() a1 = a1.strip(); a2 = a2.strip(); a3 = a3.strip() typ = AngleType(float(k), float(eq)) self.angle_types[(a1, a2, a3)] = typ self.angle_types[(a3, a2, a1)] = typ
def _process_bond_line(self, line): rematch = _bondre.match(line) if not rematch: raise ParameterError('Could not understand BOND line [%s]' % line) a1, a2, k, eq = rematch.groups() a1 = a1.strip(); a2 = a2.strip() typ = BondType(float(k), float(eq)) self.bond_types[(a1, a2)] = typ self.bond_types[(a2, a1)] = typ
def _process_improper_line(self, line): rematch = _impropre.match(line) if not rematch: raise ParameterError('Could not understand IMPROPER line ' '[%s]' % line) a1, a2, a3, a4, k, phi, per = rematch.groups() a1 = a1.strip(); a2 = a2.strip(); a3 = a3.strip(); a4 = a4.strip() # Pre-sort the improper types, assuming atom3 is the central atom (which # it must be in Amber parameter files!!!!) a1, a2, a4 = sorted([a1, a2, a4]) key = (a1, a2, a3, a4) self.improper_periodic_types[key] = \ DihedralType(float(k), float(per), float(phi))
def _process_dihedral_line(self, line, finished_diheds, last_key): """ Processes a dihedral line, possibly part of a multi-term dihedral Parameters ---------- line : str Line of the file that contains a dihedral term finished_diheds : dict Dictionary of dihedral parameters whose final term has been read in already (which means additional terms will overwrite, not add) last_key : str or None If not None, this is the key for the last dihedral type that should be implied if the atom types are missing. Atom types seem to only be required for the first term in a multi-term torsion definition Returns ------- key or None If a negative periodicity indicates another term is coming, the current key is returned so it can be passed as key to the next _process_dihedral_call """ rematch = _dihedre.match(line) if not rematch and last_key is None: raise ParameterError('Could not understand DIHEDRAL line ' '[%s]' % line) elif not rematch: rematch = _dihed2re.match(line) if not rematch: raise ParameterError('Could not understand DIHEDRAL line ' '[%s]' % line) div, k, phi, per = rematch.groups() key = last_key rkey = tuple(reversed(key)) assert key in finished_diheds if finished_diheds[key]: raise AssertionError('Cannot have an implied torsion that ' 'has already finished!') else: a1, a2, a3, a4, div, k, phi, per = rematch.groups() a1, a2, a3, a4 = a1.strip(), a2.strip(), a3.strip(), a4.strip() key = (a1, a2, a3, a4) rkey = (a4, a3, a2, a1) if last_key is not None and (last_key != key and last_key != rkey): warnings.warn('Expecting next term in dihedral %r, got ' 'definition for dihedral %r' % (last_key, key), ParameterWarning) scee = [float(x) for x in _sceere.findall(line)] or [1.2] scnb = [float(x) for x in _scnbre.findall(line)] or [2.0] per = float(per) typ = DihedralType(float(k)/float(div), abs(per), float(phi), scee[0], scnb[0]) if finished_diheds[key]: # This dihedral is already finished its definition, which means # we go ahead and add a new one to override it typs = DihedralTypeList() typs.append(typ) self.dihedral_types[key] = self.dihedral_types[rkey] = typs else: self.dihedral_types[key].append(typ) finished_diheds[key] = finished_diheds[rkey] = per >= 0 if per < 0: return key
def _parse_parm_dat(self, f, line): """ Internal parser for parm.dat files from open file handle """ def fiter(): yield line for l in f: yield l # Keep yielding empty string after file has ended yield '' fiter = fiter() rawline = next(fiter) finished_diheds = defaultdict(lambda: True) # Parse the masses while rawline: line = rawline.strip() if not line: break self._process_mass_line(line) rawline = next(fiter) next(fiter) # Skip the list of hydrophobic atom types # Process the bonds rawline = next(fiter) while rawline: line = rawline.strip() if not line: break self._process_bond_line(line) rawline = next(fiter) # Process the angles rawline = next(fiter) while rawline: line = rawline.strip() if not line: break self._process_angle_line(line) rawline = next(fiter) # Process the dihedrals rawline = next(fiter) key = None while rawline: line = rawline.strip() if not line: break key = self._process_dihedral_line(line, finished_diheds, key) rawline = next(fiter) # Process the impropers rawline = next(fiter) while rawline: line = rawline.strip() if not line: break self._process_improper_line(line) rawline = next(fiter) # Process the 10-12 terms rawline = next(fiter) while rawline: line = rawline.strip() if not line: break try: a1, a2, acoef, bcoef = line.split()[:4] acoef = float(acoef) bcoef = float(bcoef) except ValueError: raise ParameterError('Trouble parsing 10-12 terms') if acoef != 0 or bcoef != 0: raise ParameterError('10-12 potential not supported in ' 'AmberParameterSet currently') rawline = next(fiter) # Process 12-6 terms. Get Equivalencing first rawline = next(fiter) equivalent_ljtypes = dict() equivalent_types = defaultdict(list) while rawline: line = rawline.strip() if not line: break words = line.split() for typ in words[1:]: equivalent_ljtypes[typ] = words[0] equivalent_types[words[0]].append(typ) rawline = next(fiter) words = next(fiter).split() if len(words) < 2: raise ParameterError('Could not parse the kind of nonbonded ' 'parameters in Amber parameter file') if words[1].upper() != 'RE': raise ParameterError('Only RE nonbonded parameters supported') rawline = next(fiter) while rawline: line = rawline.strip() if not line: break self._process_nonbond_line(line) rawline = next(fiter) # Now assign all of the equivalenced atoms for atyp, otyp in iteritems(equivalent_ljtypes): otyp = self.atom_types[otyp] if atyp in self.atom_types: if (self.atom_types[atyp].rmin is not None and self.atom_types[atyp].epsilon is not None): if (abs(otyp.epsilon-self.atom_types[atyp].epsilon) > TINY or abs(otyp.rmin-self.atom_types[atyp].rmin) > TINY): warnings.warn('Equivalency defined between %s and %s ' 'but parameters are not equal' % (otyp.name, atyp), AmberWarning) # Remove from equivalent types equivalent_types[otyp.name].remove(atyp) continue self.atom_types[atyp].set_lj_params(otyp.epsilon, otyp.rmin) line = next(fiter).strip() if line == 'LJEDIT': rawline = next(fiter) while rawline: line = rawline.strip() if not line: break self._process_nbfix_line(line, equivalent_types) rawline = next(fiter)
def from_leaprc(cls, fname, search_oldff=False): """ Load a parameter set from a leaprc file Parameters ---------- fname : str or file-like Name of the file or open file-object from which a leaprc-style file will be read search_oldff : bool, optional, default=False If True, search the oldff directories in the main Amber leap folders. Default is False Notes ----- This does not read all parts of a leaprc file -- only those pertinent to defining force field information. For instance, the following sections and commands are processed: - addAtomTypes - loadAmberParams - loadOFF - loadMol2 - loadMol3 """ params = cls() if isinstance(fname, string_types): f = genopen(fname, 'r') own_handle = True else: f = fname own_handle = False # To make parsing easier, and because leaprc files are usually quite # short, I'll read the whole file into memory def joinlines(lines): newlines = [] composite = [] for line in lines: if line.endswith('\\\n'): composite.append(line[:-2]) continue else: composite.append(line) newlines.append(''.join(composite)) composite = [] if composite: newlines.append(''.join(composite)) return newlines lines = joinlines(map(lambda line: line if '#' not in line else line[:line.index('#')], f)) text = ''.join(lines) if own_handle: f.close() lowertext = text.lower() # commands are case-insensitive # Now process the parameter files def process_fname(fname): if fname[0] in ('"', "'"): fname = fname[1:-1] fname = fname.replace('_BSTOKEN_', r'\ ').replace(r'\ ', ' ') return fname for line in lines: line = line.replace(r'\ ', '_BSTOKEN_') if _loadparamsre.findall(line): fname = process_fname(_loadparamsre.findall(line)[0]) params.load_parameters(_find_amber_file(fname, search_oldff)) elif _loadoffre.findall(line): fname = process_fname(_loadoffre.findall(line)[0]) params.residues.update( AmberOFFLibrary.parse(_find_amber_file(fname, search_oldff)) ) elif _loadmol2re.findall(line): (resname, fname), = _loadmol2re.findall(line) residue = Mol2File.parse(_find_amber_file(fname, search_oldff)) if isinstance(residue, ResidueTemplateContainer): warnings.warn('Multi-residue mol2 files not supported by ' 'tleap. Loading anyway using names in mol2', AmberWarning) for res in residue: params.residues[res.name] = res else: params.residues[resname] = residue # Now process the addAtomTypes try: idx = lowertext.index('addatomtypes') except ValueError: # Does not exist in this file atom_types_str = '' else: i = idx + len('addatomtypes') while i < len(text) and text[i] != '{': if text[i] not in '\r\n\t ': raise ParameterError('Unsupported addAtomTypes syntax in ' 'leaprc file') i += 1 if i == len(text): raise ParameterError('Unsupported addAtomTypes syntax in ' 'leaprc file') # We are at our first brace chars = [] nopen = 1 i += 1 while i < len(text): char = text[i] if char == '{': nopen += 1 elif char == '}': nopen -= 1 if nopen == 0: break elif char == '\n': char = ' ' chars.append(char) i += 1 atom_types_str = ''.join(chars).strip() for _, name, symb, hyb in _atomtypere.findall(atom_types_str): if symb not in AtomicNum: raise ParameterError('%s is not a recognized element' % symb) if name in params.atom_types: params.atom_types[name].atomic_number = AtomicNum[symb] return params
def load_parameters(self, parmset, copy_parameters=True): """ Loads parameters from a parameter set that was loaded via CHARMM RTF, PAR, and STR files. Parameters ---------- parmset : :class:`CharmmParameterSet` List of all parameters copy_parameters : bool, optional, default=True If False, parmset will not be copied. WARNING: ------- Not copying parmset will cause ParameterSet and Structure to share references to types. If you modify the original parameter set, the references in Structure list_types will be silently modified. However, if you change any reference in the parameter set, then that reference will no longer be shared with structure. Example where the reference in ParameterSet is changed. The following will NOT modify the parameters in the psf:: psf.load_parameters(parmset, copy_parameters=False) parmset.angle_types[('a1', 'a2', a3')] = AngleType(1, 2) The following WILL change the parameter in the psf because the reference has not been changed in ``ParameterSet``:: psf.load_parameters(parmset, copy_parameters=False) a = parmset.angle_types[('a1', 'a2', 'a3')] a.k = 10 a.theteq = 100 Extra care should be taken when trying this with dihedral_types. Since dihedral_type is a Fourier sequence, ParameterSet stores DihedralType for every term in DihedralTypeList. Therefore, the example below will STILL modify the type in the :class:`Structure` list_types:: parmset.dihedral_types[('a', 'b', 'c', 'd')][0] = DihedralType(1, 2, 3) This assigns a new instance of DihedralType to an existing DihedralTypeList that ParameterSet and Structure are tracking and the shared reference is NOT changed. Use with caution! Notes ----- - If any dihedral or improper parameters cannot be found, I will try inserting wildcards (at either end for dihedrals and as the two central atoms in impropers) and see if that matches. Wild-cards will apply ONLY if specific parameters cannot be found. - This method will expand the dihedrals attribute by adding a separate Dihedral object for each term for types that have a multi-term expansion Raises ------ ParameterError if any parameters cannot be found """ if copy_parameters: parmset = _copy(parmset) self.combining_rule = parmset.combining_rule # First load the atom types for atom in self.atoms: try: if isinstance(atom.type, int): atype = parmset.atom_types_int[atom.type] else: atype = parmset.atom_types_str[atom.type] except KeyError: raise ParameterError('Could not find atom type for %s' % atom.type) atom.atom_type = atype # Change to string type to look up the rest of the parameters atom.type = str(atom.atom_type) atom.atomic_number = atype.atomic_number # Next load all of the bonds for bond in self.bonds: # Construct the key key = (min(bond.atom1.type, bond.atom2.type), max(bond.atom1.type, bond.atom2.type)) try: bond.type = parmset.bond_types[key] except KeyError: raise ParameterError('Missing bond type for %r' % bond) bond.type.used = False # Build the bond_types list del self.bond_types[:] for bond in self.bonds: if bond.type.used: continue bond.type.used = True self.bond_types.append(bond.type) bond.type.list = self.bond_types # Next load all of the angles. If a Urey-Bradley term is defined for # this angle, also build the urey_bradley and urey_bradley_type lists del self.urey_bradleys[:] for ang in self.angles: # Construct the key key = (min(ang.atom1.type, ang.atom3.type), ang.atom2.type, max(ang.atom1.type, ang.atom3.type)) try: ang.type = parmset.angle_types[key] ang.type.used = False ubt = parmset.urey_bradley_types[key] if ubt is not NoUreyBradley: ub = UreyBradley(ang.atom1, ang.atom3, ubt) self.urey_bradleys.append(ub) ubt.used = False except KeyError: raise ParameterError('Missing angle type for %r' % ang) del self.urey_bradley_types[:] del self.angle_types[:] for ub in self.urey_bradleys: if ub.type.used: continue ub.type.used = True self.urey_bradley_types.append(ub.type) ub.type.list = self.urey_bradley_types for ang in self.angles: if ang.type.used: continue ang.type.used = True self.angle_types.append(ang.type) ang.type.list = self.angle_types # Next load all of the dihedrals. active_dih_list = set() for dih in self.dihedrals: # Store the atoms a1, a2, a3, a4 = dih.atom1, dih.atom2, dih.atom3, dih.atom4 key = (a1.type, a2.type, a3.type, a4.type) # First see if the exact dihedral is specified if not key in parmset.dihedral_types: # Check for wild-cards key = ('X', a2.type, a3.type, 'X') if not key in parmset.dihedral_types: raise ParameterError('No dihedral parameters found for ' '%r' % dih) dih.type = parmset.dihedral_types[key] dih.type.used = False pair = (dih.atom1.idx, dih.atom4.idx) # To determine exclusions if (dih.atom1 in dih.atom4.bond_partners or dih.atom1 in dih.atom4.angle_partners): dih.ignore_end = True elif pair in active_dih_list: dih.ignore_end = True else: active_dih_list.add(pair) active_dih_list.add((dih.atom4.idx, dih.atom1.idx)) del self.dihedral_types[:] for dihedral in self.dihedrals: if dihedral.type.used: continue dihedral.type.used = True self.dihedral_types.append(dihedral.type) dihedral.type.list = self.dihedral_types # Now do the impropers for imp in self.impropers: # Store the atoms a1, a2, a3, a4 = imp.atom1, imp.atom2, imp.atom3, imp.atom4 at1, at2, at3, at4 = a1.type, a2.type, a3.type, a4.type key = tuple(sorted([at1, at2, at3, at4])) altkey1 = a1.type, a2.type, a3.type, a4.type altkey2 = a4.type, a3.type, a2.type, a1.type # Check for exact harmonic or exact periodic if key in parmset.improper_types: imp.type = parmset.improper_types[key] elif key in parmset.improper_periodic_types: imp.type = parmset.improper_periodic_types[key] elif altkey1 in parmset.improper_periodic_types: imp.type = parmset.improper_periodic_types[altkey1] elif altkey2 in parmset.improper_periodic_types: imp.type = parmset.improper_periodic_types[altkey2] else: # Check for wild-card harmonic for anchor in (at2, at3, at4): key = tuple(sorted([at1, anchor, 'X', 'X'])) if key in parmset.improper_types: imp.type = parmset.improper_types[key] break # Check for wild-card periodic if key not in parmset.improper_types: for anchor in (at2, at3, at4): key = tuple(sorted([at1, anchor, 'X', 'X'])) if key in parmset.improper_periodic_types: imp.type = parmset.improper_periodic_types[key] break # Not found anywhere if key not in parmset.improper_periodic_types: raise ParameterError( 'No improper parameters found for ' '%r' % imp) imp.type.used = False # prepare list of harmonic impropers present in system del self.improper_types[:] for improper in self.impropers: if improper.type.used: continue improper.type.used = True if isinstance(improper.type, ImproperType): self.improper_types.append(improper.type) improper.type.list = self.improper_types elif isinstance(improper.type, DihedralType): self.dihedral_types.append(improper.type) improper.type.list = self.dihedral_types else: assert False, 'Should not be here' # Look through the list of impropers -- if there are any periodic # impropers, move them over to the dihedrals list for i in reversed(range(len(self.impropers))): if isinstance(self.impropers[i].type, DihedralType): imp = self.impropers.pop(i) dih = Dihedral(imp.atom1, imp.atom2, imp.atom3, imp.atom4, improper=True, ignore_end=True, type=imp.type) imp.delete() self.dihedrals.append(dih) # Now do the cmaps. These will not have wild-cards for cmap in self.cmaps: key = (cmap.atom1.type, cmap.atom2.type, cmap.atom3.type, cmap.atom4.type, cmap.atom2.type, cmap.atom3.type, cmap.atom4.type, cmap.atom5.type) try: cmap.type = parmset.cmap_types[key] except KeyError: raise ParameterError('No CMAP parameters found for %r' % cmap) cmap.type.used = False del self.cmap_types[:] for cmap in self.cmaps: if cmap.type.used: continue cmap.type.used = True self.cmap_types.append(cmap.type) cmap.type.list = self.cmap_types
def load_parameters(self, parmset): """ Loads parameters from a parameter set that was loaded via CHARMM RTF, PAR, and STR files. Parameters ---------- parmset : :class:`CharmmParameterSet` List of all parameters Notes ----- - If any dihedral or improper parameters cannot be found, I will try inserting wildcards (at either end for dihedrals and as the two central atoms in impropers) and see if that matches. Wild-cards will apply ONLY if specific parameters cannot be found. - This method will expand the dihedrals attribute by adding a separate Dihedral object for each term for types that have a multi-term expansion Raises ------ ParameterError if any parameters cannot be found """ parmset = _copy(parmset) self.combining_rule = parmset.combining_rule # First load the atom types for atom in self.atoms: try: if isinstance(atom.type, int): atype = parmset.atom_types_int[atom.type] else: atype = parmset.atom_types_str[atom.type] except KeyError: raise ParameterError('Could not find atom type for %s' % atom.type) atom.atom_type = atype # Change to string type to look up the rest of the parameters atom.type = str(atom.atom_type) atom.atomic_number = atype.atomic_number # Next load all of the bonds for bond in self.bonds: # Construct the key key = (min(bond.atom1.type, bond.atom2.type), max(bond.atom1.type, bond.atom2.type)) try: bond.type = parmset.bond_types[key] except KeyError: raise ParameterError('Missing bond type for %r' % bond) bond.type.used = False # Build the bond_types list del self.bond_types[:] for bond in self.bonds: if bond.type.used: continue bond.type.used = True self.bond_types.append(bond.type) bond.type.list = self.bond_types # Next load all of the angles. If a Urey-Bradley term is defined for # this angle, also build the urey_bradley and urey_bradley_type lists del self.urey_bradleys[:] for ang in self.angles: # Construct the key key = (min(ang.atom1.type, ang.atom3.type), ang.atom2.type, max(ang.atom1.type, ang.atom3.type)) try: ang.type = parmset.angle_types[key] ang.type.used = False ubt = parmset.urey_bradley_types[key] if ubt is not NoUreyBradley: ub = UreyBradley(ang.atom1, ang.atom3, ubt) self.urey_bradleys.append(ub) ubt.used = False except KeyError: raise ParameterError('Missing angle type for %r' % ang) del self.urey_bradley_types[:] del self.angle_types[:] for ub in self.urey_bradleys: if ub.type.used: continue ub.type.used = True self.urey_bradley_types.append(ub.type) ub.type.list = self.urey_bradley_types for ang in self.angles: if ang.type.used: continue ang.type.used = True self.angle_types.append(ang.type) ang.type.list = self.angle_types # Next load all of the dihedrals. active_dih_list = set() for dih in self.dihedrals: # Store the atoms a1, a2, a3, a4 = dih.atom1, dih.atom2, dih.atom3, dih.atom4 key = (a1.type, a2.type, a3.type, a4.type) # First see if the exact dihedral is specified if not key in parmset.dihedral_types: # Check for wild-cards key = ('X', a2.type, a3.type, 'X') if not key in parmset.dihedral_types: raise ParameterError('No dihedral parameters found for ' '%r' % dih) dih.type = parmset.dihedral_types[key] dih.type.used = False pair = (dih.atom1.idx, dih.atom4.idx) # To determine exclusions if (dih.atom1 in dih.atom4.bond_partners or dih.atom1 in dih.atom4.angle_partners): dih.ignore_end = True elif pair in active_dih_list: dih.ignore_end = True else: active_dih_list.add(pair) active_dih_list.add((dih.atom4.idx, dih.atom1.idx)) del self.dihedral_types[:] for dihedral in self.dihedrals: if dihedral.type.used: continue dihedral.type.used = True self.dihedral_types.append(dihedral.type) dihedral.type.list = self.dihedral_types # Now do the impropers for imp in self.impropers: # Store the atoms a1, a2, a3, a4 = imp.atom1, imp.atom2, imp.atom3, imp.atom4 at1, at2, at3, at4 = a1.type, a2.type, a3.type, a4.type key = tuple(sorted([at1, at2, at3, at4])) # Check for exact harmonic or exact periodic if key in parmset.improper_types: imp.type = parmset.improper_types[key] elif key in parmset.improper_periodic_types: imp.type = parmset.improper_periodic_types[key] else: # Check for wild-card harmonic for anchor in (at2, at3, at4): key = tuple(sorted([at1, anchor, 'X', 'X'])) if key in parmset.improper_types: imp.type = parmset.improper_types[key] break # Check for wild-card periodic if key not in parmset.improper_types: for anchor in (at2, at3, at4): key = tuple(sorted([at1, anchor, 'X', 'X'])) if key in parmset.improper_periodic_types: imp.type = parmset.improper_periodic_types[key] break # Not found anywhere if key not in parmset.improper_periodic_types: raise ParameterError('No improper parameters found for' '%r' % imp) imp.type.used = False # prepare list of harmonic impropers present in system del self.improper_types[:] for improper in self.impropers: if improper.type.used: continue improper.type.used = True if isinstance(improper.type, ImproperType): self.improper_types.append(improper.type) improper.type.list = self.improper_types elif isinstance(improper.type, DihedralType): self.dihedral_types.append(improper.type) improper.type.list = self.dihedral_types else: assert False, 'Should not be here' # Look through the list of impropers -- if there are any periodic # impropers, move them over to the dihedrals list for i in reversed(range(len(self.impropers))): if isinstance(self.impropers[i].type, DihedralType): imp = self.impropers.pop(i) dih = Dihedral(imp.atom1, imp.atom2, imp.atom3, imp.atom4, improper=True, ignore_end=True, type=imp.type) imp.delete() self.dihedrals.append(dih) # Now do the cmaps. These will not have wild-cards for cmap in self.cmaps: key = (cmap.atom1.type, cmap.atom2.type, cmap.atom3.type, cmap.atom4.type, cmap.atom2.type, cmap.atom3.type, cmap.atom4.type, cmap.atom5.type) try: cmap.type = parmset.cmap_types[key] except KeyError: raise ParameterError('No CMAP parameters found for %r' % cmap) cmap.type.used = False del self.cmap_types[:] for cmap in self.cmaps: if cmap.type.used: continue cmap.type.used = True self.cmap_types.append(cmap.type) cmap.type.list = self.cmap_types
def from_structure(cls, struct, allow_unequal_duplicates=True): """ Extracts known parameters from a Structure instance Parameters ---------- struct : :class:`parmed.structure.Structure` The parametrized ``Structure`` instance from which to extract parameters into a ParameterSet allow_unequal_duplicates : bool, optional If True, if two or more unequal parameter types are defined by the same atom types, the last one encountered will be assigned. If False, an exception will be raised. Default is True Returns ------- params : :class:`ParameterSet` The parameter set with all parameters defined in the Structure Notes ----- The parameters here are copies of the ones in the Structure, so modifying the generated ParameterSet will have no effect on ``struct``. Furthermore, the *first* occurrence of each parameter will be used. If future ones differ, they will be silently ignored, since this is expected behavior in some instances (like with Gromacs topologies in the ff99sb-ildn force field) unless ``allow_unequal_duplicates`` is set to ``False`` Dihedrals are a little trickier. They can be multi-term, which can be represented either as a *single* entry in dihedrals with a type of DihedralTypeList or multiple entries in dihedrals with a DihedralType parameter type. In this case, the parameter is constructed from either the first DihedralTypeList found or the first DihedralType of each periodicity found if no matching DihedralTypeList is found. Raises ------ :class:`parmed.exceptions.ParameterError` if allow_unequal_duplicates is False and 2+ unequal parameters are defined between the same atom types. `NotImplementedError` if any AMOEBA potential terms are defined in the input structure """ params = cls() found_dihed_type_list = dict() for atom in struct.atoms: if atom.atom_type in (UnassignedAtomType, None): atom_type = AtomType(atom.type, None, atom.mass, atom.atomic_number) atom_type.set_lj_params(atom.epsilon, atom.rmin, atom.epsilon_14, atom.rmin_14) params.atom_types[atom.type] = atom_type else: atom_type = copy(atom.atom_type) params.atom_types[str(atom_type)] = atom_type if atom_type.number is not None: params.atom_types_int[int(atom_type)] = atom_type params.atom_types_tuple[(int(atom_type), str(atom_type))] =\ atom_type for bond in struct.bonds: if bond.type is None: continue key = (bond.atom1.type, bond.atom2.type) if key in params.bond_types: if (not allow_unequal_duplicates and params.bond_types[key] != bond.type): raise ParameterError('Unequal bond types defined between ' '%s and %s' % key) continue # pragma: no cover typ = copy(bond.type) key = (bond.atom1.type, bond.atom2.type) params.bond_types[key] = typ params.bond_types[tuple(reversed(key))] = typ for angle in struct.angles: if angle.type is None: continue key = (angle.atom1.type, angle.atom2.type, angle.atom3.type) if key in params.angle_types: if (not allow_unequal_duplicates and params.angle_types[key] != angle.type): raise ParameterError('Unequal angle types defined between ' '%s, %s, and %s' % key) continue # pragma: no cover typ = copy(angle.type) key = (angle.atom1.type, angle.atom2.type, angle.atom3.type) params.angle_types[key] = typ params.angle_types[tuple(reversed(key))] = typ if angle.funct == 5: key = (angle.atom1.type, angle.atom3.type) params.urey_bradley_types[key] = NoUreyBradley params.urey_bradley_types[tuple(reversed(key))] = NoUreyBradley for dihedral in struct.dihedrals: if dihedral.type is None: continue key = (dihedral.atom1.type, dihedral.atom2.type, dihedral.atom3.type, dihedral.atom4.type) if dihedral.improper: key = cls._periodic_improper_key( dihedral.atom1, dihedral.atom2, dihedral.atom3, dihedral.atom4, ) if key in params.improper_periodic_types: if (not allow_unequal_duplicates and params.improper_periodic_types[key] != dihedral.type): raise ParameterError('Unequal dihedral types defined ' 'between %s, %s, %s, and %s' % key) continue # pragma: no cover typ = copy(dihedral.type) params.improper_periodic_types[key] = typ else: # Proper dihedral. Look out for multi-term forms if (key in params.dihedral_types and found_dihed_type_list[key]): # Already found a multi-term dihedral type list if not allow_unequal_duplicates: if isinstance(dihedral.type, DihedralTypeList): if params.dihedral_types[key] != dihedral.type: raise ParameterError( 'Unequal dihedral types ' 'defined between %s, %s, %s, and %s' % key) elif isinstance(dihedral.type, DihedralType): for dt in params.dihedral_types[key]: if dt == dihedral.type: break else: raise ParameterError( 'Unequal dihedral types ' 'defined between %s, %s, %s, and %s' % key) continue # pragma: no cover elif key in params.dihedral_types: # We have one term of a potentially multi-term dihedral. if isinstance(dihedral.type, DihedralTypeList): # This is a full Fourier series list found_dihed_type_list[key] = True found_dihed_type_list[tuple(reversed(key))] = True typ = copy(dihedral.type) params.dihedral_types[key] = typ params.dihedral_types[tuple(reversed(key))] = typ else: # This *might* be another term. Make sure another term # with its periodicity does not already exist for t in params.dihedral_types[key]: if t.per == dihedral.type.per: if (not allow_unequal_duplicates and t != dihedral.type): raise ParameterError( 'Unequal dihedral ' 'types defined bewteen %s, %s, %s, ' 'and %s' % key) break else: # If we got here, we did NOT find this periodicity. # And since this is mutating a list in-place, it # automatically propagates to the reversed key typ = copy(dihedral.type) params.dihedral_types[key].append(typ) else: # New parameter. If it's a DihedralTypeList, assign it and # be done with it. If it's a DihedralType, start a # DihedralTypeList to be added to later. if isinstance(dihedral.type, DihedralTypeList): found_dihed_type_list[key] = True found_dihed_type_list[tuple(reversed(key))] = True typ = copy(dihedral.type) params.dihedral_types[key] = typ params.dihedral_types[tuple(reversed(key))] = typ else: found_dihed_type_list[key] = False found_dihed_type_list[tuple(reversed(key))] = False typ = DihedralTypeList() typ.append(copy(dihedral.type)) params.dihedral_types[key] = typ params.dihedral_types[tuple(reversed(key))] = typ for improper in struct.impropers: if improper.type is None: continue key = (improper.atom1.type, improper.atom2.type, improper.atom3.type, improper.atom4.type) if key in params.improper_types: if (not allow_unequal_duplicates and params.improper_types[key] != improper.type): raise ParameterError('Unequal improper types defined ' 'between %s, %s, %s, and %s' % key) continue # pragma: no cover params.improper_types[key] = copy(improper.type) for cmap in struct.cmaps: if cmap.type is None: continue key = (cmap.atom1.type, cmap.atom2.type, cmap.atom3.type, cmap.atom4.type, cmap.atom2.type, cmap.atom3.type, cmap.atom4.type, cmap.atom5.type) if key in params.cmap_types: if (not allow_unequal_duplicates and cmap.type != params.cmap_types[key]): raise ParameterError( 'Unequal CMAP types defined between ' '%s, %s, %s, %s, and %s' % (key[0], key[1], key[2], key[3], key[7])) continue # pragma: no cover typ = copy(cmap.type) params.cmap_types[key] = typ params.cmap_types[tuple(reversed(key))] = typ for urey in struct.urey_bradleys: if urey.type is None or urey.type is NoUreyBradley: continue key = (urey.atom1.type, urey.atom2.type) if key not in params.urey_bradley_types: warnings.warn('Angle corresponding to Urey-Bradley type not ' 'found') typ = copy(urey.type) params.urey_bradley_types[key] = typ params.urey_bradley_types[tuple(reversed(key))] = typ for adjust in struct.adjusts: if adjust.type is None: continue key = (adjust.atom1.type, adjust.atom2.type) if key in params.pair_types: if (not allow_unequal_duplicates and params.pair_types[key] != adjust.type): raise ParameterError('Unequal pair types defined between ' '%s and %s' % key) continue # pragma: no cover typ = copy(adjust.type) params.pair_types[key] = typ params.pair_types[tuple(reversed(key))] = typ # Trap for Amoeba potentials if (struct.trigonal_angles or struct.out_of_plane_bends or struct.torsion_torsions or struct.stretch_bends or struct.trigonal_angles or struct.pi_torsions): raise NotImplementedError('Cannot extract parameters from an ' 'Amoeba-parametrized system yet') return params