Esempio n. 1
0
def test_unrestricted_RPA_C1():
    ch2 = psi4.geometry("""
    0 3
    c
    h 1 1.0
    h 1 1.0 2 125.0
    symmetry c1
    """)
    psi4.set_options({"scf_type": "pk", 'reference': 'UHF', 'save_jk': True})
    e, wfn = psi4.energy("hf/cc-pvdz", molecule=ch2, return_wfn=True)
    A_ref, B_ref = build_UHF_AB_C1(wfn)
    nI, nA, _, _ = A_ref['IAJB'].shape
    nIA = nI * nA
    ni, na, _, _ = A_ref['iajb'].shape
    nia = ni * na

    P_ref = {k: A_ref[k] + B_ref[k] for k in A_ref.keys()}
    M_ref = {k: A_ref[k] - B_ref[k] for k in A_ref.keys()}

    eng = TDUSCFEngine(wfn, ptype='rpa')
    X_jb = [psi4.core.Matrix.from_array(v.reshape((ni, na))) for v in tuple(np.eye(nia).T)]
    zero_jb = [psi4.core.Matrix(ni, na) for x in range(nIA)]
    X_JB = [psi4.core.Matrix.from_array(v.reshape((nI, nA))) for v in tuple(np.eye(nIA).T)]
    zero_JB = [psi4.core.Matrix(nI, nA) for x in range(nia)]
    # Guess Identity:
    #     X_I0          X_0I          =      X_I0      X_0I
    # [ I{nOV x nOV} | 0{nOV x nov}]  = [ X{KC,JB} | 0{KC, jb}]
    # [ 0{nov x nOV} | I{nov x nov}]    [ 0{kc,JB} | X{kc, jb}]

    # Products:
    # [ A+/-B{IA, KC}  A+/-B{IA, kc}] [ I{KC, JB} |  0{KC,jb}] = [A+/-B x X_I0] = [ (A+/-B)_IAJB, (A+/-B)_iaJB]
    # [ A+/-B{ia, KC}  A+/-B{ia, kc}] [ O{kc, JB} |  X{kc,jb}]   [A+/-B x X_0I] = [ (A+/-B)_IAjb, (A+/-B)_iajb]
    X_I0 = [[x, zero] for x, zero in zip(X_JB, zero_jb)]
    X_0I = [[zero, x] for zero, x in zip(zero_JB, X_jb)]
    Px_I0, Mx_I0 = eng.compute_products(X_I0)[:-1]
    Px_0I, Mx_0I = eng.compute_products(X_0I)[:-1]

    P_IAJB_test = np.column_stack([x[0].to_array().flatten() for x in Px_I0])
    assert compare_arrays(P_ref['IAJB'].reshape(nIA, nIA), P_IAJB_test, 8, "A_IAJB")

    M_IAJB_test = np.column_stack([x[0].to_array().flatten() for x in Mx_I0])
    assert compare_arrays(M_ref['IAJB'].reshape(nIA, nIA), M_IAJB_test, 8, "A_IAJB")

    P_iaJB_test = np.column_stack([x[1].to_array().flatten() for x in Px_I0])
    assert compare_arrays(P_ref['iaJB'].reshape(nia, nIA), P_iaJB_test, 8, "P_iaJB")

    M_iaJB_test = np.column_stack([x[1].to_array().flatten() for x in Mx_I0])
    assert compare_arrays(M_ref['iaJB'].reshape(nia, nIA), M_iaJB_test, 8, "M_iaJB")

    P_IAjb_test = np.column_stack([x[0].to_array().flatten() for x in Px_0I])
    assert compare_arrays(P_ref['IAjb'].reshape(nIA, nia), P_IAjb_test, 8, "P_IAjb")

    M_IAjb_test = np.column_stack([x[0].to_array().flatten() for x in Mx_0I])
    assert compare_arrays(M_ref['IAjb'].reshape(nIA, nia), M_IAjb_test, 8, "M_IAjb")

    P_iajb_test = np.column_stack([x[1].to_array().flatten() for x in Px_0I])
    assert compare_arrays(P_ref['iajb'].reshape(nia, nia), P_iajb_test, 8, "P_iajb")

    M_iajb_test = np.column_stack([x[1].to_array().flatten() for x in Mx_0I])
    assert compare_arrays(M_ref['iajb'].reshape(nia, nia), M_iajb_test, 8, "M_iajb")
Esempio n. 2
0
def test_unrestricted_RPA_C1():
    ch2 = psi4.geometry("""
    0 3
    c
    h 1 1.0
    h 1 1.0 2 125.0
    symmetry c1
    """)
    psi4.set_options({"scf_type": "pk", 'reference': 'UHF', 'save_jk': True})
    e, wfn = psi4.energy("hf/cc-pvdz", molecule=ch2, return_wfn=True)
    A_ref, B_ref = build_UHF_AB_C1(wfn)
    nI, nA, _, _ = A_ref['IAJB'].shape
    nIA = nI * nA
    ni, na, _, _ = A_ref['iajb'].shape
    nia = ni * na

    P_ref = {k: A_ref[k] + B_ref[k] for k in A_ref.keys()}
    M_ref = {k: A_ref[k] - B_ref[k] for k in A_ref.keys()}

    eng = TDUSCFEngine(wfn, ptype='rpa')
    X_jb = [psi4.core.Matrix.from_array(v.reshape((ni, na))) for v in tuple(np.eye(nia).T)]
    zero_jb = [psi4.core.Matrix(ni, na) for x in range(nIA)]
    X_JB = [psi4.core.Matrix.from_array(v.reshape((nI, nA))) for v in tuple(np.eye(nIA).T)]
    zero_JB = [psi4.core.Matrix(nI, nA) for x in range(nia)]
    # Guess Identity:
    #     X_I0          X_0I          =      X_I0      X_0I
    # [ I{nOV x nOV} | 0{nOV x nov}]  = [ X{KC,JB} | 0{KC, jb}]
    # [ 0{nov x nOV} | I{nov x nov}]    [ 0{kc,JB} | X{kc, jb}]

    # Products:
    # [ A+/-B{IA, KC}  A+/-B{IA, kc}] [ I{KC, JB} |  0{KC,jb}] = [A+/-B x X_I0] = [ (A+/-B)_IAJB, (A+/-B)_iaJB]
    # [ A+/-B{ia, KC}  A+/-B{ia, kc}] [ O{kc, JB} |  X{kc,jb}]   [A+/-B x X_0I] = [ (A+/-B)_IAjb, (A+/-B)_iajb]
    X_I0 = [[x, zero] for x, zero in zip(X_JB, zero_jb)]
    X_0I = [[zero, x] for zero, x in zip(zero_JB, X_jb)]
    Px_I0, Mx_I0 = eng.compute_products(X_I0)[:-1]
    Px_0I, Mx_0I = eng.compute_products(X_0I)[:-1]

    P_IAJB_test = np.column_stack([x[0].to_array().flatten() for x in Px_I0])
    assert compare_arrays(P_ref['IAJB'].reshape(nIA, nIA), P_IAJB_test, 8, "A_IAJB")

    M_IAJB_test = np.column_stack([x[0].to_array().flatten() for x in Mx_I0])
    assert compare_arrays(M_ref['IAJB'].reshape(nIA, nIA), M_IAJB_test, 8, "A_IAJB")

    P_iaJB_test = np.column_stack([x[1].to_array().flatten() for x in Px_I0])
    assert compare_arrays(P_ref['iaJB'].reshape(nia, nIA), P_iaJB_test, 8, "P_iaJB")

    M_iaJB_test = np.column_stack([x[1].to_array().flatten() for x in Mx_I0])
    assert compare_arrays(M_ref['iaJB'].reshape(nia, nIA), M_iaJB_test, 8, "M_iaJB")

    P_IAjb_test = np.column_stack([x[0].to_array().flatten() for x in Px_0I])
    assert compare_arrays(P_ref['IAjb'].reshape(nIA, nia), P_IAjb_test, 8, "P_IAjb")

    M_IAjb_test = np.column_stack([x[0].to_array().flatten() for x in Mx_0I])
    assert compare_arrays(M_ref['IAjb'].reshape(nIA, nia), M_IAjb_test, 8, "M_IAjb")

    P_iajb_test = np.column_stack([x[1].to_array().flatten() for x in Px_0I])
    assert compare_arrays(P_ref['iajb'].reshape(nia, nia), P_iajb_test, 8, "P_iajb")

    M_iajb_test = np.column_stack([x[1].to_array().flatten() for x in Mx_0I])
    assert compare_arrays(M_ref['iajb'].reshape(nia, nia), M_iajb_test, 8, "M_iajb")
Esempio n. 3
0
def _solve_loop(wfn,
                ptype,
                solve_function,
                states_per_irrep: List[int],
                maxiter: int,
                restricted: bool = True,
                spin_mult: str = "singlet") -> List[_TDSCFResults]:
    """

    References
    ----------
    For the expression of the transition moments in length and velocity gauges:

    - T. B. Pedersen, A. E. Hansen, "Ab Initio Calculation and Display of the
    Rotary Strength Tensor in the Random Phase Approximation. Method and Model
    Studies." Chem. Phys. Lett., 246, 1 (1995)
    - P. J. Lestrange, F. Egidi, X. Li, "The Consequences of Improperly
    Describing Oscillator Strengths beyond the Electric Dipole Approximation."
    J. Chem. Phys., 143, 234103 (2015)
    """

    core.print_out("\n  ==> Requested Excitations <==\n\n")
    for nstate, state_sym in zip(states_per_irrep,
                                 wfn.molecule().irrep_labels()):
        core.print_out(
            f"      {nstate} {spin_mult} states with {state_sym} symmetry\n")

    # construct the engine
    if restricted:
        if spin_mult == "triplet":
            engine = TDRSCFEngine(wfn, ptype=ptype.lower(), triplet=True)
        else:
            engine = TDRSCFEngine(wfn, ptype=ptype.lower(), triplet=False)
    else:
        engine = TDUSCFEngine(wfn, ptype=ptype.lower())

    # collect results and compute some spectroscopic observables
    mints = core.MintsHelper(wfn.basisset())
    results = []
    irrep_GS = wfn.molecule().irrep_labels()[engine.G_gs]
    for state_sym, nstates in enumerate(states_per_irrep):
        if nstates == 0:
            continue
        irrep_ES = wfn.molecule().irrep_labels()[state_sym]
        core.print_out(
            f"\n\n  ==> Seeking the lowest {nstates} {spin_mult} states with {irrep_ES} symmetry"
        )
        engine.reset_for_state_symm(state_sym)
        guess_ = engine.generate_guess(nstates * 4)

        # ret = {"eigvals": ee, "eigvecs": (rvecs, rvecs), "stats": stats} (TDA)
        # ret = {"eigvals": ee, "eigvecs": (rvecs, lvecs), "stats": stats} (RPA)
        ret = solve_function(engine, nstates, guess_, maxiter)

        # check whether all roots converged
        if not ret["stats"][-1]["done"]:
            # raise error
            raise TDSCFConvergenceError(
                maxiter, wfn, f"singlet excitations in irrep {irrep_ES}",
                ret["stats"][-1])

        # flatten dictionary: helps with sorting by energy
        # also append state symmetry to return value
        for e, (R, L) in zip(ret["eigvals"], ret["eigvecs"]):
            irrep_trans = wfn.molecule().irrep_labels()[engine.G_gs
                                                        ^ state_sym]

            # length-gauge electric dipole transition moment
            edtm_length = engine.residue(R, mints.so_dipole())
            # length-gauge oscillator strength
            f_length = ((2 * e) / 3) * np.sum(edtm_length**2)
            # velocity-gauge electric dipole transition moment
            edtm_velocity = engine.residue(L, mints.so_nabla())
            ## velocity-gauge oscillator strength
            f_velocity = (2 / (3 * e)) * np.sum(edtm_velocity**2)
            # length gauge magnetic dipole transition moment
            # 1/2 is the Bohr magneton in atomic units
            mdtm = 0.5 * engine.residue(L, mints.so_angular_momentum())
            # NOTE The signs for rotatory strengths are opposite WRT the cited paper.
            # This is becasue Psi4 defines length-gauge dipole integral to include the electron charge (-1.0)
            # length gauge rotatory strength
            R_length = np.einsum("i,i", edtm_length, mdtm)
            # velocity gauge rotatory strength
            R_velocity = -np.einsum("i,i", edtm_velocity, mdtm) / e

            results.append(
                _TDSCFResults(e, irrep_GS, irrep_ES, irrep_trans, edtm_length,
                              f_length, edtm_velocity, f_velocity, mdtm,
                              R_length, R_velocity, spin_mult, R, L))

    return results
Esempio n. 4
0
def engines():
    return {
        'RHF-1': lambda w, p: TDRSCFEngine(w, ptype=p.lower(), triplet=False),
        'RHF-3': lambda w, p: TDRSCFEngine(w, ptype=p.lower(), triplet=True),
        'UHF': lambda w, p: TDUSCFEngine(w, ptype=p.lower())
    }
Esempio n. 5
0
def tdscf_excitations(wfn, **kwargs):
    """Compute excitations from a scf(HF/KS) wavefunction:

    Parameters
    -----------
    wfn : :py:class:`psi4.core.Wavefunction`
       The reference wavefunction
    states_per_irrep : list (int), {optional}
       The solver will find this many lowest excitations for each irreducible representation of the computational point group.
       The default is to find the lowest excitation of each symmetry. If this option is provided it must have the same number of elements
       as the number of irreducible representations as the computational point group.
    triplets : str {optional, ``none``, ``only``, ``also``}
       The default ``none`` will solve for no triplet states, ``only`` will solve for triplet states only, and ``also``
       will solve for the requested number of states of both triplet and singlet. This option is only valid for restricted references,
       and is ignored otherwise. The triplet and singlet solutions are found separately so using the ``also`` option will roughly double
       the computational cost of the calculation.
    tda :  bool {optional ``False``}
       If true the Tamm-Dancoff approximation (TDA) will be employed. For HF references this is equivalent to CIS.
    e_tol : float, {optional, 1.0e-6}
       The convergence threshold for the excitation energy
    r_tol : float, {optional, 1.0e-8}
       The convergence threshold for the norm of the residual vector
    max_ss_vectors: int {optional}
       The maximum number of ss vectors that will be stored before a collapse is done.
    guess : str
       If string the guess that will be used. Allowed choices:
       - ``denominators``: {default} uses orbital energy differences to generate guess vectors.


    ..note:: The algorithm employed to solve the non-Hermitian eigenvalue problem
             (when ``tda`` is False) will fail when the SCF wavefunction has a triplet instability.
    """
    # gather arguments
    e_tol = kwargs.pop('e_tol', 1.0e-6)
    r_tol = kwargs.pop('r_tol', 1.0e-8)
    max_ss_vec = kwargs.pop('max_ss_vectors', 50)
    verbose = kwargs.pop('print_lvl', 0)

    # how many states
    passed_spi = kwargs.pop('states_per_irrep',
                            [0 for _ in range(wfn.nirrep())])

    #TODO:states_per_irrep = _validate_states_args(wfn, passed_spi, nstates)
    states_per_irrep = passed_spi

    #TODO: guess types, user guess
    guess_type = kwargs.pop("guess", "denominators")
    if guess_type != "denominators":
        raise ValidationError("Guess type {} is not valid".format(guess_type))

    # which problem
    ptype = 'rpa'
    solve_function = solvers.hamiltonian_solver
    if kwargs.pop('tda', False):
        ptype = 'tda'
        solve_function = solvers.davidson_solver

    restricted = wfn.same_a_b_orbs()
    if restricted:
        triplet = kwargs.pop('triplet', False)
    else:
        triplet = None

    _print_tdscf_header(
        etol=e_tol,
        rtol=r_tol,
        states=[(count, label)
                for count, label in zip(states_per_irrep,
                                        wfn.molecule().irrep_labels())],
        guess_type=guess_type,
        restricted=restricted,
        triplet=triplet,
        ptype=ptype)

    # construct the engine
    if restricted:
        engine = TDRSCFEngine(wfn, triplet=triplet, ptype=ptype)
    else:
        engine = TDUSCFEngine(wfn, ptype=ptype)

    # just energies for now
    solver_results = []
    for state_sym, nstates in enumerate(states_per_irrep):
        if nstates == 0:
            continue
        engine.reset_for_state_symm(state_sym)
        guess_ = engine.generate_guess(nstates * 2)

        vecs_per_root = max_ss_vec // nstates

        # ret = (ee, rvecs, stats) (TDA)
        # ret = (ee, rvecs, lvecs, stats) (full TDSCF)
        ret = solve_function(engine=engine,
                             e_tol=e_tol,
                             r_tol=r_tol,
                             max_vecs_per_root=vecs_per_root,
                             nroot=nstates,
                             guess=guess_,
                             verbose=verbose)

        # store excitation energies tagged with final state symmetry (for printing)
        # TODO: handle R eigvecs (TDA) R/L eigvecs(full TDSCF): solver maybe should return dicts
        for ee in ret[0]:
            solver_results.append((ee, state_sym))

    # sort by energy symmetry is just meta data
    solver_results.sort(key=lambda x: x[0])

    # print excitation energies
    core.print_out("\n\nFinal Energetic Summary:\n")
    core.print_out("        " + (" " * 20) + " " +
                   "Excitation Energy".center(31) +
                   " {:^15}\n".format("Total Energy"))
    core.print_out("    {:^4} {:^20} {:^15} {:^15} {:^15}\n".format(
        "#", "Sym: GS->ES (Trans)", "[au]", "[eV]", "(au)"))
    core.print_out("    {:->4} {:->20} {:->15} {:->15} {:->15}\n".format(
        "-", "-", "-", "-", "-"))

    irrep_GS = wfn.molecule().irrep_labels()[engine.G_gs]
    for i, (E_ex_au, final_sym) in enumerate(solver_results):
        irrep_ES = wfn.molecule().irrep_labels()[final_sym]
        irrep_trans = wfn.molecule().irrep_labels()[engine.G_gs ^ final_sym]
        sym_descr = "{}->{} ({})".format(irrep_GS, irrep_ES, irrep_trans)

        #TODO: psivars/wfnvars

        E_ex_ev = constants.conversion_factor('hartree', 'eV') * E_ex_au

        E_tot_au = wfn.energy() + E_ex_au
        core.print_out(
            "    {:^4} {:^20} {:< 15.5f} {:< 15.5f} {:< 15.5f}\n".format(
                i + 1, sym_descr, E_ex_au, E_ex_ev, E_tot_au))

    core.print_out("\n")

    #TODO: output table

    #TODO: oscillator strengths

    #TODO: check/handle convergence failures

    return solver_results