def writeModel(topology, positions, file=sys.stdout, modelIndex=1, keepIds=False): """Write out a model to a PDBx/mmCIF file. Parameters ---------- topology : Topology The Topology defining the model to write positions : list The list of atomic positions to write file : file=stdout A file to write the model to modelIndex : int=1 The model number of this frame keepIds : bool=False If True, keep the residue and chain IDs specified in the Topology rather than generating new ones. Warning: It is up to the caller to make sure these are valid IDs that satisfy the requirements of the PDBx/mmCIF format. Otherwise, the output file will be invalid. """ if len(list(topology.atoms())) != len(positions): raise ValueError('The number of positions must match the number of atoms') if is_quantity(positions): positions = positions.value_in_unit(angstroms) if any(math.isnan(norm(pos)) for pos in positions): raise ValueError('Particle position is NaN. For more information, see https://github.com/openmm/openmm/wiki/Frequently-Asked-Questions#nan') if any(math.isinf(norm(pos)) for pos in positions): raise ValueError('Particle position is infinite. For more information, see https://github.com/openmm/openmm/wiki/Frequently-Asked-Questions#nan') nonHeterogens = PDBFile._standardResidues[:] nonHeterogens.remove('HOH') atomIndex = 1 posIndex = 0 for (chainIndex, chain) in enumerate(topology.chains()): if keepIds: chainName = chain.id else: chainName = chr(ord('A')+chainIndex%26) residues = list(chain.residues()) for (resIndex, res) in enumerate(residues): if keepIds: resId = res.id resIC = (res.insertionCode if res.insertionCode.strip() else '.') else: resId = resIndex + 1 resIC = '.' if res.name in nonHeterogens: recordName = "ATOM" else: recordName = "HETATM" for atom in res.atoms(): coords = positions[posIndex] if atom.element is not None: symbol = atom.element.symbol else: symbol = '?' line = "%s %5d %-3s %-4s . %-4s %s ? %5s %s %10.4f %10.4f %10.4f 0.0 0.0 ? ? ? ? ? . %5s %4s %s %4s %5d" print(line % (recordName, atomIndex, symbol, atom.name, res.name, chainName, resId, resIC, coords[0], coords[1], coords[2], resId, res.name, chainName, atom.name, modelIndex), file=file) posIndex += 1 atomIndex += 1
def computeLengthsAndAngles(periodicBoxVectors): """Convert periodic box vectors to lengths and angles. Lengths are returned in nanometers and angles in radians. """ if is_quantity(periodicBoxVectors): (a, b, c) = periodicBoxVectors.value_in_unit(nanometers) else: a, b, c = periodicBoxVectors a_length = norm(a) b_length = norm(b) c_length = norm(c) alpha = math.acos(dot(b, c) / (b_length * c_length)) beta = math.acos(dot(c, a) / (c_length * a_length)) gamma = math.acos(dot(a, b) / (a_length * b_length)) return (a_length, b_length, c_length, alpha, beta, gamma)
def testUnitMathModule(self): """ Tests the unit_math functions on Quantity objects """ self.assertEqual(u.sqrt(1.0 * u.kilogram * u.joule), 1.0 * u.kilogram * u.meter / u.second) self.assertEqual(u.sqrt(1.0 * u.kilogram * u.calorie), math.sqrt(4.184) * u.kilogram * u.meter / u.second) self.assertEqual(u.sqrt(9), 3) # Test on a scalar self.assertEqual(u.sin(90 * u.degrees), 1) self.assertEqual(u.sin(math.pi / 2 * u.radians), 1) self.assertEqual(u.sin(math.pi / 2), 1) self.assertEqual(u.cos(180 * u.degrees), -1) self.assertEqual(u.cos(math.pi * u.radians), -1) self.assertEqual(u.cos(math.pi), -1) self.assertAlmostEqual(u.tan(45 * u.degrees), 1) self.assertAlmostEqual(u.tan(math.pi / 4 * u.radians), 1) self.assertAlmostEqual(u.tan(math.pi / 4), 1) acos = u.acos(1.0) asin = u.asin(1.0) atan = u.atan(1.0) self.assertTrue(u.is_quantity(acos)) self.assertTrue(u.is_quantity(asin)) self.assertTrue(u.is_quantity(atan)) self.assertEqual(acos.unit, u.radians) self.assertEqual(asin.unit, u.radians) self.assertEqual(atan.unit, u.radians) self.assertEqual(acos.value_in_unit(u.degrees), 0) self.assertEqual(acos / u.radians, 0) self.assertEqual(asin.value_in_unit(u.degrees), 90) self.assertEqual(asin / u.radians, math.pi / 2) self.assertAlmostEqual(atan.value_in_unit(u.degrees), 45) self.assertAlmostEqual(atan / u.radians, math.pi / 4) # Check some sequence maths seq = [1, 2, 3, 4] * u.meters self.assertEqual(u.sum(seq), 10 * u.meters) self.assertEqual(u.dot(seq, seq), (1 + 4 + 9 + 16) * u.meters**2) self.assertEqual(u.norm(seq), math.sqrt(30) * u.meters)
def writeModel(topology, positions, file=sys.stdout, modelIndex=None, keepIds=False, extraParticleIdentifier='EP'): """Write out a model to a PDB file. Parameters ---------- topology : Topology The Topology defining the model to write positions : list The list of atomic positions to write file : file=stdout A file to write the model to modelIndex : int=None If not None, the model will be surrounded by MODEL/ENDMDL records with this index keepIds : bool=False If True, keep the residue and chain IDs specified in the Topology rather than generating new ones. Warning: It is up to the caller to make sure these are valid IDs that satisfy the requirements of the PDB format. No guarantees are made about what will happen if they are not, and the output file could be invalid. extraParticleIdentifier : string='EP' String to write in the element column of the ATOM records for atoms whose element is None (extra particles) """ if len(list(topology.atoms())) != len(positions): raise ValueError( 'The number of positions must match the number of atoms') if is_quantity(positions): positions = positions.value_in_unit(angstroms) if any(math.isnan(norm(pos)) for pos in positions): raise ValueError('Particle position is NaN') if any(math.isinf(norm(pos)) for pos in positions): raise ValueError('Particle position is infinite') nonHeterogens = PDBFile._standardResidues[:] nonHeterogens.remove('HOH') atomIndex = 1 posIndex = 0 if modelIndex is not None: print("MODEL %4d" % modelIndex, file=file) for (chainIndex, chain) in enumerate(topology.chains()): if keepIds and len(chain.id) == 1: chainName = chain.id else: chainName = chr(ord('A') + chainIndex % 26) residues = list(chain.residues()) for (resIndex, res) in enumerate(residues): if len(res.name) > 3: resName = res.name[:3] else: resName = res.name if keepIds and len(res.id) < 5: resId = res.id else: resId = "%4d" % ((resIndex + 1) % 10000) if len(res.insertionCode) == 1: resIC = res.insertionCode else: resIC = " " if res.name in nonHeterogens: recordName = "ATOM " else: recordName = "HETATM" for atom in res.atoms(): if atom.element is not None: symbol = atom.element.symbol else: symbol = extraParticleIdentifier if len(atom.name) < 4 and atom.name[:1].isalpha( ) and len(symbol) < 2: atomName = ' ' + atom.name elif len(atom.name) > 4: atomName = atom.name[:4] else: atomName = atom.name coords = positions[posIndex] line = "%s%5d %-4s %3s %s%4s%1s %s%s%s 1.00 0.00 %2s " % ( recordName, atomIndex % 100000, atomName, resName, chainName, resId, resIC, _format_83(coords[0]), _format_83(coords[1]), _format_83(coords[2]), symbol) if len(line) != 80: raise ValueError('Fixed width overflow detected') print(line, file=file) posIndex += 1 atomIndex += 1 if resIndex == len(residues) - 1: print("TER %5d %3s %s%4s" % (atomIndex, resName, chainName, resId), file=file) atomIndex += 1 if modelIndex is not None: print("ENDMDL", file=file)
def writeModel(self, positions, unitCellDimensions=None, periodicBoxVectors=None): """Write out a model to the DCD file. The periodic box can be specified either by the unit cell dimensions (for a rectangular box), or the full set of box vectors (for an arbitrary triclinic box). If neither is specified, the box vectors specified in the Topology will be used. Regardless of the value specified, no dimensions will be written if the Topology does not represent a periodic system. Parameters ---------- positions : list The list of atomic positions to write unitCellDimensions : Vec3=None The dimensions of the crystallographic unit cell. periodicBoxVectors : tuple of Vec3=None The vectors defining the periodic box. """ if len(list(self._topology.atoms())) != len(positions): raise ValueError('The number of positions must match the number of atoms') if is_quantity(positions): positions = positions.value_in_unit(nanometers) if any(math.isnan(norm(pos)) for pos in positions): raise ValueError('Particle position is NaN') if any(math.isinf(norm(pos)) for pos in positions): raise ValueError('Particle position is infinite') file = self._file self._modelCount += 1 if self._interval > 1 and self._firstStep+self._modelCount*self._interval > 1<<31: # This will exceed the range of a 32 bit integer. To avoid crashing or producing a corrupt file, # update the header to say the trajectory consisted of a smaller number of larger steps (so the # total trajectory length remains correct). self._firstStep //= self._interval self._dt *= self._interval self._interval = 1 file.seek(0, os.SEEK_SET) file.write(struct.pack('<i4c9if', 84, b'C', b'O', b'R', b'D', 0, self._firstStep, self._interval, 0, 0, 0, 0, 0, 0, self._dt)) # Update the header. file.seek(8, os.SEEK_SET) file.write(struct.pack('<i', self._modelCount)) file.seek(20, os.SEEK_SET) file.write(struct.pack('<i', self._firstStep+self._modelCount*self._interval)) # Write the data. file.seek(0, os.SEEK_END) boxVectors = self._topology.getPeriodicBoxVectors() if boxVectors is not None: if periodicBoxVectors is not None: boxVectors = periodicBoxVectors elif unitCellDimensions is not None: if is_quantity(unitCellDimensions): unitCellDimensions = unitCellDimensions.value_in_unit(nanometers) boxVectors = (Vec3(unitCellDimensions[0], 0, 0), Vec3(0, unitCellDimensions[1], 0), Vec3(0, 0, unitCellDimensions[2]))*nanometers (a_length, b_length, c_length, alpha, beta, gamma) = computeLengthsAndAngles(boxVectors) a_length = a_length * 10. # computeLengthsAndAngles returns unitless nanometers, but need angstroms here. b_length = b_length * 10. # computeLengthsAndAngles returns unitless nanometers, but need angstroms here. c_length = c_length * 10. # computeLengthsAndAngles returns unitless nanometers, but need angstroms here. angle1 = math.sin(math.pi/2-gamma) angle2 = math.sin(math.pi/2-beta) angle3 = math.sin(math.pi/2-alpha) file.write(struct.pack('<i6di', 48, a_length, angle1, b_length, angle2, angle3, c_length, 48)) length = struct.pack('<i', 4*len(positions)) for i in range(3): file.write(length) data = array.array('f', (10*x[i] for x in positions)) data.tofile(file) file.write(length) try: file.flush() except AttributeError: pass