示例#1
0
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)
示例#2
0
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)
示例#3
0
 def isHbond(d, h, a):
     if norm(d-a) > 0.35*nanometer:
         return False
     deltaDH = h-d
     deltaHA = a-h
     deltaDH /= norm(deltaDH)
     deltaHA /= norm(deltaHA)
     return acos(dot(deltaDH, deltaHA)) < 50*degree
示例#4
0
 def isHbond(d, h, a):
     if norm(d - a) > 0.35 * nanometer:
         return False
     deltaDH = h - d
     deltaHA = a - h
     deltaDH /= norm(deltaDH)
     deltaHA /= norm(deltaHA)
     return acos(dot(deltaDH, deltaHA)) < 50 * degree
示例#5
0
    def writeHeader(topology, file=sys.stdout):
        """Write out the header for a PDB file.

        Parameters:
         - topology (Topology) The Topology defining the molecular system being written
         - file (file=stdout) A file to write the file to
        """
        print >> file, "REMARK   1 CREATED WITH OPENMM %s, %s" % (
            Platform.getOpenMMVersion(), str(date.today()))
        vectors = topology.getPeriodicBoxVectors()
        if vectors is not None:
            (a, b, c) = vectors.value_in_unit(angstroms)
            a_length = norm(a)
            b_length = norm(b)
            c_length = norm(c)
            alpha = math.acos(dot(b, c) /
                              (b_length * c_length)) * 180.0 / math.pi
            beta = math.acos(dot(c, a) /
                             (c_length * a_length)) * 180.0 / math.pi
            gamma = math.acos(dot(a, b) /
                              (a_length * b_length)) * 180.0 / math.pi
            print >> file, "CRYST1%9.3f%9.3f%9.3f%7.2f%7.2f%7.2f P 1           1 " % (
                a_length, b_length, c_length, alpha, beta, gamma)
示例#6
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)
示例#7
0
    def _addAtomsToTopology(self, heavyAtomsOnly, omitUnknownMolecules):
        """Create a new Topology in which missing atoms have been added.

        Parameters
        ----------
        heavyAtomsOnly : bool
            If True, only heavy atoms will be added to the topology.
        omitUnknownMolecules : bool
            If True, unknown molecules will be omitted from the topology.

        Returns
        -------
        newTopology : simtk.openmm.app.Topology
            A new Topology object containing atoms from the old.         
        newPositions : list of simtk.unit.Quantity with units compatible with nanometers
            Atom positions for the new Topology object.
        newAtoms : simtk.openmm.app.Topology.Atom
            New atom objects.
        existingAtomMap : dict
            Mapping from old atoms to new atoms.

        """
        
        newTopology = app.Topology()
        newPositions = []*unit.nanometer
        newAtoms = []
        existingAtomMap = {}
        addedAtomMap = {}
        addedOXT = []
        residueCenters = [self._computeResidueCenter(res).value_in_unit(unit.nanometers) for res in self.topology.residues()]*unit.nanometers
        for chain in self.topology.chains():
            if omitUnknownMolecules and not any(residue.name in self.templates for residue in chain.residues()):
                continue
            chainResidues = list(chain.residues())
            newChain = newTopology.addChain(chain.id)
            for indexInChain, residue in enumerate(chain.residues()):
                
                # Insert missing residues here.
                
                if (chain.index, indexInChain) in self.missingResidues:
                    insertHere = self.missingResidues[(chain.index, indexInChain)]
                    endPosition = self._computeResidueCenter(residue)
                    if indexInChain > 0:
                        startPosition = self._computeResidueCenter(chainResidues[indexInChain-1])
                        loopDirection = _findUnoccupiedDirection((startPosition+endPosition)/2, residueCenters)
                    else:
                        outward = _findUnoccupiedDirection(endPosition, residueCenters)*unit.nanometers
                        norm = unit.norm(outward)
                        if norm > 0*unit.nanometer:
                            outward *= len(insertHere)*0.5*unit.nanometer/norm
                        startPosition = endPosition+outward
                        loopDirection = None
                    firstIndex = int(residue.id)-len(insertHere)
                    self._addMissingResiduesToChain(newChain, insertHere, startPosition, endPosition, loopDirection, residue, newAtoms, newPositions, firstIndex)
                
                # Create the new residue and add existing heavy atoms.
                                
                newResidue = newTopology.addResidue(residue.name, newChain, residue.id)
                addResiduesAfter = (residue == chainResidues[-1] and (chain.index, indexInChain+1) in self.missingResidues)
                for atom in residue.atoms():
                    if not heavyAtomsOnly or (atom.element is not None and atom.element != hydrogen):
                        if atom.name == 'OXT' and (chain.index, indexInChain+1) in self.missingResidues:
                            continue # Remove terminal oxygen, since we'll add more residues after this one
                        newAtom = newTopology.addAtom(atom.name, atom.element, newResidue)
                        existingAtomMap[atom] = newAtom
                        newPositions.append(self.positions[atom.index])
                if residue in self.missingAtoms:
                    
                    # Find corresponding atoms in the residue and the template.
                    
                    template = self.templates[residue.name]
                    atomPositions = dict((atom.name, self.positions[atom.index]) for atom in residue.atoms())
                    points1 = []
                    points2 = []
                    for atom in template.topology.atoms():
                        if atom.name in atomPositions:
                            points1.append(atomPositions[atom.name].value_in_unit(unit.nanometer))
                            points2.append(template.positions[atom.index].value_in_unit(unit.nanometer))
                    
                    # Compute the optimal transform to overlay them.
                    
                    (translate2, rotate, translate1) = _overlayPoints(points1, points2)
                    
                    # Add the missing atoms.
                    
                    addedAtomMap[residue] = {}
                    for atom in self.missingAtoms[residue]:
                        newAtom = newTopology.addAtom(atom.name, atom.element, newResidue)
                        newAtoms.append(newAtom)
                        addedAtomMap[residue][atom] = newAtom
                        templatePosition = template.positions[atom.index].value_in_unit(unit.nanometer)
                        newPositions.append((mm.Vec3(*np.dot(rotate, templatePosition+translate2))+translate1)*unit.nanometer)
                if residue in self.missingTerminals:
                    terminalsToAdd = self.missingTerminals[residue]
                else:
                    terminalsToAdd = None

                # If this is the end of the chain, add any missing residues that come after it.
                
                if residue == chainResidues[-1] and (chain.index, indexInChain+1) in self.missingResidues:
                    insertHere = self.missingResidues[(chain.index, indexInChain+1)]
                    if len(insertHere) > 0:
                        startPosition = self._computeResidueCenter(residue)
                        outward = _findUnoccupiedDirection(startPosition, residueCenters)*unit.nanometers
                        norm = unit.norm(outward)
                        if norm > 0*unit.nanometer:
                            outward *= len(insertHere)*0.5*unit.nanometer/norm
                        endPosition = startPosition+outward
                        firstIndex = int(residue.id)+1
                        self._addMissingResiduesToChain(newChain, insertHere, startPosition, endPosition, None, residue, newAtoms, newPositions, firstIndex)
                        newResidue = list(newChain.residues())[-1]
                        if newResidue.name in proteinResidues:
                            terminalsToAdd = ['OXT']
                        else:
                            terminalsToAdd = None
                
                # If a terminal OXT is missing, add it.
                
                if terminalsToAdd is not None:
                    atomPositions = dict((atom.name, newPositions[atom.index].value_in_unit(unit.nanometer)) for atom in newResidue.atoms())
                    if 'OXT' in terminalsToAdd:
                        newAtom = newTopology.addAtom('OXT', oxygen, newResidue)
                        newAtoms.append(newAtom)
                        addedOXT.append(newAtom)
                        d_ca_o = atomPositions['O']-atomPositions['CA']
                        d_ca_c = atomPositions['C']-atomPositions['CA']
                        d_ca_c /= unit.sqrt(unit.dot(d_ca_c, d_ca_c))
                        v = d_ca_o - d_ca_c*unit.dot(d_ca_c, d_ca_o)
                        newPositions.append((atomPositions['O']+2*v)*unit.nanometer)
        newTopology.setUnitCellDimensions(self.topology.getUnitCellDimensions())
        newTopology.createStandardBonds()
        newTopology.createDisulfideBonds(newPositions)
        
        # Return the results.
        
        return (newTopology, newPositions, newAtoms, existingAtomMap)
示例#8
0
    def _addAtomsToTopology(self, heavyAtomsOnly, omitUnknownMolecules):
        """Create a new Topology in which missing atoms have been added."""
        
        newTopology = app.Topology()
        newPositions = []*unit.nanometer
        newAtoms = []
        existingAtomMap = {}
        addedAtomMap = {}
        addedOXT = []
        for chain in self.topology.chains():
            if omitUnknownMolecules and not any(residue.name in self.templates for residue in chain.residues()):
                continue
            chainResidues = list(chain.residues())
            newChain = newTopology.addChain()
            for indexInChain, residue in enumerate(chain.residues()):
                
                # Insert missing residues here.
                
                if (chain.index, indexInChain) in self.missingResidues:
                    insertHere = self.missingResidues[(chain.index, indexInChain)]
                    endPosition = self._computeResidueCenter(residue)
                    if indexInChain > 0:
                        startPosition = self._computeResidueCenter(chainResidues[indexInChain-1])
                    else:
                        outward = endPosition-self.centroid
                        norm = unit.norm(outward)
                        if norm > 0*unit.nanometer:
                            outward *= len(insertHere)*0.5*unit.nanometer/norm
                        startPosition = endPosition+outward
                    self._addMissingResiduesToChain(newChain, insertHere, startPosition, endPosition, residue, newAtoms, newPositions)
                
                # Create the new residue and add existing heavy atoms.
                                
                newResidue = newTopology.addResidue(residue.name, newChain)
                addResiduesAfter = (residue == chainResidues[-1] and (chain.index, indexInChain+1) in self.missingResidues)
                for atom in residue.atoms():
                    if not heavyAtomsOnly or (atom.element is not None and atom.element != hydrogen):
                        if atom.name == 'OXT' and (chain.index, indexInChain+1) in self.missingResidues:
                            continue # Remove terminal oxygen, since we'll add more residues after this one
                        newAtom = newTopology.addAtom(atom.name, atom.element, newResidue)
                        existingAtomMap[atom] = newAtom
                        newPositions.append(self.positions[atom.index])
                if residue in self.missingAtoms:
                    
                    # Find corresponding atoms in the residue and the template.
                    
                    template = self.templates[residue.name]
                    atomPositions = dict((atom.name, self.positions[atom.index]) for atom in residue.atoms())
                    points1 = []
                    points2 = []
                    for atom in template.topology.atoms():
                        if atom.name in atomPositions:
                            points1.append(atomPositions[atom.name].value_in_unit(unit.nanometer))
                            points2.append(template.positions[atom.index].value_in_unit(unit.nanometer))
                    
                    # Compute the optimal transform to overlay them.
                    
                    (translate2, rotate, translate1) = _overlayPoints(points1, points2)
                    
                    # Add the missing atoms.
                    
                    addedAtomMap[residue] = {}
                    for atom in self.missingAtoms[residue]:
                        newAtom = newTopology.addAtom(atom.name, atom.element, newResidue)
                        newAtoms.append(newAtom)
                        addedAtomMap[residue][atom] = newAtom
                        templatePosition = template.positions[atom.index].value_in_unit(unit.nanometer)
                        newPositions.append((mm.Vec3(*np.dot(rotate, templatePosition+translate2))+translate1)*unit.nanometer)
                if residue in self.missingTerminals:
                    terminalsToAdd = self.missingTerminals[residue]
                else:
                    terminalsToAdd = None

                # If this is the end of the chain, add any missing residues that come after it.
                
                if residue == chainResidues[-1] and (chain.index, indexInChain+1) in self.missingResidues:
                    insertHere = self.missingResidues[(chain.index, indexInChain+1)]
                    if len(insertHere) > 0:
                        startPosition = self._computeResidueCenter(residue)
                        outward = startPosition-self.centroid
                        norm = unit.norm(outward)
                        if norm > 0*unit.nanometer:
                            outward *= len(insertHere)*0.5*unit.nanometer/norm
                        endPosition = startPosition+outward
                        self._addMissingResiduesToChain(newChain, insertHere, startPosition, endPosition, residue, newAtoms, newPositions)
                        newResidue = list(newChain.residues())[-1]
                        if newResidue.name in proteinResidues:
                            terminalsToAdd = ['OXT']
                        else:
                            terminalsToAdd = None
                
                # If a terminal OXT is missing, add it.
                
                if terminalsToAdd is not None:
                    atomPositions = dict((atom.name, newPositions[atom.index].value_in_unit(unit.nanometer)) for atom in newResidue.atoms())
                    if 'OXT' in terminalsToAdd:
                        newAtom = newTopology.addAtom('OXT', oxygen, newResidue)
                        newAtoms.append(newAtom)
                        addedOXT.append(newAtom)
                        d_ca_o = atomPositions['O']-atomPositions['CA']
                        d_ca_c = atomPositions['C']-atomPositions['CA']
                        d_ca_c /= unit.sqrt(unit.dot(d_ca_c, d_ca_c))
                        v = d_ca_o - d_ca_c*unit.dot(d_ca_c, d_ca_o)
                        newPositions.append((atomPositions['O']+2*v)*unit.nanometer)
        newTopology.setUnitCellDimensions(self.topology.getUnitCellDimensions())
        newTopology.createStandardBonds()
        newTopology.createDisulfideBonds(newPositions)
        
        # Return the results.
        
        return (newTopology, newPositions, newAtoms, existingAtomMap)
示例#9
0
def runTest(path, systemName, platformName, eps, tolerance, precision, queue):
    """This function runs the test for a single system."""
    print "\nsystemName =", systemName

    # load the system from the xml file
    system = loadXMLFile(path, systemName)
    
    # set Ewald error tolerance to a sufficiently small value so the requested accuracy will be achievable
    for f in system.getForces():
        try:
            f.setEwaldErrorTolerance(tolerance/2)
        except:
            pass

    # read in the particle positions from the .pos file
    positions = loadPosFile(path, systemName)

    numParticles = len(positions)
    print "numParticles =", numParticles, "(from the .pos file)"

    integrator = mm.LangevinIntegrator(300*unit.kelvin, 1/unit.picosecond, 0.002*unit.picoseconds)

    print "mm.Platform.getNumPlatforms =", mm.Platform.getNumPlatforms() 
    platform = mm.Platform.getPlatformByName(platformName)
    print "platform.getName() =", platform.getName()

    print "Building \'context\' on \'platform\'."
    context = mm.Context(system, integrator, platform, properties)
    context.setPositions(positions)
    state0 = context.getState(getForces=True)
    forces0 = state0.getForces()


    # make sure the size of the force vector is equal to the number of particles
    assert (len(forces0)==numParticles)

    # calculate the norm of the forces
    force0NormSum = 0.0*unit.kilojoules**2/unit.mole**2/unit.nanometer**2
    for f in forces0:
        force0NormSum += unit.dot(f,f)
    force0Norm = unit.sqrt(force0NormSum)
    print "force0Norm =", force0Norm

    epsilon = eps*unit.nanometer
    step = epsilon/force0Norm
    print "step =", step

    # perturb the coordinates along the direction of forces0 and evaluate the energy
    context.setPositions([p-2*f*step for p,f in zip(positions, forces0)])
    pe1 = context.getState(getEnergy=True).getPotentialEnergy()
    context.setPositions([p-f*step for p,f in zip(positions, forces0)])
    pe2 = context.getState(getEnergy=True).getPotentialEnergy()
    context.setPositions([p+f*step for p,f in zip(positions, forces0)])
    pe3 = context.getState(getEnergy=True).getPotentialEnergy()
    context.setPositions([p+2*f*step for p,f in zip(positions, forces0)])
    pe4 = context.getState(getEnergy=True).getPotentialEnergy()

    # use a finite difference approximation to calculate the expected force0Norm
    expectedForceNorm = (-pe1+8*pe2-8*pe3+pe4)/(12*epsilon)
    relativeDifference = abs(expectedForceNorm-force0Norm)/force0Norm

    print "pe1 =", pe1
    print "pe2 =", pe2
    print "pe3 =", pe3
    print "pe4 =", pe4
    print "expectedForceNorm =", expectedForceNorm
    print "relativeDifference =", relativeDifference

    # check that the energy is within the desired range
    if relativeDifference > tolerance:
        print "*** ERROR EXCEEDS TOLERANCE ***"
        queue.put(False)
    else:
        queue.put(True)
    print "Test of", systemName, "complete."
    del(context)
示例#10
0
def createRigidBodies(system, positions, bodies):
    """Modify a System to turn specified sets of particles into rigid bodies.
    
    For every rigid body, four particles are selected as "real" particles whose positions are integrated.
    Constraints are added between them to make them move as a rigid body.  All other particles in the body
    are then turned into virtual sites whose positions are computed based on the "real" particles.
    
    Because virtual sites are massless, the mass properties of the rigid bodies will be slightly different
    from the corresponding sets of particles in the original system.  The masses of the non-virtual particles
    are chosen to guarantee that the total mass and center of mass of each rigid body exactly match those of
    the original particles.  The moment of inertia will be similar to that of the original particles, but
    not identical.
    
    Care is needed when using constraints, since virtual particles cannot participate in constraints.  If the
    input system includes any constraints, this function will automatically remove ones that connect two
    particles in the same rigid body.  But if there is a constraint beween a particle in a rigid body and
    another particle not in that body, it will likely lead to an exception when you try to create a context.
    
    Parameters:
     - system (System) the System to modify
     - positions (list) the positions of all particles in the system
     - bodies (list) each element of this list defines one rigid body.  Each element should itself be a list
       of the indices of all particles that make up that rigid body.
    """
    # Remove any constraints involving particles in rigid bodies.

    for i in range(system.getNumConstraints() - 1, -1, -1):
        p1, p2, distance = system.getConstraintParameters(i)
        if (any(p1 in body and p2 in body for body in bodies)):
            system.removeConstraint(i)

    # Loop over rigid bodies and process them.

    for particles in bodies:
        if len(particles) < 5:
            # All the particles will be "real" particles.

            realParticles = particles
            realParticleMasses = [system.getParticleMass(i) for i in particles]
        else:
            # Select four particles to use as the "real" particles.  All others will be virtual sites.

            pos = [positions[i] for i in particles]
            mass = [system.getParticleMass(i) for i in particles]
            cm = unit.sum([p * m for p, m in zip(pos, mass)]) / unit.sum(mass)
            r = [p - cm for p in pos]
            avgR = unit.sqrt(
                unit.sum([unit.dot(x, x) for x in r]) / len(particles))
            rank = sorted(range(len(particles)),
                          key=lambda i: abs(unit.norm(r[i]) - avgR))
            for p in combinations(rank, 4):
                # Select masses for the "real" particles.  If any is negative, reject this set of particles
                # and keep going.

                matrix = np.zeros((4, 4))
                for i in range(4):
                    particleR = r[p[i]].value_in_unit(unit.nanometers)
                    matrix[0][i] = particleR[0]
                    matrix[1][i] = particleR[1]
                    matrix[2][i] = particleR[2]
                    matrix[3][i] = 1.0
                rhs = np.array(
                    [0.0, 0.0, 0.0,
                     unit.sum(mass).value_in_unit(unit.amu)])
                weights = lin.solve(matrix, rhs)
                if all(w > 0.0 for w in weights):
                    # We have a good set of particles.

                    realParticles = [particles[i] for i in p]
                    realParticleMasses = [float(w) for w in weights] * unit.amu
                    break

        # Set particle masses.

        for i, m in zip(realParticles, realParticleMasses):
            system.setParticleMass(i, m)

        # Add constraints between the real particles.

        for p1, p2 in combinations(realParticles, 2):
            distance = unit.norm(positions[p1] - positions[p2])
            key = (min(p1, p2), max(p1, p2))
            system.addConstraint(p1, p2, distance)

        # Select which three particles to use for defining virtual sites.

        bestNorm = 0
        for p1, p2, p3 in combinations(realParticles, 3):
            d12 = (positions[p2] - positions[p1]).value_in_unit(unit.nanometer)
            d13 = (positions[p3] - positions[p1]).value_in_unit(unit.nanometer)
            crossNorm = unit.norm((d12[1] * d13[2] - d12[2] * d13[1],
                                   d12[2] * d13[0] - d12[0] * d13[2],
                                   d12[0] * d13[1] - d12[1] * d13[0]))
            if crossNorm > bestNorm:
                bestNorm = crossNorm
                vsiteParticles = (p1, p2, p3)

        # Create virtual sites.

        d12 = (positions[vsiteParticles[1]] -
               positions[vsiteParticles[0]]).value_in_unit(unit.nanometer)
        d13 = (positions[vsiteParticles[2]] -
               positions[vsiteParticles[0]]).value_in_unit(unit.nanometer)
        cross = mm.Vec3(d12[1] * d13[2] - d12[2] * d13[1],
                        d12[2] * d13[0] - d12[0] * d13[2],
                        d12[0] * d13[1] - d12[1] * d13[0])
        matrix = np.zeros((3, 3))
        for i in range(3):
            matrix[i][0] = d12[i]
            matrix[i][1] = d13[i]
            matrix[i][2] = cross[i]
        for i in particles:
            if i not in realParticles:
                system.setParticleMass(i, 0)
                rhs = np.array((positions[i] -
                                positions[vsiteParticles[0]]).value_in_unit(
                                    unit.nanometer))
                weights = lin.solve(matrix, rhs)
                system.setVirtualSite(
                    i,
                    mm.OutOfPlaneSite(vsiteParticles[0], vsiteParticles[1],
                                      vsiteParticles[2], weights[0],
                                      weights[1], weights[2]))