def HardSphereIonMobilities(molecule,
                            nreflections=30,
                            ntrajectories=600000,
                            randomNumberGenerator=None,
                            temperature=298.0,
                            log=logFile):
    """Calculate ion mobilities with a hard-sphere model."""

    # . Get the atom data.
    hsradii = _GetHardSphereRadii(molecule.atoms)
    masses = molecule.atoms.GetItemAttributes("mass")
    totalmass = masses.Sum()

    # . Get initial coordinates, move to center of mass and convert to metres.
    xyz0 = Clone(molecule.coordinates3)
    xyz0.TranslateToCenter(weights=masses)
    xyz0.Scale(1.0e-10)

    # . Get the mass constant.
    massHe = PeriodicTable.Element(2).mass
    massconstant = _MASSCONSTANT * math.sqrt((1.0 / massHe) +
                                             (1.0 / totalmass))

    # . Get the random number generator.
    if randomNumberGenerator is None:
        randomNumberGenerator = RandomNumberGenerator.WithRandomSeed()
    rotation = Matrix33.Null()

    # . Initialize some calculation variables.
    cof = Real1DArray.WithExtent(nreflections)
    cof.Set(0.0)
    crof = Real1DArray.WithExtent(nreflections)
    crof.Set(0.0)
    crb = 0.0
    mreflections = 0

    # . Loop over the trajectories.
    for it in range(ntrajectories):

        # . Randomly rotate the coordinate set.
        rotation.RandomRotation(randomNumberGenerator)
        xyz = Clone(xyz0)
        xyz.Rotate(rotation)

        # . Loop over the collisions.
        QCOLLISION = False
        for ir in range(nreflections):

            # . Initial collision - at a random point in the yz plane along the x-axis.
            if ir == 0:
                (origin, extents) = xyz.EnclosingOrthorhombicBox(radii=hsradii)
                yzarea = extents[1] * extents[2]
                yc = origin[1] + extents[1] * randomNumberGenerator.NextReal()
                zc = origin[2] + extents[2] * randomNumberGenerator.NextReal()
                xaxis = Vector3.WithValues(1.0, 0.0, 0.0)
            # . Subsequent collisions - always along the x-axis.
            else:
                yc = 0.0
                zc = 0.0

            # . Initialization.
            ic = -1  # . The index of the colliding particle.
            xc = origin[0] + extents[0]  # . The largest x-coordinate.

            # . Loop over particles.
            for (i, h) in enumerate(hsradii):
                # . After the first collision only x-values > 0 are allowed.
                if (ir == 0) or (xyz[i, 0] > 1.0e-16):
                    # . yd and zd are the coordinates of the impact points for the ith atom
                    # . with respect to its own coordinates (if such a point exists).
                    # . dev is the impact parameter.
                    h2 = h * h
                    y = yc - xyz[i, 1]
                    z = zc - xyz[i, 2]
                    yz2 = y * y + z * z
                    # . If there is a collision with the ith atom, check to see if it occurs before previous collisions.
                    if yz2 < h2:
                        x = xyz[i, 0] - math.sqrt(h2 - yz2)
                        if x < xc:
                            xc = x
                            ic = i

            # . Check mreflections.
            if ir >= mreflections: mreflections = ir + 1

            # . There was a collision.
            if ic >= 0:
                QCOLLISION = True
                # . Translate the coordinates so that the collision point is at the origin.
                xyz.Translate(Vector3.WithValues(-xc, -yc, -zc))
                # . Rotate the coordinates so that the outgoing vector is along the x-axis.
                h = xyz.GetRow(
                    ic
                )  # . Normalized vector from the collision point to the ic-th atom.
                h.Normalize(tolerance=1.0e-20)
                axis = Vector3.WithValues(
                    0.0, h[2], -h[1])  # . Normalized axis of rotation.
                axis.Normalize(tolerance=1.0e-20)
                alpha = math.pi - 2.0 * math.acos(h[0])  # . Angle of rotation.
                rotation.RotationAboutAxis(alpha, axis)
                xyz.Rotate(rotation)
                rotation.ApplyTo(xaxis)
                # . Calculate the cosine of the angle between the incoming vector and the normal to a plane,
                # . the reflection from which would be equivalent to the accumulated reflection.
                # . This is equal to h[0] when ir = 0.
                cof[ir] = math.cos(0.5 * (math.pi - math.acos(xaxis[0])))
                # . Check outgoing.
                # . Get the outgoing vector (the ingoing vector is always [1,0,0]).
                out = Vector3.WithValues(1.0 - 2.0 * h[0] * h[0],
                                         -2.0 * h[0] * h[1],
                                         -2.0 * h[0] * h[2])
                rotation.ApplyTo(out)
                out[0] -= 1.0
                if out.Norm2() > 1.0e-6:
                    print(
                        "Invalid Rotation: {:10.3f} {:10.3f} {:10.3f}.".format(
                            out[0], out[1], out[2]))
            # . There was no collision.
            else:
                # . Top up the remaining elements of cof with the last valid value of cof.
                if ir == 0: t = 0.0
                else: t = cof[ir - 1]
                for i in range(ir, nreflections):
                    cof[i] = t
                # . Exit.
                break

        # . End of collisions.
        # . Projection approximation.
        if QCOLLISION: crb += yzarea
        # . Hard-sphere approximation.
        for ir in range(nreflections):
            crof[ir] += yzarea * cof[ir] * cof[ir]

    # . End of trajectories.
    crof.Scale(2.0 / float(ntrajectories))
    pacs = crb / float(ntrajectories)
    pamob = massconstant / (pacs * math.sqrt(temperature))
    hscs = crof[mreflections - 1]
    hsmob = massconstant / (hscs * math.sqrt(temperature))

    # . Output results.
    if LogFileActive(log):
        summary = log.GetSummary()
        summary.Start("Hard-Sphere Ion Mobilities")
        summary.Entry("MC Trajectories", "{:d}".format(ntrajectories))
        summary.Entry("Reflection Limit", "{:d}".format(nreflections))
        summary.Entry("PA Mobility", "{:.4g}".format(pamob))
        summary.Entry("PA Cross-Section", "{:.4g}".format(pacs * 1.0e+20))
        summary.Entry("HS Mobility", "{:.4g}".format(hsmob))
        summary.Entry("HS Cross-Section", "{:.4g}".format(hscs * 1.0e+20))
        summary.Entry("Max. Reflections", "{:d}".format(mreflections))
        summary.Stop()

    # . Finish up.
    results = {
        "MC Trajectories": ntrajectories,
        "Reflection Limit": nreflections,
        "PA Mobility": pamob,
        "PA Cross-Section": pacs * 1.0e+20,
        "HS Mobility": hsmob,
        "HS Cross-Section": hscs * 1.0e+20,
        "Maximum Reflections": mreflections
    }
    return results
Esempio n. 2
0
def BuildHydrogenCoordinates3FromConnectivity(system,
                                              log=logFile,
                                              randomNumberGenerator=None):
    """Build hydrogen coordinates.

    The coordinates are built using connectivity information only (bonds
    and angles) which means that no account is taken of non-connectivity
    information (such as hydrogen-bonding). These interactions will have
    to be optimized separately using energy minimization or dynamics.

    Note that bonds to other hydrogens are ignored and unbound hydrogens
    or hydrogens linked to more than one heavy atom will not be built."""

    # . Check for a system object.
    if isinstance(system, System) and (system.connectivity is not None) and (
            system.connectivity.HasFullConnectivity()):

        # . Check whether there are undefined coordinates.
        coordinates3 = system.coordinates3
        numberUndefined0 = coordinates3.numberUndefined
        if numberUndefined0 > 0:

            # . Initialization.
            bonds = system.connectivity.bonds
            direction = Vector3.Null()
            if randomNumberGenerator is None:
                randomNumberGenerator = RandomNumberGenerator.WithRandomSeed()

            # . Loop over heavy atoms with defined coordinates.
            for (c, atom) in enumerate(system.atoms):
                if (atom.atomicNumber !=
                        1) and (c not in coordinates3.undefined):

                    # . Initialization.
                    built = []
                    builth = []
                    tobuild = []
                    unbuildable = []

                    # . Loop over the connected atoms.
                    others = bonds.GetConnectedAtoms(c)
                    for i in others:
                        other = system.atoms[i]
                        QBUILT = (i not in coordinates3.undefined)
                        # . Hydrogens.
                        if other.atomicNumber == 1:
                            if QBUILT: builth.append(i)
                            elif len(bonds.GetConnectedAtoms(i)) == 1:
                                tobuild.append(i)
                            else:
                                unbuildable.append(i)
                        # . Other atoms.
                        else:
                            if QBUILT: built.append(i)
                            else: unbuildable.append(i)

                    # . Skip this atom if the number of connections is greater than four, there are no hydrogens to build or there are unbuildable atoms.
                    if (len(others) > 4) or (len(tobuild)
                                             == 0) or (len(unbuildable) > 0):
                        continue

                    # . Order the lists and put built hydrogens after built heavy atoms as it is reasoned that heavy atom coordinates will be more reliable.
                    built.sort()
                    builth.sort()
                    tobuild.sort()
                    built += builth

                    # . Get coordination data for the center.
                    nconnections = len(built) + len(tobuild)
                    bondlength = PeriodicTable.Element(
                        atom.atomicNumber).GetSingleBondDistance(1)
                    if bondlength is None: bondlength = _DEFAULTBONDDISTANCE
                    angle = PeriodicTable.Element(
                        atom.atomicNumber).GetCoordinationAngle(nconnections)
                    if angle is None:
                        angle = _COORDINATIONANGLES.get(nconnections, 0.0)
                    planeangle = _COORDINATIONPLANEANGLES.get(
                        nconnections, 0.0)

                    # . Build the hydrogens.
                    while len(tobuild) > 0:

                        # . Get the hydrogen index.
                        h = tobuild.pop(0)

                        # . Build according to the number of built connected atoms.
                        nbuilt = len(built)

                        # . Get a random normalized vector.
                        if (nbuilt == 0) or (nbuilt == 1):
                            for i in range(3):
                                direction[i] = 2.0 * (
                                    randomNumberGenerator.NextReal() - 0.5)
                            direction.Normalize()

                        # . Put the hydrogen in a random direction from the center.
                        # . Works for all cases.
                        if nbuilt == 0:
                            coordinates3.BuildPointFromDistance(
                                h, c, bondlength, direction)

                            # . Put the hydrogen at the correct angle from the center and built atom but in a random plane.
                            # . Works for all cases given correct choice of angle.
                        elif nbuilt == 1:
                            coordinates3.BuildPointFromDistanceAngle(
                                h, c, built[0], bondlength, angle, direction)

                        # . Put the hydrogen away from the other built points at an appropriate angle from their plane.
                        # . The sign of the plane angle is arbitrary.
                        # . Works for cases 3, 4, 5 (square pyramidal), 6 with correct choice of planeangle.
                        elif nbuilt == 2:
                            coordinates3.BuildPointFromDistancePlaneAngle(
                                h, c, built[0], built[1], bondlength,
                                planeangle)

                        # . Put the hydrogen using a tetrahedral tripod.
                        # . Only works for tetrahedral coordination.
                        elif nbuilt == 3:
                            coordinates3.BuildPointFromDistanceTetrahedralTripod(
                                h, c, built[0], built[1], built[2], bondlength)

                        # . Cannot handle valencies greater than 4 for the moment.
                        else:
                            break

                        # . The hydrogen has been built.
                        built.append(h)
                        coordinates3.FlagCoordinateAsDefined(h)

            # . Output a summary.
            if LogFileActive(log):
                numberToBuild = coordinates3.numberUndefined
                numberBuilt = (numberUndefined0 - numberToBuild)
                if numberBuilt <= 0:
                    log.Paragraph("Coordinates for no hydrogens were built.")
                elif numberBuilt == 1:
                    log.Paragraph("Coordinates for one hydrogen were built.")
                else:
                    log.Paragraph(
                        "Coordinates for {:d} hydrogens were built.".format(
                            numberBuilt))
def GenerateVanDerWaalsSurface(system,
                               gridangularmomentum=21,
                               log=logFile,
                               qcAtomsOnly=False,
                               scalingfactors=[1.0]):
    """Generate a superposition of van der Waals surfaces represented by grid points."""

    # . QC atoms only (including link atoms).
    if qcAtomsOnly:
        qcAtoms = system.energyModel.qcAtoms
        atomicNumbers = qcAtoms.GetAtomicNumbers()
        coordinates3 = qcAtoms.GetCoordinates3(system.coordinates3)
    # . All atoms.
    else:
        atomicNumbers = system.atoms.GetItemAttributes("atomicNumber")
        coordinates3 = system.coordinates3

    # . Find the number of atoms.
    natoms = len(atomicNumbers)

    # . Set radii.
    radii = Real1DArray.WithExtent(natoms)
    for (i, n) in enumerate(atomicNumbers):
        radii[i] = PeriodicTable.Element(n).vdwRadius

    # . Get the grid points for a single center.
    (basicgrid, weights) = LebedevLaikovGrid_GetGridPoints(gridangularmomentum)

    # . Allocate space for the possible number of grid points.
    npossible = natoms * basicgrid.rows * len(scalingfactors)
    gridPoints = Coordinates3.WithExtent(npossible)
    gridPoints.Set(0.0)

    # . Initialization.
    nfound = 0
    atomgrid = Coordinates3.WithExtent(basicgrid.rows)
    translation = Vector3.Null()
    atomgrid.Set(0.0)

    # . Loop over scaling factors.
    for factor in scalingfactors:

        # . Loop over points.
        for i in range(natoms):

            # . Get the radius.
            iradius = factor * radii[i]

            # . Get the translation.
            translation[0] = coordinates3[i, 0]
            translation[1] = coordinates3[i, 1]
            translation[2] = coordinates3[i, 2]

            # . Get the scaled grid centered at the point.
            basicgrid.CopyTo(atomgrid)
            atomgrid.Scale(iradius)
            atomgrid.Translate(translation)

            # . Remove points that are within the current scaled radii of other points.
            for p in range(atomgrid.rows):
                QOK = True
                x = atomgrid[p, 0]
                y = atomgrid[p, 1]
                z = atomgrid[p, 2]
                for j in range(natoms):
                    if j != i:
                        dx = coordinates3[j, 0] - x
                        dy = coordinates3[j, 1] - y
                        dz = coordinates3[j, 2] - z
                        jradius2 = (factor * radii[j])**2
                        if (dx**2 + dy**2 + dz**2) <= jradius2:
                            QOK = False
                            break
                if QOK:
                    gridPoints[nfound, 0] = x
                    gridPoints[nfound, 1] = y
                    gridPoints[nfound, 2] = z
                    nfound += 1

    # . Reduce the array size if necessary.
    if nfound < npossible:
        newpoints = Coordinates3.WithExtent(nfound)
        for p in range(nfound):
            newpoints[p, 0] = gridPoints[p, 0]
            newpoints[p, 1] = gridPoints[p, 1]
            newpoints[p, 2] = gridPoints[p, 2]
        gridPoints = newpoints

    # . Create a system.
#    from pBabel  import XYZFile_FromSystem
#    from pMolecule import System
#    ngrid = gridPoints.rows
#    junk = System ( ngrid * [ 1 ] )
#    junk.coordinates3 = gridPoints
#    XYZFile_FromSystem ( "junk.xyz", junk )

# . Do some printing.
    if LogFileActive(log):
        summary = log.GetSummary()
        summary.Start("van der Waals Surface Generation Summary")
        summary.Entry("Atoms", "{:d}".format(natoms))
        summary.Entry("Surfaces", "{:d}".format(len(scalingfactors)))
        summary.Entry("Found Points", "{:d}".format(nfound))
        summary.Entry("Possible Points", "{:d}".format(npossible))
        summary.Stop()

    # . Finish up.
    return gridPoints