def testScalarQuantityConstructor(self): """ Tests creating a Quantity using the Quantity constructor """ self.assertTrue(u.is_quantity(u.Quantity(5, u.centimeters))) self.assertTrue(u.is_quantity(u.Quantity(5, u.centimeters**-1))) x = u.Quantity(value=5.0, unit=100.0 * u.meters) self.assertTrue(u.is_quantity(x)) self.assertEqual(x, 500 * u.meters)
def testNumpyDeepCopy(self): """ Check that deepcopy on numpy array does not strip units """ x = u.Quantity(np.zeros((2, 3)), u.nanometer) y = copy.deepcopy(x) self.assertTrue(np.all(x == y)) self.assertTrue(u.is_quantity(x)) self.assertTrue(u.is_quantity(y))
def testCollectionQuantities(self): """ Tests the use of collections as Quantity values """ s = [1, 2, 3] * u.centimeters self.assertEqual(str(s), '[1, 2, 3] cm') self.assertTrue(u.is_quantity(s)) s2 = s / u.millimeters self.assertEqual(s2, [10.0, 20.0, 30.0]) self.assertEqual(s2, s.value_in_unit(u.millimeters)) # Test 2-D list s = [[1, 2, 3], [4, 5, 6]] s *= u.centimeters self.assertTrue(u.is_quantity(s)) s2 = s / u.millimeters self.assertEqual(s2, [[10.0, 20.0, 30.0], [40.0, 50.0, 60.0]]) self.assertEqual(s.value_in_unit(u.millimeters), s2) # Test tuples s = (1, 2, 3) * u.centimeters self.assertTrue(u.is_quantity(s)) self.assertEqual(str(s), '(1, 2, 3) cm') s2 = s / u.millimeters self.assertEqual(s2, (10, 20, 30)) self.assertIsInstance(s2, tuple) self.assertEqual(s.value_in_unit(u.millimeters), s2) self.assertIsInstance(s.value_in_unit(u.millimeters), tuple) x = [1, 2, 3] * u.centimeters x *= u.meters self.assertEqual(x, [100, 200, 300] * u.centimeters**2)
def getEffectiveEnergy(self, totalEnergy, groupEnergy): """Given the actual potential energy of the system, return the value of the effective potential. Parameters ---------- totalEnergy : energy the actual potential energy of the whole system groupEnergy : energy the actual potential energy of the boosted force group Returns ------- energy the value of the effective potential """ alphaTotal = self.getAlphaTotal() ETotal = self.getETotal() alphaGroup = self.getAlphaGroup() EGroup = self.getEGroup() if not is_quantity(totalEnergy): totalEnergy = totalEnergy * kilojoules_per_mole # Assume kJ/mole if not is_quantity(groupEnergy): groupEnergy = groupEnergy * kilojoules_per_mole # Assume kJ/mole dE = 0.0 * kilojoules_per_mole if (totalEnergy < ETotal): dE = dE + (ETotal - totalEnergy) * (ETotal - totalEnergy) / ( alphaTotal + ETotal - totalEnergy) if (groupEnergy < EGroup): dE = dE + (EGroup - groupEnergy) * (EGroup - groupEnergy) / ( alphaGroup + EGroup - groupEnergy) return totalEnergy + dE
def testAngleQuantities(self): """ Tests angle measurements """ self.assertEqual(1.0 * u.radians / u.degrees, 180 / math.pi) self.assertTrue(u.is_quantity(1.0 * u.radians)) self.assertTrue(u.is_quantity(1.0 * u.degrees)) self.assertEqual((1.0 * u.radians).in_units_of(u.degrees), (180 / math.pi) * u.degrees) self.assertEqual(90 * u.degrees / u.radians, math.pi / 2) q = 90 * u.degrees + 0.3 * u.radians self.assertEqual(q._value, 90 + 180 * 0.3 / math.pi) self.assertEqual(q.unit, u.degrees)
def runForClockTime(self, time, checkpointFile=None, stateFile=None, checkpointInterval=None): """Advance the simulation by integrating time steps until a fixed amount of clock time has elapsed. This is useful when you have a limited amount of computer time available, and want to run the longest simulation possible in that time. This method will continue taking time steps until the specified clock time has elapsed, then return. It also can automatically write out a checkpoint and/or state file before returning, so you can later resume the simulation. Another option allows it to write checkpoints or states at regular intervals, so you can resume even if the simulation is interrupted before the time limit is reached. Parameters ---------- time : time the amount of time to run for. If no units are specified, it is assumed to be a number of hours. checkpointFile : string or file=None if specified, a checkpoint file will be written at the end of the simulation (and optionally at regular intervals before then) by passing this to saveCheckpoint(). stateFile : string or file=None if specified, a state file will be written at the end of the simulation (and optionally at regular intervals before then) by passing this to saveState(). checkpointInterval : time=None if specified, checkpoints and/or states will be written at regular intervals during the simulation, in addition to writing a final version at the end. If no units are specified, this is assumed to be in hours. """ if unit.is_quantity(time): time = time.value_in_unit(unit.hours) if unit.is_quantity(checkpointInterval): checkpointInterval = checkpointInterval.value_in_unit(unit.hours) endTime = datetime.now() + timedelta(hours=time) while (datetime.now() < endTime): if checkpointInterval is None: nextTime = endTime else: nextTime = datetime.now() + timedelta(hours=checkpointInterval) if nextTime > endTime: nextTime = endTime self._simulate(endTime=nextTime) if checkpointFile is not None: self.saveCheckpoint(checkpointFile) if stateFile is not None: self.saveState(stateFile)
def _initializeConstants(self, simulation): """Initialize a set of constants required for the reports Parameters - simulation (Simulation) The simulation to generate a report for """ system = simulation.system if self._temperature: # Compute the number of degrees of freedom. dof = 0 for i in range(system.getNumParticles()): if system.getParticleMass(i) > 0 * unit.dalton: dof += 3 for i in range(system.getNumConstraints()): p1, p2, distance = system.getConstraintParameters(i) if system.getParticleMass( p1) > 0 * unit.dalton or system.getParticleMass( p2) > 0 * unit.dalton: dof -= 1 if any( type(system.getForce(i)) == mm.CMMotionRemover for i in range(system.getNumForces())): dof -= 3 self._dof = dof if self._density: if self._totalMass is None: # Compute the total system mass. self._totalMass = 0 * unit.dalton for i in range(system.getNumParticles()): self._totalMass += system.getParticleMass(i) elif not unit.is_quantity(self._totalMass): self._totalMass = self._totalMass * unit.dalton
def testDimensionless(self): """ Tests the properties of unit.dimensionless """ x = 5 * u.dimensionless y = u.Quantity(5, u.dimensionless) self.assertTrue(u.is_quantity(x)) self.assertTrue(u.is_quantity(y)) self.assertNotEqual(x, 5) self.assertNotEqual(y, 5) self.assertEqual(x, y) self.assertEqual(x.value_in_unit_system(u.si_unit_system), 5) self.assertEqual(x.value_in_unit_system(u.cgs_unit_system), 5) self.assertEqual(x.value_in_unit_system(u.md_unit_system), 5) x = u.Quantity(1.0, u.dimensionless) y = u.Quantity(1.0, u.dimensionless) self.assertIsNot(x, y) self.assertEqual(x, y)
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 _strip_optunit(thing, unit): """ Strips optional units, converting to specified unit type. If no unit present, it just returns the number """ if u.is_quantity(thing): return thing.value_in_unit(unit) return thing
def testCollectionQuantityOperations(self): """ Tests that Quantity collections behave correctly """ # Tests that __getitem__ returns a unit s = [1, 2, 3, 4] * u.angstroms self.assertTrue(u.is_quantity(s[0])) for i, val in enumerate(s): self.assertTrue(u.is_quantity(val)) self.assertEqual(val, (i + 1) * u.angstroms) # Tests that __setitem__ fails when an incompatible type is added def fail(s): s[0] = 5 self.assertRaises(AttributeError, lambda: fail(s)) def fail(s): s[0] = 5 * u.joules self.assertRaises(TypeError, lambda: fail(s)) def fail(s): s[0] /= 10 * u.meters self.assertRaises(AttributeError, lambda: fail(s)) # Tests that __setitem__ converts to the unit of the container s[0] = 1 * u.nanometers self.assertEqual(s[0]._value, 10) # Tests that __setitem__ handles slice assignment correctly x = [0, 1, 2, 3, 4] * u.kelvin x[2:4] = [-2, -3] * u.kelvin self.assertEqual(x._value, [0, 1, -2, -3, 4]) # Tests standard unit conversions x = [1, 2, 3] * u.centimeters self.assertEqual(x / u.millimeters, [10, 20, 30]) # Test the construction of a container in which each element is a # Quantity, passed to the Quantity constructor x = u.Quantity([1 * u.angstrom, 2 * u.nanometer, 3 * u.angstrom]) self.assertEqual(x._value, [1, 20, 3]) self.assertEqual(x.unit, u.angstrom) x = u.Quantity((1, 2, 3)) self.assertTrue(u.is_quantity(x)) self.assertTrue(x.unit.is_dimensionless()) x = u.Quantity(([1 * u.angstrom, 2 * u.nanometer, 3 * u.angstrom], [1 * u.angstrom, 4 * u.nanometer, 3 * u.angstrom])) self.assertEqual(x._value, ([1, 20, 3], [1, 40, 3])) self.assertEqual(x.unit, u.angstrom) self.assertTrue(u.is_quantity(u.Quantity([])))
def __init__(self, file, topology, dt, firstStep=0, interval=1, append=False): """Create a DCD file and write out the header, or open an existing file to append. Parameters ---------- file : file A file to write to topology : Topology The Topology defining the molecular system being written dt : time The time step used in the trajectory firstStep : int=0 The index of the first step in the trajectory interval : int=1 The frequency (measured in time steps) at which states are written to the trajectory append : bool=False If True, open an existing DCD file to append to. If False, create a new file. """ self._file = file self._topology = topology self._firstStep = firstStep self._interval = interval self._modelCount = 0 if is_quantity(dt): dt = dt.value_in_unit(picoseconds) dt /= 0.04888821 self._dt = dt boxFlag = 0 if topology.getUnitCellDimensions() is not None: boxFlag = 1 if append: file.seek(8, os.SEEK_SET) self._modelCount = struct.unpack('<i', file.read(4))[0] file.seek(268, os.SEEK_SET) numAtoms = struct.unpack('<i', file.read(4))[0] if numAtoms != len(list(topology.atoms())): raise ValueError( 'Cannot append to a DCD file that contains a different number of atoms' ) else: header = struct.pack('<i4c9if', 84, b'C', b'O', b'R', b'D', 0, firstStep, interval, 0, 0, 0, 0, 0, 0, dt) header += struct.pack('<13i', boxFlag, 0, 0, 0, 0, 0, 0, 0, 0, 21, 84, 164, 2) header += struct.pack('<80s', b'Created by OpenMM') header += struct.pack( '<80s', b'Created ' + time.asctime(time.localtime(time.time())).encode('ascii')) header += struct.pack('<4i', 164, 4, len(list(topology.atoms())), 4) file.write(header)
def getEffectiveEnergy(self, energy): """Given the actual potential energy of the system, return the value of the effective potential.""" alpha = self.getAlpha() E = self.getE() if not is_quantity(energy): energy = energy * kilojoules_per_mole # Assume kJ/mole if (energy > E): return energy return energy + (E - energy) * (E - energy) / (alpha + E - energy)
def testNumpyFunctions(self): """ Tests various numpy attributes that they result in Quantities """ a = u.Quantity(np.arange(10), u.seconds) self.assertEqual(a.max(), 9 * u.seconds) self.assertEqual(a.min(), 0 * u.seconds) self.assertEqual(a.mean(), 4.5 * u.seconds) self.assertAlmostEqualQuantities(a.std(), 2.8722813232690143 * u.seconds) b = a.reshape((5, 2)) self.assertTrue(u.is_quantity(b))
def testNumpyDivision(self): """ Tests that division of numpy Quantities works correctly """ x = u.Quantity(np.asarray([1., 2.]), u.nanometers) y = u.Quantity(np.asarray([3., 4.]), u.picoseconds) xy = x / y self.assertTrue(u.is_quantity(xy)) self.assertEqual(xy.unit, u.nanometers / u.picoseconds) self.assertEqual(xy[0].value_in_unit(u.nanometers / u.picoseconds), 1 / 3) self.assertEqual(xy[1].value_in_unit(u.nanometers / u.picoseconds), 0.5)
def setUnitCellDimensions(self, dimensions): """Set the dimensions of the crystallographic unit cell. This method is an alternative to setPeriodicBoxVectors() for the case of a rectangular box. It sets the box vectors to be orthogonal to each other and to have the specified lengths.""" if dimensions is None: self._periodicBoxVectors = None else: if is_quantity(dimensions): dimensions = dimensions.value_in_unit(nanometers) self._periodicBoxVectors = (Vec3(dimensions[0], 0, 0), Vec3(0, dimensions[1], 0), Vec3(0, 0, dimensions[2])) * nanometers
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 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 reducePeriodicBoxVectors(periodicBoxVectors): """ Reduces the representation of the PBC. periodicBoxVectors is expected to be an unpackable iterable of length-3 iterables """ if is_quantity(periodicBoxVectors): a, b, c = periodicBoxVectors.value_in_unit(nanometers) else: a, b, c = periodicBoxVectors a = Vec3(*a) b = Vec3(*b) c = Vec3(*c) c = c - b * round(c[1] / b[1]) c = c - a * round(c[0] / a[0]) b = b - a * round(b[0] / a[0]) return (a, b, c) * nanometers
def testQuantityMaths(self): """ Tests dimensional analysis & maths on and b/w Quantity objects """ x = 1.3 * u.meters y = 75.2 * u.centimeters self.assertEqual((x + y) / u.meters, 2.052) self.assertEqual((x - y) / u.meters, 0.548) self.assertEqual(x / y, 1.3 / 0.752) self.assertEqual(x * y, 1.3 * 0.752 * u.meters**2) d1 = 2.0 * u.meters d2 = 2.0 * u.nanometers self.assertEqual(d1 + d2, (2 + 2e-9) * u.meters) self.assertAlmostEqual((d2 + d1 - (2e9 + 2) * u.nanometers)._value, 0, places=6) self.assertEqual(d1 + d1, 4.0 * u.meters) self.assertEqual(d1 - d1, 0.0 * u.meters) self.assertEqual(d1 / d1, 1.0) self.assertEqual(d1 * u.meters, 2.0 * u.meters**2) self.assertEqual(u.kilograms * (d1 / u.seconds) * (d1 / u.seconds), 4 * u.kilograms * u.meters**2 / u.seconds**2) self.assertEqual(u.kilograms * (d1 / u.seconds)**2, 4 * u.kilograms * u.meters**2 / u.seconds**2) self.assertEqual(d1**3, 8.0 * u.meters**3) x = d1**(3 / 2) self.assertAlmostEqual(x._value, math.sqrt(2)**3) self.assertEqual(x.unit, u.meters**(3 / 2)) self.assertAlmostEqual((d1**0.5)._value, math.sqrt(2)) self.assertEqual((d1**0.5).unit, u.meters**0.5) comp = (3.0 + 4.0j) * u.meters self.assertTrue(u.is_quantity(comp)) self.assertEqual(comp.unit, u.meters) self.assertEqual(str(comp), '(3+4j) m') self.assertEqual(comp + comp, (6.0 + 8.0j) * u.meters) self.assertEqual(comp - comp, 0 * u.meters) self.assertEqual(comp * comp, (3.0 + 4.0j)**2 * u.meters**2) self.assertAlmostEqual(comp / comp, 1) self.assertAlmostEqual(1.5 * u.nanometers / u.meters, 1.5e-9, places=15) self.assertEqual((2.3 * u.meters)**2, 2.3**2 * u.meters**2) x = 4.3 * u.meters self.assertEqual(x / u.centimeters, 430) self.assertEqual(str(x / u.seconds), '4.3 m/s') self.assertEqual(str(8.4 / (4.2 * u.centimeters)), '2.0 /cm') x = 1.2 * u.meters self.assertEqual(x * 5, u.Quantity(6.0, u.meters))
def getByMass(mass): """ Get the element whose mass is CLOSEST to the requested mass. This method should not be used for repartitioned masses Parameters ---------- mass : float or Quantity Mass of the atom to find the element for. Units assumed to be daltons if not specified Returns ------- Element The element whose atomic mass is closest to the input mass """ # Assume masses are in daltons if they are not units if is_quantity(mass): mass = mass.value_in_unit(daltons) if mass < 0: raise ValueError('Invalid Higgs field') # If this is our first time calling getByMass (or we added an element # since the last call), re-generate the ordered by-mass dict cache if Element._elements_by_mass is None: Element._elements_by_mass = OrderedDict() for elem in sorted(Element._elements_by_symbol.values(), key=lambda x: x.mass): Element._elements_by_mass[elem.mass.value_in_unit(daltons)] = elem diff = mass best_guess = None for elemmass, element in _iteritems(Element._elements_by_mass): massdiff = abs(elemmass - mass) if massdiff < diff: best_guess = element diff = massdiff if elemmass > mass: # Elements are only getting heavier, so bail out early return best_guess # This really should only happen if we wanted ununoctium or something # bigger... won't really happen but still make sure we return an Element return best_guess
def setPeriodicBoxVectors(self, vectors): """Set the vectors defining the periodic box.""" if vectors is not None: if not is_quantity(vectors[0][0]): vectors = vectors * nanometers if vectors[0][1] != 0 * nanometers or vectors[0][ 2] != 0 * nanometers: raise ValueError( "First periodic box vector must be parallel to x.") if vectors[1][2] != 0 * nanometers: raise ValueError( "Second periodic box vector must be in the x-y plane.") if vectors[0][0] <= 0 * nanometers or vectors[1][ 1] <= 0 * nanometers or vectors[2][ 2] <= 0 * nanometers or vectors[0][0] < 2 * abs( vectors[1][0]) or vectors[0][0] < 2 * abs( vectors[2][0]) or vectors[1][1] < 2 * abs( vectors[2][1]): raise ValueError( "Periodic box vectors must be in reduced form.") self._periodicBoxVectors = deepcopy(vectors)
def testChemistryProblems(self): """ Tests some gen-chem applications with Quantity's """ def work(f, dx): return f * dx F = 1.0 * u.kilogram * u.meter / u.second**2 dx = 1.0 * u.meter self.assertEqual(work(F, dx), 1.0 * u.joule) self.assertEqual(F, 1.0 * u.newton) def ideal_gas_law(P, V, T): R = u.MOLAR_GAS_CONSTANT_R return (P * V / (R * T)).in_units_of(u.mole) T = (273.0 + 37.0) * u.kelvin P = (1.01325e5) * u.pascals r = 0.5e-6 * u.meters V = 4 / 3 * math.pi * r**3 n = ideal_gas_law(P, V, T) val = 4 / 3 * math.pi * 0.5e-6**3 * 1 self.assertAlmostEqualQuantities(P * V, val * u.atmospheres * u.meters**3) self.assertAlmostEqualQuantities(n, 2.05834818672e-17 * u.mole) self.assertAlmostEqualQuantities(V, 5.2359833333333e-19 * u.meters**3) self.assertEqual(str(T), '310.0 K') self.assertEqual(str(1 * u.joules / u.kelvin / u.mole), '1 J/(K mol)') self.assertTrue(u.is_quantity(V)) # Checks trouble with complicated unit conversion factors p1 = 1.0 * u.atmospheres p2 = p1.in_units_of(u.joules / u.nanometers**3) V = 2.4 * u.nanometers**3 beta = 4.e-4 * u.mole / u.joule x1 = beta * p1 * V y1 = x1 * u.AVOGADRO_CONSTANT_NA self.assertAlmostEqual(y1, 0.0585785776197) x2 = beta * p2 * V y2 = x2 * u.AVOGADRO_CONSTANT_NA self.assertAlmostEqual(y1, y2)
def computePeriodicBoxVectors(a_length, b_length, c_length, alpha, beta, gamma): """Convert lengths and angles to periodic box vectors. Lengths should be given in nanometers and angles in radians (or as Quantity instances) """ if is_quantity(a_length): a_length = a_length.value_in_unit(nanometers) if is_quantity(b_length): b_length = b_length.value_in_unit(nanometers) if is_quantity(c_length): c_length = c_length.value_in_unit(nanometers) if is_quantity(alpha): alpha = alpha.value_in_unit(radians) if is_quantity(beta): beta = beta.value_in_unit(radians) if is_quantity(gamma): gamma = gamma.value_in_unit(radians) # Compute the vectors. a = [a_length, 0, 0] b = [b_length * math.cos(gamma), b_length * math.sin(gamma), 0] cx = c_length * math.cos(beta) cy = c_length * (math.cos(alpha) - math.cos(beta) * math.cos(gamma)) / math.sin(gamma) cz = math.sqrt(c_length * c_length - cx * cx - cy * cy) c = [cx, cy, cz] # If any elements are very close to 0, set them to exactly 0. for i in range(3): if abs(a[i]) < 1e-6: a[i] = 0.0 if abs(b[i]) < 1e-6: b[i] = 0.0 if abs(c[i]) < 1e-6: c[i] = 0.0 a = Vec3(*a) b = Vec3(*b) c = Vec3(*c) # Make sure they're in the reduced form required by OpenMM. c = c - b * round(c[1] / b[1]) c = c - a * round(c[0] / a[0]) b = b - a * round(b[0] / a[0]) return (a, b, c) * nanometers
def __init__(self, system, variables, temperature, biasFactor, height, frequency, saveFrequency=None, biasDir=None): """Create a Metadynamics object. Parameters ---------- system: System the System to simulate. A CustomCVForce implementing the bias is created and added to the System. variables: list of BiasVariables the collective variables to sample temperature: temperature the temperature at which the simulation is being run. This is used in computing the free energy. biasFactor: float used in scaling the height of the Gaussians added to the bias. The collective variables are sampled as if the effective temperature of the simulation were temperature*biasFactor. height: energy the initial height of the Gaussians to add frequency: int the interval in time steps at which Gaussians should be added to the bias potential saveFrequency: int (optional) the interval in time steps at which to write out the current biases to disk. At the same time it writes biases, it also checks for updated biases written by other processes and loads them in. This must be a multiple of frequency. biasDir: str (optional) the directory to which biases should be written, and from which biases written by other processes should be loaded """ if not unit.is_quantity(temperature): temperature = temperature * unit.kelvin if not unit.is_quantity(height): height = height * unit.kilojoules_per_mole if biasFactor < 1.0: raise ValueError('biasFactor must be >= 1') if (saveFrequency is None and biasDir is not None) or (saveFrequency is not None and biasDir is None): raise ValueError('Must specify both saveFrequency and biasDir') if saveFrequency is not None and (saveFrequency < frequency or saveFrequency % frequency != 0): raise ValueError('saveFrequency must be a multiple of frequency') self.variables = variables self.temperature = temperature self.biasFactor = biasFactor self.height = height self.frequency = frequency self.biasDir = biasDir self.saveFrequency = saveFrequency self._id = np.random.randint(0x7FFFFFFF) self._saveIndex = 0 self._selfBias = np.zeros( tuple(v.gridWidth for v in reversed(variables))) self._totalBias = np.zeros( tuple(v.gridWidth for v in reversed(variables))) self._loadedBiases = {} self._syncWithDisk() self._deltaT = temperature * (biasFactor - 1) varNames = ['cv%d' % i for i in range(len(variables))] self._force = mm.CustomCVForce('table(%s)' % ', '.join(varNames)) for name, var in zip(varNames, variables): self._force.addCollectiveVariable(name, var.force) self._widths = [v.gridWidth for v in variables] self._limits = sum(([v.minValue, v.maxValue] for v in variables), []) numPeriodics = sum(v.periodic for v in variables) if numPeriodics not in [0, len(variables)]: raise ValueError( 'Metadynamics cannot handle mixed periodic/non-periodic variables' ) periodic = numPeriodics == len(variables) if len(variables) == 1: self._table = mm.Continuous1DFunction(self._totalBias.flatten(), *self._limits, periodic) elif len(variables) == 2: self._table = mm.Continuous2DFunction(*self._widths, self._totalBias.flatten(), *self._limits, periodic) elif len(variables) == 3: self._table = mm.Continuous3DFunction(*self._widths, self._totalBias.flatten(), *self._limits, periodic) else: raise ValueError( 'Metadynamics requires 1, 2, or 3 collective variables') self._force.addTabulatedFunction('table', self._table) freeGroups = set(range(32)) - set(force.getForceGroup() for force in system.getForces()) if len(freeGroups) == 0: raise RuntimeError( 'Cannot assign a force group to the metadynamics force. ' 'The maximum number (32) of the force groups is already used.') self._force.setForceGroup(max(freeGroups)) system.addForce(self._force)
def _standardize(self, quantity): if unit.is_quantity(quantity): return quantity.value_in_unit_system(unit.md_unit_system) else: return quantity
def createSystem(self, nonbondedMethod=ff.NoCutoff, nonbondedCutoff=1.0 * u.nanometer, constraints=None, rigidWater=True, implicitSolvent=None, implicitSolventSaltConc=0.0 * (u.moles / u.liter), implicitSolventKappa=None, temperature=298.15 * u.kelvin, soluteDielectric=1.0, solventDielectric=78.5, removeCMMotion=True, hydrogenMass=None, ewaldErrorTolerance=0.0005, switchDistance=0.0 * u.nanometer, gbsaModel='ACE'): """Construct an OpenMM System representing the topology described by this prmtop file. Parameters ---------- nonbondedMethod : object=NoCutoff The method to use for nonbonded interactions. Allowed values are NoCutoff, CutoffNonPeriodic, CutoffPeriodic, Ewald, PME, or LJPME. nonbondedCutoff : distance=1*nanometer The cutoff distance to use for nonbonded interactions constraints : object=None Specifies which bonds angles should be implemented with constraints. Allowed values are None, HBonds, AllBonds, or HAngles. rigidWater : boolean=True If true, water molecules will be fully rigid regardless of the value passed for the constraints argument implicitSolvent : object=None If not None, the implicit solvent model to use. Allowed values are HCT, OBC1, OBC2, GBn, or GBn2. implicitSolventSaltConc : float=0.0*unit.moles/unit.liter The salt concentration for GB calculations (modelled as a debye screening parameter). It is converted to the debye length (kappa) using the provided temperature and solventDielectric temperature : float=300*kelvin Temperature of the system. Only used to compute the Debye length from implicitSolventSoltConc implicitSolventKappa : float units of 1/length If this value is set, implicitSolventSaltConc will be ignored. soluteDielectric : float=1.0 The solute dielectric constant to use in the implicit solvent model. solventDielectric : float=78.5 The solvent dielectric constant to use in the implicit solvent model. removeCMMotion : boolean=True If true, a CMMotionRemover will be added to the System hydrogenMass : mass=None The mass to use for hydrogen atoms bound to heavy atoms. Any mass added to a hydrogen is subtracted from the heavy atom to keep their total mass the same. If rigidWater is used to make water molecules rigid, then water hydrogens are not altered. ewaldErrorTolerance : float=0.0005 The error tolerance to use if nonbondedMethod is Ewald, PME, or LJPME. switchDistance : float=0*nanometers The distance at which the potential energy switching function is turned on for Lennard-Jones interactions. If the switchDistance is 0 or evaluates to boolean False, no switching function will be used. Values greater than nonbondedCutoff or less than 0 raise ValueError gbsaModel : str='ACE' The SA model used to model the nonpolar solvation component of GB implicit solvent models. If GB is active, this must be 'ACE' or None (the latter indicates no SA model will be used). Other values will result in a ValueError Returns ------- System the newly created System """ methodMap = { ff.NoCutoff: 'NoCutoff', ff.CutoffNonPeriodic: 'CutoffNonPeriodic', ff.CutoffPeriodic: 'CutoffPeriodic', ff.Ewald: 'Ewald', ff.PME: 'PME', ff.LJPME: 'LJPME' } if nonbondedMethod not in methodMap: raise ValueError('Illegal value for nonbonded method') if not self._prmtop.getIfBox() and nonbondedMethod in ( ff.CutoffPeriodic, ff.Ewald, ff.PME, ff.LJPME): raise ValueError( 'Illegal nonbonded method for a non-periodic system') constraintMap = { None: None, ff.HBonds: 'h-bonds', ff.AllBonds: 'all-bonds', ff.HAngles: 'h-angles' } if constraints is None: constraintString = None elif constraints in constraintMap: constraintString = constraintMap[constraints] else: raise ValueError('Illegal value for constraints') if implicitSolvent is None: implicitString = None elif implicitSolvent is HCT: implicitString = 'HCT' elif implicitSolvent is OBC1: implicitString = 'OBC1' elif implicitSolvent is OBC2: implicitString = 'OBC2' elif implicitSolvent is GBn: implicitString = 'GBn' elif implicitSolvent is GBn2: implicitString = 'GBn2' else: raise ValueError('Illegal value for implicit solvent model') # If implicitSolventKappa is None, compute it from the salt concentration if implicitSolvent is not None and implicitSolventKappa is None: if u.is_quantity(implicitSolventSaltConc): implicitSolventSaltConc = implicitSolventSaltConc.value_in_unit( u.moles / u.liter) if u.is_quantity(temperature): temperature = temperature.value_in_unit(u.kelvin) # The constant is 1 / sqrt( epsilon_0 * kB / (2 * NA * q^2 * 1000) ) # where NA is avogadro's number, epsilon_0 is the permittivity of # free space, q is the elementary charge (this number matches # Amber's kappa conversion factor) implicitSolventKappa = 50.33355 * sqrt( implicitSolventSaltConc / solventDielectric / temperature) # Multiply by 0.73 to account for ion exclusions, and multiply by 10 # to convert to 1/nm from 1/angstroms implicitSolventKappa *= 7.3 elif implicitSolvent is None: implicitSolventKappa = 0.0 sys = amber_file_parser.readAmberSystem( self.topology, prmtop_loader=self._prmtop, shake=constraintString, nonbondedCutoff=nonbondedCutoff, nonbondedMethod=methodMap[nonbondedMethod], flexibleConstraints=False, gbmodel=implicitString, soluteDielectric=soluteDielectric, solventDielectric=solventDielectric, implicitSolventKappa=implicitSolventKappa, rigidWater=rigidWater, elements=self.elements, gbsaModel=gbsaModel) if hydrogenMass is not None: for atom1, atom2 in self.topology.bonds(): if atom1.element == elem.hydrogen: (atom1, atom2) = (atom2, atom1) if rigidWater and atom2.residue.name == 'HOH': continue if atom2.element == elem.hydrogen and atom1.element not in ( elem.hydrogen, None): transferMass = hydrogenMass - sys.getParticleMass( atom2.index) sys.setParticleMass(atom2.index, hydrogenMass) sys.setParticleMass( atom1.index, sys.getParticleMass(atom1.index) - transferMass) for force in sys.getForces(): if isinstance(force, mm.NonbondedForce): force.setEwaldErrorTolerance(ewaldErrorTolerance) if isinstance(force, (mm.NonbondedForce, mm.CustomNonbondedForce)): if switchDistance and nonbondedMethod is not ff.NoCutoff: # make sure it's legal if (_strip_optunit(switchDistance, u.nanometer) >= _strip_optunit(nonbondedCutoff, u.nanometer)): raise ValueError( 'switchDistance is too large compared ' 'to the cutoff!') if _strip_optunit(switchDistance, u.nanometer) < 0: # Detects negatives for both Quantity and float raise ValueError( 'switchDistance must be non-negative!') force.setUseSwitchingFunction(True) force.setSwitchingDistance(switchDistance) if removeCMMotion: sys.addForce(mm.CMMotionRemover()) return sys
def strip_unit(value, unit): """Strip off any units and return value in unit""" if not u.is_quantity(value): return value return value.value_in_unit(unit)
def __init__(self, simulation, temperatures=None, numTemperatures=None, minTemperature=None, maxTemperature=None, weights=None, tempChangeInterval=25, reportInterval=1000, reportFile=stdout): """Create a new SimulatedTempering. Parameters ---------- simulation: Simulation The Simulation defining the System, Context, and Integrator to use temperatures: list The list of temperatures to use for tempering, in increasing order numTemperatures: int The number of temperatures to use for tempering. If temperatures is not None, this is ignored. minTemperature: temperature The minimum temperature to use for tempering. If temperatures is not None, this is ignored. maxTemperature: temperature The maximum temperature to use for tempering. If temperatures is not None, this is ignored. weights: list The weight factor for each temperature. If none, weights are selected automatically. tempChangeInterval: int The interval (in time steps) at which to attempt transitions between temperatures reportInterval: int The interval (in time steps) at which to write information to the report file reportFile: string or file The file to write reporting information to, specified as a file name or file object """ self.simulation = simulation if temperatures is None: if unit.is_quantity(minTemperature): minTemperature = minTemperature.value_in_unit(unit.kelvin) if unit.is_quantity(maxTemperature): maxTemperature = maxTemperature.value_in_unit(unit.kelvin) self.temperatures = [ minTemperature * ((float(maxTemperature) / minTemperature)** (i / float(numTemperatures - 1))) for i in range(numTemperatures) ] * unit.kelvin else: numTemperatures = len(temperatures) self.temperatures = [ (t.value_in_unit(unit.kelvin) if unit.is_quantity(t) else t) * unit.kelvin for t in temperatures ] if any(self.temperatures[i] >= self.temperatures[i + 1] for i in range(numTemperatures - 1)): raise ValueError( 'The temperatures must be in strictly increasing order') self.tempChangeInterval = tempChangeInterval self.reportInterval = reportInterval self.inverseTemperatures = [ 1.0 / (unit.MOLAR_GAS_CONSTANT_R * t) for t in self.temperatures ] # If necessary, open the file we will write reports to. self._openedFile = isinstance(reportFile, str) if self._openedFile: # Detect the desired compression scheme from the filename extension # and open all files unbuffered if reportFile.endswith('.gz'): if not have_gzip: raise RuntimeError( "Cannot write .gz file because Python could not import gzip library" ) self._out = gzip.GzipFile(fileobj=open(reportFile, 'wb', 0)) elif reportFile.endswith('.bz2'): if not have_bz2: raise RuntimeError( "Cannot write .bz2 file because Python could not import bz2 library" ) self._out = bz2.BZ2File(reportFile, 'w', 0) else: self._out = open(reportFile, 'w', 1) else: self._out = reportFile # Initialize the weights. if weights is None: self._weights = [0.0] * numTemperatures self._updateWeights = True self._weightUpdateFactor = 1.0 self._histogram = [0] * numTemperatures self._hasMadeTransition = False else: self._weights = weights self._updateWeights = False # Select the initial temperature. self.currentTemperature = 0 self.simulation.integrator.setTemperature( self.temperatures[self.currentTemperature]) for param in self.simulation.context.getParameters(): if 'MonteCarloTemperature' in param: self.simulation.context.setParameter( param, self.temperatures[self.currentTemperature]) # Add a reporter to the simulation which will handle the updates and reports. class STReporter(object): def __init__(self, st): self.st = st def describeNextReport(self, simulation): st = self.st steps1 = st.tempChangeInterval - simulation.currentStep % st.tempChangeInterval steps2 = st.reportInterval - simulation.currentStep % st.reportInterval steps = min(steps1, steps2) isUpdateAttempt = (steps1 == steps) return (steps, False, isUpdateAttempt, False, isUpdateAttempt) def report(self, simulation, state): st = self.st if simulation.currentStep % st.tempChangeInterval == 0: st._attemptTemperatureChange(state) if simulation.currentStep % st.reportInterval == 0: st._writeReport() simulation.reporters.append(STReporter(self)) # Write out the header line. headers = ['Steps', 'Temperature (K)'] for t in self.temperatures: headers.append('%gK Weight' % t.value_in_unit(unit.kelvin)) print('#"%s"' % ('"\t"').join(headers), file=self._out)
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)