コード例 #1
0
 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)
コード例 #2
0
 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))
コード例 #3
0
 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)
コード例 #4
0
ファイル: amd.py プロジェクト: z-gong/openmm
    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
コード例 #5
0
 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)
コード例 #6
0
ファイル: simulation.py プロジェクト: wouterboomsma/openmm
    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)
コード例 #7
0
    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
コード例 #8
0
 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)
コード例 #9
0
ファイル: pdbxfile.py プロジェクト: z-gong/openmm
    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
コード例 #10
0
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
コード例 #11
0
    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([])))
コード例 #12
0
ファイル: dcdfile.py プロジェクト: z-gong/openmm
    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)
コード例 #13
0
ファイル: amd.py プロジェクト: z-gong/openmm
 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)
コード例 #14
0
 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))
コード例 #15
0
 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)
コード例 #16
0
ファイル: topology.py プロジェクト: wouterboomsma/openmm
    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
コード例 #17
0
 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)
コード例 #18
0
ファイル: unitcell.py プロジェクト: z-gong/openmm
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)
コード例 #19
0
ファイル: unitcell.py プロジェクト: z-gong/openmm
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
コード例 #20
0
 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))
コード例 #21
0
ファイル: element.py プロジェクト: z-gong/openmm
    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
コード例 #22
0
ファイル: topology.py プロジェクト: wouterboomsma/openmm
 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)
コード例 #23
0
    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)
コード例 #24
0
ファイル: unitcell.py プロジェクト: z-gong/openmm
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
コード例 #25
0
    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)
コード例 #26
0
 def _standardize(self, quantity):
     if unit.is_quantity(quantity):
         return quantity.value_in_unit_system(unit.md_unit_system)
     else:
         return quantity
コード例 #27
0
    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
コード例 #28
0
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)
コード例 #29
0
    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)
コード例 #30
0
ファイル: pdbfile.py プロジェクト: wouterboomsma/openmm
    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)