def __init__(self, fname, seq=None): super(XyzFile, self).__init__() if isinstance(fname, string_types): fxyz = genopen(fname, 'r') own_handle_xyz = True else: fxyz = fname own_handle_xyz = False if seq is not None: seqstruct = load_file(seq) # Now parse the file try: natom = int(fxyz.readline().split()[0]) except (ValueError, IndexError): raise TinkerError('Bad XYZ file format; first line') if seq is not None and natom != len(seqstruct.atoms): raise ValueError('Sequence file %s # of atoms does not match the # ' 'of atoms in the XYZ file' % seq) words = fxyz.readline().split() if len(words) == 6 and not XyzFile._check_atom_record(words): self.box = [float(w) for w in words] words = fxyz.readline().split() atom = Atom(atomic_number=AtomicNum[element_by_name(words[1])], name=words[1], type=words[5]) atom.xx, atom.xy, atom.xz = [float(w) for w in words[2:5]] residue = Residue('SYS') residue.number = 1 residue._idx = 0 if seq is not None: residue = seqstruct.residues[0] self.add_atom(atom, residue.name, residue.number, residue.chain, residue.insertion_code, residue.segid) bond_ids = [[int(w) for w in words[6:]]] for i, line in enumerate(fxyz): words = line.split() atom = Atom(atomic_number=AtomicNum[element_by_name(words[1])], name=words[1], type=words[5]) atom.xx, atom.xy, atom.xz = [float(w) for w in words[2:5]] if seq is not None: residue = seqstruct.atoms[i+1].residue self.add_atom(atom, residue.name, residue.number, residue.chain, residue.insertion_code, residue.segid) bond_ids.append([int(w) for w in words[6:]]) # All of the bonds are stored now -- go ahead and make them now for atom, bonds in zip(self.atoms, bond_ids): i = atom.idx + 1 for idx in bonds: if idx > i: self.bonds.append(Bond(atom, self.atoms[idx-1])) if own_handle_xyz: fxyz.close()
def parse(cls, filename, structure=False): self = cls(filename) if structure: obj = Structure() for _ in range(self.natom): # fake obj.add_atom(Atom(), resname='XXX', resnum=0) obj.box = self.box obj.coordinates = self.coordinates[0] obj._coordinates = self.coordinates return obj else: return self
def __init__(self, fname, seq=None): super(XyzFile, self).__init__() if isinstance(fname, string_types): fxyz = genopen(fname, 'r') own_handle_xyz = True else: fxyz = fname own_handle_xyz = False if seq is not None: seqstruct = load_file(seq) # Now parse the file try: natom = int(fxyz.readline().split()[0]) except (ValueError, IndexError): raise TinkerError('Bad XYZ file format; first line') if seq is not None and natom != len(seqstruct.atoms): raise ValueError( 'Sequence file %s # of atoms does not match the # ' 'of atoms in the XYZ file' % seq) words = fxyz.readline().split() if len(words) == 6 and not XyzFile._check_atom_record(words): self.box = [float(w) for w in words] words = fxyz.readline().split() atom = Atom(atomic_number=AtomicNum[element_by_name(words[1])], name=words[1], type=words[5]) atom.xx, atom.xy, atom.xz = [float(w) for w in words[2:5]] residue = Residue('SYS') residue.number = 1 residue._idx = 0 if seq is not None: residue = seqstruct.residues[0] self.add_atom(atom, residue.name, residue.number, residue.chain, residue.insertion_code, residue.segid) bond_ids = [[int(w) for w in words[6:]]] for i, line in enumerate(fxyz): words = line.split() atom = Atom(atomic_number=AtomicNum[element_by_name(words[1])], name=words[1], type=words[5]) atom.xx, atom.xy, atom.xz = [float(w) for w in words[2:5]] if seq is not None: residue = seqstruct.atoms[i + 1].residue self.add_atom(atom, residue.name, residue.number, residue.chain, residue.insertion_code, residue.segid) bond_ids.append([int(w) for w in words[6:]]) # All of the bonds are stored now -- go ahead and make them now for atom, bonds in zip(self.atoms, bond_ids): i = atom.idx + 1 for idx in bonds: if idx > i: self.bonds.append(Bond(atom, self.atoms[idx - 1])) if own_handle_xyz: fxyz.close()
def read(self, line): """ Reads a line Parameters ---------- line : str A line with an atom record from a GRO file Returns ------- atom, resname, resnum : Atom, str, int The Atom instance, residue name, and residue number containing the atom """ resnum = int(line[:5]) resname = line[5:10].strip() atomname = line[10:15].strip() elem = element_by_name(atomname) atomic_number = AtomicNum[elem] mass = Mass[elem] atnum = int(line[15:20]) if atomic_number == 0: atom = ExtraPoint(name=atomname, number=atnum) else: atom = Atom(atomic_number=atomic_number, name=atomname, number=atnum, mass=mass) if self._digits is None: self._pdeci = line.index('.', 20) self._ndeci = line.index('.', self._pdeci+1) self._digits = self._ndeci - self._pdeci atom.xx, atom.xy, atom.xz = ( float(line[20+i*self._digits:20+(i+1)*self._digits])*10 for i in range(3) ) wbeg = 20 + self._digits * 3 wend = wbeg + self._digits if line[wbeg:wend].strip(): atom.vx, atom.vy, atom.vz = ( float(line[wbeg+i*self._digits:wend+i*self._digits])*10 for i in range(3) ) return atom, resname, resnum
def create_random_structure(parametrized, novalence=False): """ Create a random Structure with random attributes Parameters ---------- parametrized : bool If True, add at least two of all kinds of parameters to the generated random structure. If False, just fill in the atoms and residues and some random valence terms, but no "types" novalence : bool, optional If True, no valence terms will be added. Default is False. This is set to False if parametrized is True """ from parmed.topologyobjects import ( Atom, Bond, AtomType, BondType, AngleType, DihedralType, ImproperType, CmapType, OutOfPlaneBendType, StretchBendType, TorsionTorsionType, AmoebaNonbondedExceptionType, Angle, UreyBradley, Dihedral, Improper, Cmap, TrigonalAngle, OutOfPlaneBend, StretchBend, PiTorsion, TorsionTorsion, AcceptorDonor, Group, ChiralFrame, MultipoleFrame, NonbondedException, RBTorsionType) from parmed import structure from copy import copy if parametrized: novalence = False # Generate random atom and parameter types atom_types = [ AtomType(''.join(random.sample(uppercase, 3)), i, random.random() * 16 + 1, random.randint(1, 8)) for i in range(random.randint(8, 20)) ] bond_types = [ BondType(random.random() * 2, random.random() * 100) for i in range(random.randint(10, 20)) ] angle_types = [ AngleType(random.random() * 50, random.random() * 120) for i in range(random.randint(10, 20)) ] dihed_types = [ DihedralType(random.random() * 10, random.randint(1, 6), random.choice([0, 180])) for i in range(random.randint(10, 20)) ] rb_types = [RBTorsionType(*[random.random() * 10 for i in range(6)])] imp_types = [ ImproperType(random.random() * 100, random.choice([0, 180])) for i in range(random.randint(10, 20)) ] cmap_types = [ CmapType(24, [random.random() * 5 for i in range(24 * 24)]) for i in range(random.randint(5, 10)) ] oop_types = [ OutOfPlaneBendType(random.random() * 100) for i in range(random.randint(10, 20)) ] strbnd_types = [ StretchBendType(random.random() * 10, random.random() * 10, random.random() * 2, random.random() * 2, random.random() * 120) for i in range(random.randint(10, 20)) ] ang1, ang2 = list(range(-180, 180, 36)), list(range(-180, 180, 18)) tortor_types = [ TorsionTorsionType((10, 20), ang1[:], ang2[:], [random.random() * 10 for j in range(200)]) for i in range(random.randint(5, 10)) ] for typ in atom_types: typ.set_lj_params(random.random() * 2, random.random() * 2) struct = structure.Structure() # Add atoms in residues for res in range(random.randint(20, 30)): resname = ''.join(random.sample(uppercase, 3)) resid = res + 1 for i in range(random.randint(10, 25)): name = ''.join(random.sample(uppercase, 4)) if parametrized: typ = random.choice(atom_types) type = str(typ) mass = typ.mass atomic_number = typ.atomic_number else: type = ''.join(random.sample(uppercase, 3)) mass = random.random() * 16 + 1 atomic_number = random.randint(1, 8) charge = random.random() * 2 - 1 solvent_radius = random.random() * 2 screen = random.random() * 2 atom = Atom(atomic_number=atomic_number, type=type, charge=charge, mass=mass, solvent_radius=solvent_radius, screen=screen, name=name) if parametrized: atom.atom_type = typ struct.add_atom(atom, resname, resid) if novalence: return struct # Possibly add parameter type lists if parametrized: struct.bond_types.extend([copy(x) for x in bond_types]) struct.bond_types.claim() struct.angle_types.extend([copy(x) for x in angle_types]) struct.angle_types.claim() struct.dihedral_types.extend([copy(x) for x in dihed_types]) struct.dihedral_types.claim() struct.rb_torsion_types.extend([copy(x) for x in rb_types]) struct.rb_torsion_types.claim() struct.urey_bradley_types.extend([copy(x) for x in bond_types]) struct.urey_bradley_types.claim() struct.improper_types.extend([copy(x) for x in imp_types]) struct.improper_types.claim() struct.cmap_types.extend([copy(x) for x in cmap_types]) struct.cmap_types.claim() struct.trigonal_angle_types.extend([copy(x) for x in angle_types]) struct.trigonal_angle_types.claim() struct.out_of_plane_bend_types.extend([copy(x) for x in oop_types]) struct.out_of_plane_bend_types.claim() struct.pi_torsion_types.extend([copy(x) for x in dihed_types]) struct.pi_torsion_types.claim() struct.stretch_bend_types.extend([copy(x) for x in strbnd_types]) struct.stretch_bend_types.claim() struct.torsion_torsion_types.extend([copy(x) for x in tortor_types]) struct.torsion_torsion_types.claim() struct.adjust_types.extend([ AmoebaNonbondedExceptionType(0.5, 0.5, 0.6, 0.6, 0.7) for i in range(random.randint(10, 20)) ]) struct.adjust_types.claim() # Add valence terms with optional for i in range(random.randint(40, 50)): struct.bonds.append(Bond(*random.sample(struct.atoms, 2))) if parametrized: struct.bonds[-1].type = random.choice(struct.bond_types) for i in range(random.randint(35, 45)): struct.angles.append(Angle(*random.sample(struct.atoms, 3))) if parametrized: struct.angles[-1].type = random.choice(struct.angle_types) for i in range(random.randint(35, 45)): struct.urey_bradleys.append( UreyBradley(*random.sample(struct.atoms, 2))) if parametrized: struct.urey_bradleys[-1].type = random.choice( struct.urey_bradley_types) for i in range(random.randint(30, 40)): struct.dihedrals.append( Dihedral(*random.sample(struct.atoms, 4), improper=random.choice([True, False]))) if parametrized: struct.dihedrals[-1].type = random.choice(struct.dihedral_types) for i in range(random.randint(30, 40)): struct.rb_torsions.append(Dihedral(*random.sample(struct.atoms, 4))) if parametrized: struct.rb_torsions[-1].type = random.choice( struct.rb_torsion_types) for i in range(random.randint(10, 20)): struct.impropers.append(Improper(*random.sample(struct.atoms, 4))) if parametrized: struct.impropers[-1].type = random.choice(struct.improper_types) for i in range(random.randint(25, 35)): struct.cmaps.append(Cmap(*random.sample(struct.atoms, 5))) if parametrized: struct.cmaps[-1].type = random.choice(struct.cmap_types) for i in range(random.randint(30, 40)): struct.trigonal_angles.append( TrigonalAngle(*random.sample(struct.atoms, 4))) if parametrized: struct.trigonal_angles[-1].type = random.choice( struct.trigonal_angle_types) for i in range(random.randint(30, 40)): struct.out_of_plane_bends.append( OutOfPlaneBend(*random.sample(struct.atoms, 4))) if parametrized: struct.out_of_plane_bends[-1].type = random.choice( struct.out_of_plane_bend_types) for i in range(random.randint(30, 40)): struct.stretch_bends.append( StretchBend(*random.sample(struct.atoms, 3))) if parametrized: struct.stretch_bends[-1].type = random.choice( struct.stretch_bend_types) for i in range(random.randint(20, 30)): struct.pi_torsions.append(PiTorsion(*random.sample(struct.atoms, 6))) if parametrized: struct.pi_torsions[-1].type = random.choice( struct.pi_torsion_types) for i in range(random.randint(10, 20)): struct.torsion_torsions.append( TorsionTorsion(*random.sample(struct.atoms, 5))) if parametrized: struct.torsion_torsions[-1].type = random.choice( struct.torsion_torsion_types) # Now use some lesser-used features for i in range(random.randint(5, 10)): struct.acceptors.append(AcceptorDonor(*random.sample(struct.atoms, 2))) struct.donors.append(AcceptorDonor(*random.sample(struct.atoms, 2))) struct.groups.append(Group(random.choice(struct.atoms), 2, 0)) struct.chiral_frames.append( ChiralFrame(*random.sample(struct.atoms, 2), chirality=random.choice([-1, 1]))) struct.multipole_frames.append( MultipoleFrame(random.choice(struct.atoms), 0, 1, 2, 3)) for i in range(random.randint(20, 30)): struct.adjusts.append( NonbondedException(*random.sample(struct.atoms, 2))) if parametrized: struct.adjusts[-1].type = random.choice(struct.adjust_types) struct.prune_empty_terms() struct.unchange() struct.update_dihedral_exclusions() return struct
def __init__(self, psf_name=None): """ Opens and parses a PSF file, then instantiates a CharmmPsfFile instance from the data. """ global _resre Structure.__init__(self) # Bail out if we don't have a filename if psf_name is None: return conv = CharmmPsfFile._convert # Open the PSF and read the first line. It must start with "PSF" with closing(genopen(psf_name, 'r')) as psf: self.name = psf_name line = psf.readline() if not line.startswith('PSF'): raise CharmmError('Unrecognized PSF file. First line is %s' % line.strip()) # Store the flags psf_flags = line.split()[1:] # Now get all of the sections and store them in a dict psf.readline() # Now get all of the sections psfsections = _ZeroDict() while True: try: sec, ptr, data = CharmmPsfFile._parse_psf_section(psf) except _FileEOF: break psfsections[sec] = (ptr, data) # store the title self.title = psfsections['NTITLE'][1] # Next is the number of atoms natom = conv(psfsections['NATOM'][0], int, 'natom') # Parse all of the atoms for i in range(natom): words = psfsections['NATOM'][1][i].split() atid = int(words[0]) if atid != i + 1: raise CharmmError('Nonsequential atoms detected!') segid = words[1] rematch = _resre.match(words[2]) if not rematch: raise CharmmError('Could not interpret residue number %s' % # pragma: no cover words[2]) resid, inscode = rematch.groups() resid = conv(resid, int, 'residue number') resname = words[3] name = words[4] attype = words[5] # Try to convert the atom type to an integer a la CHARMM try: attype = int(attype) except ValueError: pass charge = conv(words[6], float, 'partial charge') mass = conv(words[7], float, 'atomic mass') props = words[8:] atom = Atom(name=name, type=attype, charge=charge, mass=mass) atom.props = props self.add_atom(atom, resname, resid, chain=segid, inscode=inscode, segid=segid) # Now get the number of bonds nbond = conv(psfsections['NBOND'][0], int, 'number of bonds') if len(psfsections['NBOND'][1]) != nbond * 2: raise CharmmError('Got %d indexes for %d bonds' % # pragma: no cover (len(psfsections['NBOND'][1]), nbond)) it = iter(psfsections['NBOND'][1]) for i, j in zip(it, it): self.bonds.append(Bond(self.atoms[i-1], self.atoms[j-1])) # Now get the number of angles and the angle list ntheta = conv(psfsections['NTHETA'][0], int, 'number of angles') if len(psfsections['NTHETA'][1]) != ntheta * 3: raise CharmmError('Got %d indexes for %d angles' % # pragma: no cover (len(psfsections['NTHETA'][1]), ntheta)) it = iter(psfsections['NTHETA'][1]) for i, j, k in zip(it, it, it): self.angles.append( Angle(self.atoms[i-1], self.atoms[j-1], self.atoms[k-1]) ) self.angles[-1].funct = 5 # urey-bradley # Now get the number of torsions and the torsion list nphi = conv(psfsections['NPHI'][0], int, 'number of torsions') if len(psfsections['NPHI'][1]) != nphi * 4: raise CharmmError('Got %d indexes for %d torsions' % # pragma: no cover (len(psfsections['NPHI']), nphi)) it = iter(psfsections['NPHI'][1]) for i, j, k, l in zip(it, it, it, it): self.dihedrals.append( Dihedral(self.atoms[i-1], self.atoms[j-1], self.atoms[k-1], self.atoms[l-1]) ) self.dihedrals.split = False # Now get the number of improper torsions nimphi = conv(psfsections['NIMPHI'][0], int, 'number of impropers') if len(psfsections['NIMPHI'][1]) != nimphi * 4: raise CharmmError('Got %d indexes for %d impropers' % # pragma: no cover (len(psfsections['NIMPHI'][1]), nimphi)) it = iter(psfsections['NIMPHI'][1]) for i, j, k, l in zip(it, it, it, it): self.impropers.append( Improper(self.atoms[i-1], self.atoms[j-1], self.atoms[k-1], self.atoms[l-1]) ) # Now handle the donors (what is this used for??) ndon = conv(psfsections['NDON'][0], int, 'number of donors') if len(psfsections['NDON'][1]) != ndon * 2: raise CharmmError('Got %d indexes for %d donors' % # pragma: no cover (len(psfsections['NDON'][1]), ndon)) it = iter(psfsections['NDON'][1]) for i, j in zip(it, it): self.donors.append( AcceptorDonor(self.atoms[i-1], self.atoms[j-1]) ) # Now handle the acceptors (what is this used for??) nacc = conv(psfsections['NACC'][0], int, 'number of acceptors') if len(psfsections['NACC'][1]) != nacc * 2: raise CharmmError('Got %d indexes for %d acceptors' % # pragma: no cover (len(psfsections['NACC'][1]), nacc)) it = iter(psfsections['NACC'][1]) for i, j in zip(it, it): self.acceptors.append( AcceptorDonor(self.atoms[i-1], self.atoms[j-1]) ) # Now get the group sections try: ngrp, nst2 = psfsections['NGRP NST2'][0] except ValueError: # pragma: no cover raise CharmmError('Could not unpack GROUP pointers') # pragma: no cover tmp = psfsections['NGRP NST2'][1] self.groups.nst2 = nst2 # Now handle the groups if len(psfsections['NGRP NST2'][1]) != ngrp * 3: raise CharmmError('Got %d indexes for %d groups' % # pragma: no cover (len(tmp), ngrp)) it = iter(psfsections['NGRP NST2'][1]) for i, j, k in zip(it, it, it): self.groups.append(Group(self.atoms[i], j, k)) # Assign all of the atoms to molecules recursively tmp = psfsections['MOLNT'][1] set_molecules(self.atoms) molecule_list = [a.marked for a in self.atoms] if len(tmp) == len(self.atoms): if molecule_list != tmp: warnings.warn('Detected PSF molecule section that is WRONG. ' 'Resetting molecularity.', CharmmWarning) # We have a CHARMM PSF file; now do NUMLP/NUMLPH sections numlp, numlph = psfsections['NUMLP NUMLPH'][0] if numlp != 0 or numlph != 0: raise NotImplementedError('Cannot currently handle PSFs with ' 'lone pairs defined in the NUMLP/' 'NUMLPH section.') # Now do the CMAPs ncrterm = conv(psfsections['NCRTERM'][0], int, 'Number of cross-terms') if len(psfsections['NCRTERM'][1]) != ncrterm * 8: raise CharmmError('Got %d CMAP indexes for %d cmap terms' % # pragma: no cover (len(psfsections['NCRTERM']), ncrterm)) it = iter(psfsections['NCRTERM'][1]) for i, j, k, l, m, n, o, p in zip(it, it, it, it, it, it, it, it): self.cmaps.append( Cmap.extended(self.atoms[i-1], self.atoms[j-1], self.atoms[k-1], self.atoms[l-1], self.atoms[m-1], self.atoms[n-1], self.atoms[o-1], self.atoms[p-1]) ) self.unchange() self.flags = psf_flags
def create_random_structure(parametrized, novalence=False): """ Create a random Structure with random attributes Parameters ---------- parametrized : bool If True, add at least two of all kinds of parameters to the generated random structure. If False, just fill in the atoms and residues and some random valence terms, but no "types" novalence : bool, optional If True, no valence terms will be added. Default is False. This is set to False if parametrized is True """ from parmed.topologyobjects import (Atom, Bond, AtomType, BondType, AngleType, DihedralType, ImproperType, CmapType, OutOfPlaneBendType, StretchBendType, TorsionTorsionType, AmoebaNonbondedExceptionType, Angle, UreyBradley, Dihedral, Improper, Cmap, TrigonalAngle, OutOfPlaneBend, StretchBend, PiTorsion, TorsionTorsion, AcceptorDonor, Group, ChiralFrame, MultipoleFrame, NonbondedException, RBTorsionType) from parmed import structure from copy import copy if parametrized: novalence = False # Generate random atom and parameter types atom_types = [AtomType(''.join(random.sample(uppercase, 3)), i, random.random()*16+1, random.randint(1, 8)) for i in range(random.randint(8, 20))] bond_types = [BondType(random.random()*2, random.random()*100) for i in range(random.randint(10, 20))] angle_types = [AngleType(random.random()*50, random.random()*120) for i in range(random.randint(10, 20))] dihed_types = [DihedralType(random.random()*10, random.randint(1, 6), random.choice([0, 180])) for i in range(random.randint(10, 20))] rb_types = [RBTorsionType(*[random.random()*10 for i in range(6)])] imp_types = [ImproperType(random.random()*100, random.choice([0, 180])) for i in range(random.randint(10, 20))] cmap_types = [CmapType(24, [random.random()*5 for i in range(24*24)]) for i in range(random.randint(5, 10))] oop_types = [OutOfPlaneBendType(random.random()*100) for i in range(random.randint(10, 20))] strbnd_types = [StretchBendType(random.random()*10, random.random()*10, random.random()*2, random.random()*2, random.random()*120) for i in range(random.randint(10, 20))] ang1, ang2 = list(range(-180,180,36)), list(range(-180,180,18)) tortor_types = [TorsionTorsionType((10, 20), ang1[:], ang2[:], [random.random()*10 for j in range(200)]) for i in range(random.randint(5, 10))] for typ in atom_types: typ.set_lj_params(random.random()*2, random.random()*2) struct = structure.Structure() # Add atoms in residues for res in range(random.randint(20, 30)): resname = ''.join(random.sample(uppercase, 3)) resid = res + 1 for i in range(random.randint(10, 25)): name = ''.join(random.sample(uppercase, 4)) if parametrized: typ = random.choice(atom_types) type = str(typ) mass = typ.mass atomic_number = typ.atomic_number else: type = ''.join(random.sample(uppercase, 3)) mass = random.random() * 16 + 1 atomic_number = random.randint(1, 8) charge = random.random() * 2 - 1 solvent_radius = random.random() * 2 screen = random.random() * 2 atom = Atom(atomic_number=atomic_number, type=type, charge=charge, mass=mass, solvent_radius=solvent_radius, screen=screen, name=name) if parametrized: atom.atom_type = typ struct.add_atom(atom, resname, resid) if novalence: return struct # Possibly add parameter type lists if parametrized: struct.bond_types.extend([copy(x) for x in bond_types]) struct.bond_types.claim() struct.angle_types.extend([copy(x) for x in angle_types]) struct.angle_types.claim() struct.dihedral_types.extend([copy(x) for x in dihed_types]) struct.dihedral_types.claim() struct.rb_torsion_types.extend([copy(x) for x in rb_types]) struct.rb_torsion_types.claim() struct.urey_bradley_types.extend([copy(x) for x in bond_types]) struct.urey_bradley_types.claim() struct.improper_types.extend([copy(x) for x in imp_types]) struct.improper_types.claim() struct.cmap_types.extend([copy(x) for x in cmap_types]) struct.cmap_types.claim() struct.trigonal_angle_types.extend([copy(x) for x in angle_types]) struct.trigonal_angle_types.claim() struct.out_of_plane_bend_types.extend([copy(x) for x in oop_types]) struct.out_of_plane_bend_types.claim() struct.pi_torsion_types.extend([copy(x) for x in dihed_types]) struct.pi_torsion_types.claim() struct.stretch_bend_types.extend([copy(x) for x in strbnd_types]) struct.stretch_bend_types.claim() struct.torsion_torsion_types.extend([copy(x) for x in tortor_types]) struct.torsion_torsion_types.claim() struct.adjust_types.extend([AmoebaNonbondedExceptionType(0.5, 0.5, 0.6, 0.6, 0.7) for i in range(random.randint(10, 20))]) struct.adjust_types.claim() # Add valence terms with optional for i in range(random.randint(40, 50)): struct.bonds.append(Bond(*random.sample(struct.atoms, 2))) if parametrized: struct.bonds[-1].type = random.choice(struct.bond_types) for i in range(random.randint(35, 45)): struct.angles.append(Angle(*random.sample(struct.atoms, 3))) if parametrized: struct.angles[-1].type = random.choice(struct.angle_types) for i in range(random.randint(35, 45)): struct.urey_bradleys.append(UreyBradley(*random.sample(struct.atoms, 2))) if parametrized: struct.urey_bradleys[-1].type = random.choice(struct.urey_bradley_types) for i in range(random.randint(30, 40)): struct.dihedrals.append(Dihedral(*random.sample(struct.atoms, 4), improper=random.choice([True, False]))) if parametrized: struct.dihedrals[-1].type = random.choice(struct.dihedral_types) for i in range(random.randint(30, 40)): struct.rb_torsions.append(Dihedral(*random.sample(struct.atoms, 4))) if parametrized: struct.rb_torsions[-1].type = random.choice(struct.rb_torsion_types) for i in range(random.randint(10, 20)): struct.impropers.append(Improper(*random.sample(struct.atoms, 4))) if parametrized: struct.impropers[-1].type = random.choice(struct.improper_types) for i in range(random.randint(25, 35)): struct.cmaps.append(Cmap(*random.sample(struct.atoms, 5))) if parametrized: struct.cmaps[-1].type = random.choice(struct.cmap_types) for i in range(random.randint(30, 40)): struct.trigonal_angles.append(TrigonalAngle(*random.sample(struct.atoms, 4))) if parametrized: struct.trigonal_angles[-1].type = random.choice(struct.trigonal_angle_types) for i in range(random.randint(30, 40)): struct.out_of_plane_bends.append(OutOfPlaneBend(*random.sample(struct.atoms, 4))) if parametrized: struct.out_of_plane_bends[-1].type = random.choice(struct.out_of_plane_bend_types) for i in range(random.randint(30, 40)): struct.stretch_bends.append(StretchBend(*random.sample(struct.atoms, 3))) if parametrized: struct.stretch_bends[-1].type = random.choice(struct.stretch_bend_types) for i in range(random.randint(20, 30)): struct.pi_torsions.append(PiTorsion(*random.sample(struct.atoms, 6))) if parametrized: struct.pi_torsions[-1].type = random.choice(struct.pi_torsion_types) for i in range(random.randint(10, 20)): struct.torsion_torsions.append(TorsionTorsion(*random.sample(struct.atoms, 5))) if parametrized: struct.torsion_torsions[-1].type = random.choice(struct.torsion_torsion_types) # Now use some lesser-used features for i in range(random.randint(5, 10)): struct.acceptors.append(AcceptorDonor(*random.sample(struct.atoms, 2))) struct.donors.append(AcceptorDonor(*random.sample(struct.atoms, 2))) struct.groups.append(Group(random.choice(struct.atoms), 2, 0)) struct.chiral_frames.append(ChiralFrame(*random.sample(struct.atoms, 2), chirality=random.choice([-1, 1]))) struct.multipole_frames.append(MultipoleFrame(random.choice(struct.atoms), 0, 1, 2, 3)) for i in range(random.randint(20, 30)): struct.adjusts.append(NonbondedException(*random.sample(struct.atoms, 2))) if parametrized: struct.adjusts[-1].type = random.choice(struct.adjust_types) struct.prune_empty_terms() struct.unchange() struct.update_dihedral_exclusions() return struct
def __init__(self, psf_name=None): """ Opens and parses a PSF file, then instantiates a CharmmPsfFile instance from the data. """ global _resre Structure.__init__(self) # Bail out if we don't have a filename if psf_name is None: return conv = CharmmPsfFile._convert # Open the PSF and read the first line. It must start with "PSF" with closing(genopen(psf_name, 'r')) as psf: self.name = psf_name line = psf.readline() if not line.startswith('PSF'): raise CharmmError('Unrecognized PSF file. First line is %s' % line.strip()) # Store the flags psf_flags = line.split()[1:] # Now get all of the sections and store them in a dict psf.readline() # Now get all of the sections psfsections = _ZeroDict() while True: try: sec, ptr, data = CharmmPsfFile._parse_psf_section(psf) except _FileEOF: break psfsections[sec] = (ptr, data) # store the title self.title = psfsections['NTITLE'][1] # Next is the number of atoms natom = conv(psfsections['NATOM'][0], int, 'natom') # Parse all of the atoms for i in range(natom): words = psfsections['NATOM'][1][i].split() atid = int(words[0]) if atid != i + 1: raise CharmmError('Nonsequential atoms detected!') segid = words[1] rematch = _resre.match(words[2]) if not rematch: raise CharmmError('Could not interpret residue number %s' % # pragma: no cover words[2]) resid, inscode = rematch.groups() resid = conv(resid, int, 'residue number') resname = words[3] name = words[4] attype = words[5] # Try to convert the atom type to an integer a la CHARMM try: attype = int(attype) except ValueError: pass charge = conv(words[6], float, 'partial charge') mass = conv(words[7], float, 'atomic mass') props = words[8:] atom = Atom(name=name, type=attype, charge=charge, mass=mass) atom.props = props self.add_atom(atom, resname, resid, chain=segid, inscode=inscode, segid=segid) # Now get the number of bonds nbond = conv(psfsections['NBOND'][0], int, 'number of bonds') if len(psfsections['NBOND'][1]) != nbond * 2: raise CharmmError( 'Got %d indexes for %d bonds' % # pragma: no cover (len(psfsections['NBOND'][1]), nbond)) it = iter(psfsections['NBOND'][1]) for i, j in zip(it, it): self.bonds.append(Bond(self.atoms[i - 1], self.atoms[j - 1])) # Now get the number of angles and the angle list ntheta = conv(psfsections['NTHETA'][0], int, 'number of angles') if len(psfsections['NTHETA'][1]) != ntheta * 3: raise CharmmError( 'Got %d indexes for %d angles' % # pragma: no cover (len(psfsections['NTHETA'][1]), ntheta)) it = iter(psfsections['NTHETA'][1]) for i, j, k in zip(it, it, it): self.angles.append( Angle(self.atoms[i - 1], self.atoms[j - 1], self.atoms[k - 1])) self.angles[-1].funct = 5 # urey-bradley # Now get the number of torsions and the torsion list nphi = conv(psfsections['NPHI'][0], int, 'number of torsions') if len(psfsections['NPHI'][1]) != nphi * 4: raise CharmmError( 'Got %d indexes for %d torsions' % # pragma: no cover (len(psfsections['NPHI']), nphi)) it = iter(psfsections['NPHI'][1]) for i, j, k, l in zip(it, it, it, it): self.dihedrals.append( Dihedral(self.atoms[i - 1], self.atoms[j - 1], self.atoms[k - 1], self.atoms[l - 1])) self.dihedrals.split = False # Now get the number of improper torsions nimphi = conv(psfsections['NIMPHI'][0], int, 'number of impropers') if len(psfsections['NIMPHI'][1]) != nimphi * 4: raise CharmmError( 'Got %d indexes for %d impropers' % # pragma: no cover (len(psfsections['NIMPHI'][1]), nimphi)) it = iter(psfsections['NIMPHI'][1]) for i, j, k, l in zip(it, it, it, it): self.impropers.append( Improper(self.atoms[i - 1], self.atoms[j - 1], self.atoms[k - 1], self.atoms[l - 1])) # Now handle the donors (what is this used for??) ndon = conv(psfsections['NDON'][0], int, 'number of donors') if len(psfsections['NDON'][1]) != ndon * 2: raise CharmmError( 'Got %d indexes for %d donors' % # pragma: no cover (len(psfsections['NDON'][1]), ndon)) it = iter(psfsections['NDON'][1]) for i, j in zip(it, it): self.donors.append( AcceptorDonor(self.atoms[i - 1], self.atoms[j - 1])) # Now handle the acceptors (what is this used for??) nacc = conv(psfsections['NACC'][0], int, 'number of acceptors') if len(psfsections['NACC'][1]) != nacc * 2: raise CharmmError( 'Got %d indexes for %d acceptors' % # pragma: no cover (len(psfsections['NACC'][1]), nacc)) it = iter(psfsections['NACC'][1]) for i, j in zip(it, it): self.acceptors.append( AcceptorDonor(self.atoms[i - 1], self.atoms[j - 1])) # Now get the group sections try: ngrp, nst2 = psfsections['NGRP NST2'][0] except ValueError: # pragma: no cover raise CharmmError( 'Could not unpack GROUP pointers') # pragma: no cover tmp = psfsections['NGRP NST2'][1] self.groups.nst2 = nst2 # Now handle the groups if len(psfsections['NGRP NST2'][1]) != ngrp * 3: raise CharmmError( 'Got %d indexes for %d groups' % # pragma: no cover (len(tmp), ngrp)) it = iter(psfsections['NGRP NST2'][1]) for i, j, k in zip(it, it, it): self.groups.append(Group(self.atoms[i], j, k)) # Assign all of the atoms to molecules recursively tmp = psfsections['MOLNT'][1] set_molecules(self.atoms) molecule_list = [a.marked for a in self.atoms] if len(tmp) == len(self.atoms): if molecule_list != tmp: warnings.warn( 'Detected PSF molecule section that is WRONG. ' 'Resetting molecularity.', CharmmWarning) # We have a CHARMM PSF file; now do NUMLP/NUMLPH sections numlp, numlph = psfsections['NUMLP NUMLPH'][0] if numlp != 0 or numlph != 0: raise NotImplementedError( 'Cannot currently handle PSFs with ' 'lone pairs defined in the NUMLP/' 'NUMLPH section.') # Now do the CMAPs ncrterm = conv(psfsections['NCRTERM'][0], int, 'Number of cross-terms') if len(psfsections['NCRTERM'][1]) != ncrterm * 8: raise CharmmError('Got %d CMAP indexes for %d cmap terms' % # pragma: no cover (len(psfsections['NCRTERM']), ncrterm)) it = iter(psfsections['NCRTERM'][1]) for i, j, k, l, m, n, o, p in zip(it, it, it, it, it, it, it, it): self.cmaps.append( Cmap.extended(self.atoms[i - 1], self.atoms[j - 1], self.atoms[k - 1], self.atoms[l - 1], self.atoms[m - 1], self.atoms[n - 1], self.atoms[o - 1], self.atoms[p - 1])) self.unchange() self.flags = psf_flags
def load(pose): """ Load a :class:`Pose` object and return a populated :class:`Structure` instance Parameters ---------- pose : :class:`Pose` PyRosetta :class:`Pose` object to convert """ if not Pose or not AtomID: raise ImportError('Could not load the PyRosetta module.') if not isinstance(pose, Pose): raise TypeError('Object is not a PyRosetta Pose object.') struct = Structure() atnum = 1 conf = pose.conformation() for resid in range(1, pose.total_residue() + 1): res = pose.residue(resid) resname = res.name3().strip() chain = chr(res.chain() + ord('A') - 1) for atno, at in enumerate(res.atoms(), start=1): try: atinfo = res.atom_type(atno) atname = res.atom_name(atno).strip() if atinfo.is_virtual(): atsym = 'EP' else: atsym = atinfo.element() rmin = atinfo.lj_radius() epsilon = atinfo.lj_wdepth() atomic_number = AtomicNum[atsym] mass = Mass[atsym] except KeyError: raise RosettaError('Could not recognize element: %s.' % atsym) params = dict(atomic_number=atomic_number, name=atname, charge=0.0, mass=mass, occupancy=0.0, bfactor=0.0, altloc='', number=atnum, rmin=rmin, epsilon=epsilon) if atinfo.is_virtual(): atom = ExtraPoint(**params) else: atom = Atom(**params) atom.xx, atom.xy, atom.xz = tuple(at.xyz()) struct.add_atom(atom, resname, resid, chain, '') atnum += 1 try: for nbr in conf.bonded_neighbor_all_res(AtomID( atno, resid)): if nbr.rsd() < resid or (nbr.rsd() == resid and nbr.atomno() < atno): struct.bonds.append( Bond(struct.atoms[_n_prior(pose, nbr)], atom)) except: raise RosettaError('Could not add bonds.') struct.unchange() return struct
def parse(filename, structure=False): """ Parses a mol2 file (or mol3) file Parameters ---------- filename : str or file-like Name of the file to parse or file-like object to parse from structure : bool, optional If True, the return value is a :class:`Structure` instance. If False, it is either a :class:`ResidueTemplate` or :class:`ResidueTemplateContainter` instance, depending on whether there is one or more than one residue defined in it. Default is False Returns ------- molecule : :class:`Structure`, :class:`ResidueTemplate`, or :class:`ResidueTemplateContainer` The molecule defined by this mol2 file Raises ------ Mol2Error If the file format is not recognized or non-numeric values are present where integers or floating point numbers are expected. Also raises Mol2Error if you try to parse a mol2 file that has multiple @<MOLECULE> entries with ``structure=True``. """ if isinstance(filename, string_types): f = genopen(filename, 'r') own_handle = True else: f = filename own_handle = False rescont = ResidueTemplateContainer() struct = Structure() restemp = ResidueTemplate() mol_info = [] multires_structure = False try: section = None last_residue = None headtail = 'head' molecule_number = 0 for line in f: if line.startswith('#'): continue if not line.strip() and section is None: continue if line.startswith('@<TRIPOS>'): section = line[9:].strip() if section == 'MOLECULE' and (restemp.atoms or rescont): if structure: raise Mol2Error('Cannot convert MOL2 with multiple ' '@<MOLECULE>s to a Structure') # Set the residue name from the MOL2 title if the # molecule had only 1 residue and it was given a name in # the title if not multires_structure and mol_info[0]: restemp.name = mol_info[0] multires_structure = False rescont.append(restemp) restemp = ResidueTemplate() struct = Structure() last_residue = None molecule_number += 1 mol_info = [] continue if section is None: raise Mol2Error('Bad mol2 file format') if section == 'MOLECULE': # Section formatted as follows: # mol_name # num_atoms [num_bonds [num_substr [num_feat [num_sets]]]] # mol_type # charge_type # [status_bits] # [mol_comment] # TODO: Do something with the name. if len(mol_info) == 0: mol_info.append(line.strip()) elif len(mol_info) == 1: mol_info.append([int(x) for x in line.split()]) elif len(mol_info) == 2: mol_info.append(line.strip()) elif len(mol_info) == 3: mol_info.append(line.strip()) # Ignore the rest continue if section == 'ATOM': # Section formatted as follows: # atom_id -- serial number of atom # atom_name -- name of the atom # x -- X-coordinate of the atom # y -- Y-coordinate of the atom # z -- Z-coordinate of the atom # atom_type -- type of the atom # subst_id -- Residue serial number # subst_name -- Residue name # charge -- partial atomic charge # status_bit -- ignored words = line.split() id = int(words[0]) name = words[1] x = float(words[2]) y = float(words[3]) z = float(words[4]) typ = words[5] try: resid = int(words[6]) except IndexError: resid = 0 try: resname = words[7] except IndexError: resname = 'UNK' if 'NO_CHARGES' not in mol_info: try: charge = float(words[8]) except IndexError: charge = 0 else: charge = 0 if last_residue is None: last_residue = (resid, resname) restemp.name = resname atom = Atom(name=name, type=typ, number=id, charge=charge) atom.xx, atom.xy, atom.xz = x, y, z struct.add_atom(atom, resname, resid) if last_residue != (resid, resname): rescont.append(restemp) restemp = ResidueTemplate() restemp.name = resname last_residue = (resid, resname) multires_structure = True restemp.add_atom(copy.copy(atom)) continue if section == 'BOND': # Section formatted as follows: # bond_id -- serial number of bond (ignored) # origin_atom_id -- serial number of first atom in bond # target_atom_id -- serial number of other atom in bond # bond_type -- string describing bond type (ignored) # status_bits -- ignored words = line.split() int(words[0]) # Bond serial number... redundant and ignored a1 = int(words[1]) a2 = int(words[2]) atom1 = struct.atoms.find_original_index(a1) atom2 = struct.atoms.find_original_index(a2) struct.bonds.append(Bond(atom1, atom2)) # Now add it to our residue container # See if it's a head/tail connection if atom1.residue is not atom2.residue: if atom1.residue.idx == len(rescont): res1 = restemp elif atom1.residue.idx < len(rescont): res1 = rescont[atom1.residue.idx] else: raise Mol2Error('Bad bonding pattern detected') if atom2.residue.idx == len(rescont): res2 = restemp elif atom1.residue.idx < len(rescont): res2 = rescont[atom2.residue.idx] else: raise Mol2Error('Bad bonding pattern detected') assert res1 is not res2, 'BAD identical residues' idx1 = atom1.idx - atom1.residue[0].idx idx2 = atom2.idx - atom2.residue[0].idx if atom1.residue.idx < atom2.residue.idx: res1.tail = res1[idx1] res2.head = res2[idx2] else: res1.head = res1[idx1] res2.tail = res2[idx2] elif not multires_structure: restemp.add_bond(a1-1, a2-1) else: # Same residue, add the bond offset = atom1.residue[0].idx if atom1.residue.idx == len(rescont): res = restemp else: res = rescont[atom1.residue.idx] res.add_bond(atom1.idx-offset, atom2.idx-offset) continue if section == 'CRYSIN': # Section formatted as follows: # a -- length of first unit cell vector # b -- length of second unit cell vector # c -- length of third unit cell vector # alpha -- angle b/w b and c # beta -- angle b/w a and c # gamma -- angle b/w a and b # space group -- number of space group (ignored) # space group setting -- ignored words = line.split() box = [float(x) for x in words[:6]] if len(box) != 6: raise ValueError('%d box dimensions found; needed 6' % len(box)) struct.box = copy.copy(box) rescont.box = copy.copy(box) continue if section == 'SUBSTRUCTURE': # Section formatted as follows: # subst_id -- residue number # subst_name -- residue name # root_atom -- first atom of residue # subst_type -- ignored (usually 'RESIDUE') # dict_type -- type of substructure (ignored) # chain -- chain ID of residue # sub_type -- type of the chain # inter_bonds -- # of inter-substructure bonds # status -- ignored # comment -- ignored words = line.split() if not words: continue id = int(words[0]) resname = words[1] try: chain = words[5] except IndexError: chain = '' # Set the chain ID for res in struct.residues: if res.number == id and res.name == resname: res.chain = chain continue # MOL3 sections if section == 'HEADTAIL': atname, residx = line.split() residx = int(residx) if residx in (0, 1) or residx - 1 == len(rescont): res = restemp elif residx - 1 < len(rescont): res = rescont[residx-1] else: raise Mol2Error('Residue out of range in head/tail') for atom in res: if atom.name == atname: if headtail == 'head': res.head = atom headtail = 'tail' else: res.tail = atom headtail = 'head' break else: if headtail == 'head': headtail = 'tail' else: headtail = 'head' continue if section == 'RESIDUECONNECT': words = line.split() residx = int(words[0]) if residx - 1 == len(rescont): res = restemp elif residx - 1 < len(rescont): res = rescont[residx-1] else: raise Mol2Error('Residue out of range in ' 'residueconnect') for a in words[3:]: if a == '0': continue for atom in res: if atom.name == a: atom.connections.append(atom) break else: raise Mol2Error('Residue connection atom %s not ' 'found in residue %d' % (a, residx)) if structure: return struct elif len(rescont) > 0: if not multires_structure and mol_info[0]: restemp.name = mol_info[0] rescont.append(restemp) return rescont else: return restemp except ValueError as e: raise Mol2Error('String conversion trouble: %s' % e) finally: if own_handle: f.close()
def parse(filename, structure=False): """ Parses a mol2 file (or mol3) file Parameters ---------- filename : str or file-like Name of the file to parse or file-like object to parse from structure : bool, optional If True, the return value is a :class:`Structure` instance. If False, it is either a :class:`ResidueTemplate` or :class:`ResidueTemplateContainter` instance, depending on whether there is one or more than one residue defined in it. Default is False Returns ------- molecule : :class:`Structure`, :class:`ResidueTemplate`, or :class:`ResidueTemplateContainer` The molecule defined by this mol2 file Raises ------ Mol2Error If the file format is not recognized or non-numeric values are present where integers or floating point numbers are expected. Also raises Mol2Error if you try to parse a mol2 file that has multiple @<MOLECULE> entries with ``structure=True``. """ if isinstance(filename, string_types): f = genopen(filename, 'r') own_handle = True else: f = filename own_handle = False rescont = ResidueTemplateContainer() struct = Structure() restemp = ResidueTemplate() mol_info = [] multires_structure = False try: section = None last_residue = None headtail = 'head' molecule_number = 0 for line in f: if line.startswith('#'): continue if not line.strip() and section is None: continue if line.startswith('@<TRIPOS>'): section = line[9:].strip() if section == 'MOLECULE' and (restemp.atoms or rescont): if structure: raise Mol2Error('Cannot convert MOL2 with multiple ' '@<MOLECULE>s to a Structure') # Set the residue name from the MOL2 title if the # molecule had only 1 residue and it was given a name in # the title if not multires_structure and mol_info[0]: restemp.name = mol_info[0] multires_structure = False rescont.append(restemp) restemp = ResidueTemplate() struct = Structure() last_residue = None molecule_number += 1 mol_info = [] continue if section is None: raise Mol2Error('Bad mol2 file format') if section == 'MOLECULE': # Section formatted as follows: # mol_name # num_atoms [num_bonds [num_substr [num_feat [num_sets]]]] # mol_type # charge_type # [status_bits] # [mol_comment] # TODO: Do something with the name. if len(mol_info) == 0: mol_info.append(line.strip()) elif len(mol_info) == 1: mol_info.append([int(x) for x in line.split()]) elif len(mol_info) == 2: mol_info.append(line.strip()) elif len(mol_info) == 3: mol_info.append(line.strip()) # Ignore the rest continue if section == 'ATOM': # Section formatted as follows: # atom_id -- serial number of atom # atom_name -- name of the atom # x -- X-coordinate of the atom # y -- Y-coordinate of the atom # z -- Z-coordinate of the atom # atom_type -- type of the atom # subst_id -- Residue serial number # subst_name -- Residue name # charge -- partial atomic charge # status_bit -- ignored words = line.split() id = int(words[0]) name = words[1] x = float(words[2]) y = float(words[3]) z = float(words[4]) typ = words[5] try: resid = int(words[6]) except IndexError: resid = 0 try: resname = words[7] except IndexError: resname = 'UNK' if 'NO_CHARGES' not in mol_info: try: charge = float(words[8]) except IndexError: charge = 0 else: charge = 0 if last_residue is None: last_residue = (resid, resname) restemp.name = resname atom = Atom(name=name, type=typ, number=id, charge=charge) atom.xx, atom.xy, atom.xz = x, y, z struct.add_atom(atom, resname, resid) if last_residue != (resid, resname): rescont.append(restemp) restemp = ResidueTemplate() restemp.name = resname last_residue = (resid, resname) multires_structure = True try: restemp.add_atom(copy.copy(atom)) except ValueError: # Allow mol2 files being parsed as a Structure to have # duplicate atom names if not structure: raise continue if section == 'BOND': # Section formatted as follows: # bond_id -- serial number of bond (ignored) # origin_atom_id -- serial number of first atom in bond # target_atom_id -- serial number of other atom in bond # bond_type -- string describing bond type (ignored) # status_bits -- ignored words = line.split() int(words[0]) # Bond serial number... redundant and ignored a1 = int(words[1]) a2 = int(words[2]) atom1 = struct.atoms.find_original_index(a1) atom2 = struct.atoms.find_original_index(a2) struct.bonds.append(Bond(atom1, atom2)) # Now add it to our residue container # See if it's a head/tail connection if atom1.residue is not atom2.residue: if atom1.residue.idx == len(rescont): res1 = restemp elif atom1.residue.idx < len(rescont): res1 = rescont[atom1.residue.idx] assert atom.residue.idx <= len(rescont), 'Bad bond!' if atom2.residue.idx == len(rescont): res2 = restemp elif atom2.residue.idx < len(rescont): res2 = rescont[atom2.residue.idx] assert atom.residue.idx <= len(rescont), 'Bad bond!' assert res1 is not res2, 'BAD identical residues' idx1 = atom1.idx - atom1.residue[0].idx idx2 = atom2.idx - atom2.residue[0].idx if atom1.residue.idx < atom2.residue.idx: res1.tail = res1[idx1] res2.head = res2[idx2] else: res1.head = res1[idx1] res2.tail = res2[idx2] elif not multires_structure: if not structure: restemp.add_bond(a1-1, a2-1) else: # Same residue, add the bond offset = atom1.residue[0].idx if atom1.residue.idx == len(rescont): res = restemp else: res = rescont[atom1.residue.idx] res.add_bond(atom1.idx-offset, atom2.idx-offset) continue if section == 'CRYSIN': # Section formatted as follows: # a -- length of first unit cell vector # b -- length of second unit cell vector # c -- length of third unit cell vector # alpha -- angle b/w b and c # beta -- angle b/w a and c # gamma -- angle b/w a and b # space group -- number of space group (ignored) # space group setting -- ignored words = line.split() box = [float(w) for w in words[:6]] if len(box) != 6: raise ValueError('%d box dimensions found; needed 6' % len(box)) struct.box = copy.copy(box) rescont.box = copy.copy(box) continue if section == 'SUBSTRUCTURE': # Section formatted as follows: # subst_id -- residue number # subst_name -- residue name # root_atom -- first atom of residue # subst_type -- ignored (usually 'RESIDUE') # dict_type -- type of substructure (ignored) # chain -- chain ID of residue # sub_type -- type of the chain # inter_bonds -- # of inter-substructure bonds # status -- ignored # comment -- ignored words = line.split() if not words: continue id = int(words[0]) resname = words[1] try: chain = words[5] except IndexError: chain = '' # Set the chain ID for res in struct.residues: if res.number == id and res.name == resname: res.chain = chain continue # MOL3 sections if section == 'HEADTAIL': atname, residx = line.split() residx = int(residx) if residx in (0, 1) or residx - 1 == len(rescont): res = restemp elif residx - 1 < len(rescont): res = rescont[residx-1] else: raise Mol2Error('Residue out of range in head/tail') for atom in res: if atom.name == atname: if headtail == 'head': res.head = atom headtail = 'tail' else: res.tail = atom headtail = 'head' break else: if headtail == 'head': headtail = 'tail' else: headtail = 'head' continue if section == 'RESIDUECONNECT': words = line.split() residx = int(words[0]) if residx - 1 == len(rescont): res = restemp elif residx - 1 < len(rescont): res = rescont[residx-1] else: raise Mol2Error('Residue out of range in ' 'residueconnect') for a in words[3:]: if a == '0': continue for atom in res: if atom.name == a: res.connections.append(atom) break else: raise Mol2Error('Residue connection atom %s not ' 'found in residue %d' % (a, residx)) if structure: return struct elif len(rescont) > 0: if not multires_structure and mol_info[0]: restemp.name = mol_info[0] rescont.append(restemp) return rescont else: return restemp except ValueError as e: raise Mol2Error('String conversion trouble: %s' % e) finally: if own_handle: f.close()
def apply_patch(self, patch, precision=4): """ Apply the specified PatchTemplate to the ResidueTemplate. This only handles patches that affect a single residue. An exception is thrown if patch is incompatible because * The patch specifies that an atom is to be deleted that doesn't exist in the residue * A bond specified as being added in the patch does not have both atom names present after adding/deleting atoms from the patch * The new net charge is not integral to the specified precision * The residue is not modified in any way (no atoms or bonds added/changed/deleted) Parameters ---------- patch : PatchTemplate The patch to apply to this residue precision : int, optional Each valid patch should be produce a net charge that is integral to this many decimal places. Default is 4 Returns ------- residue : ResidueTemplate A new ResidueTemplate corresponding to the patched residue is returned. The original remains unmodified. """ # Create a copy # TODO: Once ResidueTemplate.from_residue() actually copies all info, use that instead? residue = _copy.copy(self) # Record whether we've actually modified the residue. modifications_made = False # Delete atoms for atom_name in patch.delete_atoms: try: residue.delete_atom(atom_name) modifications_made = True except (KeyError, MoleculeError) as e: if atom_name.startswith('D') and atom_name[ 1:] in self and atom_name[1:] in patch.delete_atoms: # This is a Drude particle. We're also deleting its parent atom, so don't report an error. pass else: raise IncompatiblePatchError( 'Atom %s could not be deleted from the patched residue: atoms are %s (exception: %s)' % (atom_name, list(residue._map.keys()), str(e))) # Add or replace atoms for atom in patch.atoms: if atom.name in residue: # Overwrite type and charge residue[atom.name].type = atom.type residue[atom.name].charge = atom.charge else: residue.add_atom( Atom(name=atom.name, type=atom.type, charge=atom.charge)) modifications_made = True # Add bonds for (atom1_name, atom2_name, order) in patch.add_bonds: try: # Remove dangling bonds for name in [atom1_name, atom2_name]: if residue.head and (name == residue.head.name): residue.head = None if residue.tail and (name == residue.tail.name): residue.tail = None # Add bond residue.add_bond(atom1_name, atom2_name, order) modifications_made = True except (IndexError, MoleculeError) as e: raise IncompatiblePatchError( 'Bond %s-%s could not be added to patched residue: atoms are %s (exception: %s)' % (atom1_name, atom2_name, list( residue._map.keys()), str(e))) # Delete impropers for impr in patch.delete_impropers: try: residue._impr.remove(impr) # removal of impropers doesn't do anything as far as OpenMM is concerned, so don't note this as a modification having been made except ValueError as e: raise IncompatiblePatchError( 'Improper %s was not found in residue to be patched.' % impr) # Check that the net charge is integral. net_charge = residue.net_charge is_integral = (round(net_charge, precision) - round(net_charge)) == 0.0 if not is_integral: raise IncompatiblePatchError( 'Patch is not compatible with residue due to non-integral charge (charge was %f).' % net_charge) # Ensure residue is connected import networkx as nx G = residue.to_networkx(False) if not nx.is_connected(G): components = [c for c in nx.connected_components(G)] raise IncompatiblePatchError( 'Patched residue bond graph is not a connected graph: %s' % str(components)) # Make sure the patch has actually modified the residue if not modifications_made: raise IncompatiblePatchError('Patch did not modify residue.') return residue
def apply_patch(self, patch, precision=4): """ Apply the specified PatchTemplate to the ResidueTemplate. This only handles patches that affect a single residue. An exception is thrown if patch is incompatible because * The patch specifies that an atom is to be deleted that doesn't exist in the residue * A bond specified as being added in the patch does not have both atom names present after adding/deleting atoms from the patch * The new net charge is not integral to the specified precision Parameters ---------- patch : PatchTemplate The patch to apply to this residue precision : int, optional Each valid patch should be produce a net charge that is integral to this many decimal places. Default is 4 Returns ------- residue : ResidueTemplate A new ResidueTemplate corresponding to the patched residue is returned. The original remains unmodified. """ # Create a copy # TODO: Once ResidueTemplate.from_residue() actually copies all info, use that instead? residue = _copy.copy(self) # Delete atoms for atom_name in patch.delete_atoms: try: residue.delete_atom(atom_name) except KeyError as e: raise IncompatiblePatchError(str(e)) # Add or replace atoms for atom in patch.atoms: if atom.name in residue: # Overwrite type and charge residue[atom.name].type = atom.type residue[atom.name].charge = atom.charge else: residue.add_atom( Atom(name=atom.name, type=atom.type, charge=atom.charge)) # Add bonds for (atom1_name, atom2_name, order) in patch.add_bonds: try: # Remove dangling bonds for name in [atom1_name, atom2_name]: if name == residue.head: residue.head = None if name == residue.tail: residue.tail = None # Add bond residue.add_bond(atom1_name, atom2_name, order) except IndexError as e: raise IncompatiblePatchError( 'Bond %s-%s could not be added to patched residue: atoms are %s' % (atom1_name, atom2_name, list(residue._map.keys()))) # Delete impropers for impr in patch.delete_impropers: try: residue._impr.remove(impr) except ValueError as e: raise IncompatiblePatchError( 'Improper %s was not found in residue to be patched.' % impr) # Check that the net charge is integral. net_charge = residue.net_charge is_integral = (round(net_charge, precision) - round(net_charge)) == 0.0 if not is_integral: raise IncompatiblePatchError( 'Patch is not compatible with residue due to non-integral charge (charge was %f).' % net_charge) # Ensure residue is connected import networkx as nx G = residue.to_networkx() if not nx.is_connected(G): components = [c for c in nx.connected_components(G)] raise IncompatiblePatchError( 'Patched residue bond graph is not a connected graph: %s' % str(components)) return residue
def parse(filename): """ Read a PQR file and return a populated `Structure` class Parameters ---------- filename : str or file-like Name of the PQR file to read, or a file-like object that can iterate over the lines of a PQR. Compressed file names can be specified and are determined by file-name extension (e.g., file.pqr.gz, file.pqr.bz2) Returns ------- structure : :class:`Structure` The Structure object initialized with all of the information from the PDB file. No bonds or other topological features are added by default. """ if isinstance(filename, string_types): own_handle = True fileobj = genopen(filename, 'r') else: own_handle = False fileobj = filename struct = Structure() # Add metadata fields modelno = 1 # For PDB files with multiple MODELs atomno = 0 coordinates = [] all_coordinates = [] # Support hexadecimal numbering like that printed by VMD try: for line in fileobj: words = line.split() if words[0] in ('ATOM', 'HETATM'): atomno += 1 if len(words) == 10: _, num, nam, res, resn, x, y, z, chg, rad = words chn = '' elif len(words) >= 11: _, num, nam, res, chn, resn, x, y, z, chg, rad = ( words[i] for i in range(11)) # If the radius is not a float (but rather a letter, # like the element or something), then the chain might # be missing. In this case, shift all tokens "back" one # and empty the chn string try: float(rad) except ValueError: resn, x, y, z, chg, rad = chn, resn, x, y, z, chg else: raise ValueError('Illegal PQR record format: expected ' '10 or 11 tokens on the atom line') x, y, z = float(x), float(y), float(z) chg, rad = float(chg), float(rad) resn, num = int(resn), int(num) elem = element_by_name(nam) # Yuck atomic_number = AtomicNum[elem] mass = Mass[elem] if nam in ('EP', 'LP'): # lone pair atom = ExtraPoint(atomic_number=atomic_number, name=nam, charge=chg, mass=mass, number=num, solvent_radius=rad) else: atom = Atom(atomic_number=atomic_number, name=nam, charge=chg, mass=mass, number=num, solvent_radius=rad) atom.xx, atom.xy, atom.xz = float(x), float(y), float(z) if modelno == 1: struct.add_atom(atom, res, resn, chn) else: try: orig_atom = struct.atoms[atomno - 1] except IndexError: raise PDBError('Extra atom in MODEL %d' % modelno) if (orig_atom.residue.name != res.strip() or orig_atom.name != nam.strip()): raise PDBError( 'Atom %d differs in MODEL %d [%s %s ' 'vs. %s %s]' % (atomno, modelno, orig_atom.residue.name, orig_atom.name, res, nam)) coordinates.extend([atom.xx, atom.xy, atom.xz]) elif words[0] == 'ENDMDL': # End the current model if len(struct.atoms) == 0: raise PDBError('MODEL ended before any atoms read in') modelno += 1 if len(struct.atoms) * 3 != len(coordinates): raise PDBError( 'Inconsistent atom numbers in some PDB models') all_coordinates.append(coordinates) atomno = 0 coordinates = [] elif words[0] == 'MODEL': if modelno == 1 and len(struct.atoms) == 0: continue if len(coordinates) > 0: if len(struct.atoms) * 3 != len(coordinates): raise PDBError('Inconsistent atom numbers in ' 'some PDB models') warnings.warn('MODEL not explicitly ended', PDBWarning) all_coordinates.append(coordinates) coordinates = [] modelno += 1 atomno = 0 elif words[0] == 'CRYST1': a, b, c = (float(w) for w in words[1:4]) try: A, B, C = (float(w) for w in words[4:7]) except ValueError: A = B = C = 90.0 struct.box = [a, b, c, A, B, C] finally: if own_handle: fileobj.close() struct.unchange() if coordinates: if len(coordinates) != 3 * len(struct.atoms): raise PDBError('bad number of atoms in some PQR models') all_coordinates.append(coordinates) struct._coordinates = np.array(all_coordinates).reshape( (-1, len(struct.atoms), 3)) return struct
def load_topology(topology, system=None, xyz=None, box=None): """ Creates a :class:`parmed.structure.Structure` instance from an OpenMM Topology, optionally filling in parameters from a System Parameters ---------- topology : :class:`simtk.openmm.app.Topology` The Topology instance with the list of atoms and bonds for this system system : :class:`simtk.openmm.System` or str, optional If provided, parameters from this System will be applied to the Structure. If a string is given, it will be interpreted as the file name of an XML-serialized System, and it will be deserialized into a System before used to supply parameters xyz : str or array of float Name of a file containing coordinate information or an array of coordinates. If file has unit cell information, it also uses that information unless ``box`` (below) is also specified box : array of 6 floats Unit cell dimensions Returns ------- struct : :class:`Structure <parmed.structure.Structure>` The structure from the provided topology Raises ------ OpenMMWarning if parameters are found that cannot be interpreted or processed by ParmEd TypeError if there are any mismatches between the provided topology and system (e.g., they have different numbers of atoms) IOError if system is a string and it is not an existing file Notes ----- Due to its flexibility with CustomForces, it is entirely possible that the functional form of the potential will be unknown to ParmEd. This function will try to use the energy expression to identify supported potential types that are implemented as CustomForce objects. In particular, quadratic improper torsions, when recognized, will be extracted. Other CustomForces, including the CustomNonbondedForce used to implement NBFIX (off-diagonal L-J modifications) and the 12-6-4 potential, will not be processed and will result in an unknown functional form """ import simtk.openmm as mm struct = Structure() atommap = dict() for c in topology.chains(): chain = c.id for r in c.residues(): residue = r.name resid = r.index for a in r.atoms(): if a.element is None: atom = ExtraPoint(name=a.name) else: atom = Atom(atomic_number=a.element.atomic_number, name=a.name, mass=a.element.mass) struct.add_atom(atom, residue, resid, chain) atommap[a] = atom for a1, a2 in topology.bonds(): struct.bonds.append(Bond(atommap[a1], atommap[a2])) vectors = topology.getPeriodicBoxVectors() if vectors is not None: leng, ang = box_vectors_to_lengths_and_angles(*vectors) leng = leng.value_in_unit(u.angstroms) ang = ang.value_in_unit(u.degrees) struct.box = [leng[0], leng[1], leng[2], ang[0], ang[1], ang[2]] loaded_box = False if xyz is not None: if isinstance(xyz, string_types): xyz = load_file(xyz, skip_bonds=True) struct.coordinates = xyz.coordinates if struct.box is not None: if xyz.box is not None: loaded_box = True struct.box = xyz.box else: struct.coordinates = xyz if box is not None: loaded_box = True struct.box = box if struct.box is not None: struct.box = np.asarray(struct.box) if system is None: return struct if isinstance(system, string_types): system = load_file(system) if not isinstance(system, mm.System): raise TypeError('system must be an OpenMM System object or serialized ' 'XML of an OpenMM System object') # We have a system, try to extract parameters from it if len(struct.atoms) != system.getNumParticles(): raise TypeError('Topology and System have different numbers of atoms ' '(%d vs. %d)' % (len(struct.atoms), system.getNumParticles())) processed_forces = set() ignored_forces = (mm.CMMotionRemover, mm.AndersenThermostat, mm.MonteCarloBarostat, mm.MonteCarloAnisotropicBarostat, mm.MonteCarloMembraneBarostat, mm.CustomExternalForce, mm.GBSAOBCForce, mm.CustomGBForce) if system.usesPeriodicBoundaryConditions(): if not loaded_box: vectors = system.getDefaultPeriodicBoxVectors() leng, ang = box_vectors_to_lengths_and_angles(*vectors) leng = leng.value_in_unit(u.angstroms) ang = ang.value_in_unit(u.degrees) struct.box = np.asarray( [leng[0], leng[1], leng[2], ang[0], ang[1], ang[2]]) else: struct.box = None for force in system.getForces(): if isinstance(force, mm.HarmonicBondForce): if mm.HarmonicBondForce in processed_forces: # Try to process this HarmonicBondForce as a Urey-Bradley term _process_urey_bradley(struct, force) else: _process_bond(struct, force) elif isinstance(force, mm.HarmonicAngleForce): _process_angle(struct, force) elif isinstance(force, mm.PeriodicTorsionForce): _process_dihedral(struct, force) elif isinstance(force, mm.RBTorsionForce): _process_rbtorsion(struct, force) elif isinstance(force, mm.CustomTorsionForce): if not _process_improper(struct, force): struct.unknown_functional = True warnings.warn('Unknown functional form of CustomTorsionForce', OpenMMWarning) elif isinstance(force, mm.CMAPTorsionForce): _process_cmap(struct, force) elif isinstance(force, mm.NonbondedForce): _process_nonbonded(struct, force) elif isinstance(force, ignored_forces): continue else: struct.unknown_functional = True warnings.warn('Unsupported Force type %s' % type(force).__name__, OpenMMWarning) processed_forces.add(type(force)) return struct