Example #1
0
def expected_zero_modes(x0, masses):
    """
    How many eigen values of the Hessian should be zero
    because of the structure of the molecule (atom, linear, polyatomic)?
    """
    # assume that coordinate vector with 3*N components belongs to a molecule
    # with N atoms
    assert len(x0) % 3 == 0
    Nat = len(x0) / 3
    # shift the origin to the center of mass
    x0_shift = MolCo.shift_to_com(x0, masses)
    # diagonalize the tensor of inertia to obtain the principal moments and
    # the normalized eigenvectors of I
    Inert = MolCo.inertial_tensor(masses, x0_shift)
    principle_moments, X = la.eigh(Inert)
    # check that the number of rotational and translational modes
    # is what is expected:
    #   single atom  => 3 translational modes only = 3
    #   dimer or linear molecule => 3 translational and 2 rotational modes = 5
    #   otherwise => 3 translational and 3 rotational modes = 6

    # If the molecule is linear two of the principle moments of inertia
    # will be zero
    Ntrans = 3  # number of translational
    Nrot = 3  # and rotational modes which should be very close to zero
    is_linear = False
    pmom_abs = np.sort(abs(principle_moments))
    # In a linear triatomic molecule we have Icc = Ibb > Iaa = 0
    if abs(pmom_abs[2] - pmom_abs[1]) < 1.0e-6 and abs(pmom_abs[0]) < 1.0e-6:
        is_linear = True
        print("Molecule is linear")
    if Nat == 1:
        Nrot = 0
    elif is_linear == True or Nat == 2:
        Nrot = 2
    else:
        Nrot = 3

    return (Ntrans + Nrot)
Example #2
0
    def transform_gradient(self, x, g_cart, max_iter=200):
        """
        transform cartesian gradient g_cart = dE/dx to redundant internal coordinates according to

              B^T g_intern = g_cart

        The underdetermined system of linear equations is solved
        in a least square sense.
        """
        if self.verbose > 0:
            print("transform gradient to internal coordinates")
            print("  dE/dx -> dE/dq")
        # Since the energy of a molecule depends only on the internal
        # coordinates q it should be possible to transform the cartesian
        # gradient exactly into internal coordinates according to
        #   dE/dx(i) = sum_j dE/dq(j) dq(j)/dx(i)
        #            = sum_j (B^T)_{i,j} dE/dq(j)
        #
        # cartesian force
        f_cart = -g_cart
        # cartesian accelerations
        a_cart = f_cart / self.masses
        # cartesian velocity
        dt = 1.0
        vel = a_cart * dt
        # shift position to center of mass
        x = MolCo.shift_to_com(x, self.masses)
        # eliminate rotation and translation from a_cart
        I = MolCo.inertial_tensor(self.masses, x)
        L = MolCo.angular_momentum(self.masses, x, vel)
        P = MolCo.linear_momentum(self.masses, vel)
        omega = MolCo.angular_velocity(L, I)
        vel = MolCo.eliminate_translation(self.masses, vel, P)
        vel = MolCo.eliminate_rotation(vel, omega, x)
        # Check that the total linear momentum and angular momentum
        # indeed vanish.
        assert la.norm(MolCo.linear_momentum(self.masses, vel)) < 1.0e-14
        assert la.norm(MolCo.angular_momentum(self.masses, x, vel)) < 1.0e-14
        # go back from velocities to gradient
        g_cart = -vel / dt * self.masses

        # solve  B^T . g_intern = g_cart  by linear least squares.
        ret = sla.lstsq(self.B0.transpose(), g_cart, cond=self.cond_threshold)
        g_intern = ret[0]

        if self.verbose > 1:
            print(self._table_gradients(g_cart, g_intern))

        # check solution
        err = la.norm(np.dot(self.B0.transpose(), g_intern) - g_cart)
        assert err < 1.0e-10, "|B^T.g_intern - g_cart|= %e" % err

        # Abort if gradients are not reasonable
        gnorm = la.norm(g_intern)
        if gnorm > 1.0e5:
            raise RuntimeError(
                "ERROR: Internal gradient is too large |grad|= %e" % gnorm)

        # Components of gradient belonging to frozen internal
        # coordinates are zeroed to avoid changing them.
        g_intern[self.frozen_internals] = 0.0

        # Simply setting some components of the gradient to zero
        # will most likely lead to inconsistencies if the coordinates
        # are coupled (Maybe it's impossible to change internal coordinate X
        # without changing coordinate Y at the same time). These
        # inconsistencies are removed by applying the projection
        # operator repeatedly until the components of the frozen
        # internal coordinates have converged to 0.
        if self.verbose > 0:
            print(" apply projector P=G.G- to internal gradient")

        # The projector is updated everytime cartesian2internal(...)
        # is called.
        proj = self.P0

        for i in range(0, max_iter):
            g_intern = np.dot(proj, g_intern)
            # gradient along frozen coordinates should be zero
            gnorm_frozen = la.norm(g_intern[self.frozen_internals])

            if self.verbose > 0:
                print("  Iteration= %4.1d   |grad(frozen)|= %s" %
                      (i, gnorm_frozen))

            if gnorm_frozen < 1.0e-10:
                break
            else:
                g_intern[self.frozen_internals] = 0.0
        else:
            if gnorm_frozen < 1.0e-5:
                print(
                    "WARNING: Projection of gradient vector in internal coordinates did not converge!"
                )
                print(
                    "         But |grad(frozen)|= %e is not too large, so let's continue anyway."
                    % gnorm_frozen)
            else:
                raise RuntimeError(
                    "ERROR: Projection of gradient vector in internal coordinates did not converge! |grad(frozen)|= %e"
                    % gnorm_frozen)

        if self.verbose > 1:
            print(
                "gradients after applying projector (only internal gradient changes)"
            )
            print(self._table_gradients(g_cart, g_intern))

        return g_intern
Example #3
0
    def __init__(self,
                 atomlist,
                 E0,
                 vib_freq,
                 symmetry_group,
                 pressure=AtomicData.atm_pressure,
                 temperature=AtomicData.satp_temperature):
        """
        temperature in Kelvin
        """
        self.E0 = E0
        self.P = pressure
        self.T = temperature
        self.vib_freq = vib_freq
        self.symmetry_group = symmetry_group

        self.atomlist = atomlist
        Nat = len(atomlist)
        self.masses = AtomicData.atomlist2masses(self.atomlist)
        # compute rotational constants from tensor of inertia
        x0 = XYZ.atomlist2vector(atomlist)
        # shift the origin to the center of mass
        x0_shift = MolCo.shift_to_com(x0, self.masses)
        # diagonalize the tensor of inertia to obtain the principal moments and
        # the normalized eigenvectors of I
        Inert = MolCo.inertial_tensor(self.masses, x0_shift)
        principle_moments, X = la.eigh(Inert)
        Iaa, Ibb, Icc = np.sort(abs(principle_moments))
        print("principle moments of inertia (in a.u.): %s %s %s" %
              (Iaa, Ibb, Icc))
        # In a linear triatomic molecule we have Icc = Ibb > Iaa = 0
        self.is_linear = False
        if abs(Icc - Ibb) / abs(Icc) < 1.0e-6 and abs(Iaa) / abs(Icc) < 1.0e-6:
            self.is_linear = True
            print("Molecule is linear")
        # Determine the type of rotor
        if self.is_linear == True:
            self.rotor_type = "linear rotor"
        else:
            if abs(Icc - Ibb) / abs(Icc) < 1.0e-4 and abs(Icc - Iaa) / abs(
                    Icc) < 1.0e-4:
                # three equal moments of inertia
                self.rotor_type = "spherical rotor"
            elif abs(Icc - Ibb) / abs(Icc) < 1.0e-4 or abs(Ibb - Iaa) / abs(
                    Icc) < 1.0e-4:
                # two equal moments of inertia
                self.rotor_type = "symmetric rotor"
            else:
                self.rotor_type = "asymmetric rotor"

        # rotational constants
        self.rotational_constants = 1.0 / (
            2.0 * principle_moments + 1.0e-20
        )  # avoid division by zero error for a linear molecule, the invalid values are not actually used
        # symmetry number
        if symmetry_group == None:
            print(
                "Symmetry group unknown, setting rotational symmetry number to 1"
            )
            self.sigma_rot = 1
        else:
            self.sigma_rot = symmetry_group.rotational_symmetry_number()
        # beta = 1/(kB*T)
        kB = AtomicData.kBoltzmann
        self.beta = 1.0 / (kB * self.T)