def _chain_dot(*args, **kwargs): """ Chains dot products together from a series of Psi4 Matrix classes. By default there is no transposes, an optional vector of booleans can be passed in. """ trans = kwargs.pop("trans", None) if trans is None: trans = [False for x in range(len(args))] else: if len(trans) != len(args): raise ValidationError( "Chain dot: The length of the transpose arguements is not equal to the length of args.") # Setup chain ret = args[0] if trans[0]: ret = ret.transpose() # Run through for n, mat in enumerate(args[1:]): ret = core.doublet(ret, mat, False, trans[n + 1]) return ret
def _chain_dot(*args, **kwargs) -> core.Matrix: """Chains dot products together from a series of Psi4 Matrix classes. Uses :func:`~psi4.core.doublet`. Parameters ---------- args Arbitrary number of :class:`~psi4.core.Matrix` arguments to be multiplied. trans Optional iterable of booleans of length number of `args` to designate transposes, if any. """ trans = kwargs.pop("trans", None) if trans is None: trans = [False for x in range(len(args))] else: if len(trans) != len(args): raise ValidationError( "Chain dot: The length of the transpose arguements is not equal to the length of args." ) # Setup chain ret = args[0] if trans[0]: ret = ret.transpose() # Run through for n, mat in enumerate(args[1:]): ret = core.doublet(ret, mat, False, trans[n + 1]) return ret
def _core_doublet(A, B, transA, transB): """Multiply two matrices together. .. deprecated:: 1.4 Use :py:func:`psi4.core.doublet` instead. """ warnings.warn( "Using `psi4.core.Matrix.doublet` instead of `psi4.core.doublet` is deprecated, and in 1.4 it will stop working\n", category=FutureWarning, stacklevel=2) return core.doublet(A, B, transA, transB)
def test_doublets(adl, adr, Ga, bdl, bdr, Gb, at, bt): a = build_random_mat(adl, adr, Ga) b = build_random_mat(bdl, bdr, Gb) res = doublet(a, b, at, bt) expected = generate_result(a, b, at, bt) assert res.symmetry() == a.symmetry() ^ b.symmetry(), "Symm mismatch {} x {} != {}".format( a.symmetry(), b.symemtry(), res.symmetry()) res_blocks = res.to_array() if isinstance(res_blocks, np.ndarray): res_blocks = [res_blocks] block_checks = [] for blk_idx in range(res.nirrep()): assert compare_arrays(expected[blk_idx], res_blocks[blk_idx], 8, "Block[{}]".format(blk_idx))
def test_doublets(adl, adr, Ga, bdl, bdr, Gb, at, bt): a = build_random_mat(adl, adr, Ga) b = build_random_mat(bdl, bdr, Gb) res = doublet(a, b, at, bt) expected = generate_result(a, b, at, bt) assert res.symmetry() == a.symmetry() ^ b.symmetry( ), f"Symm mismatch {a.symmetry()} x {b.symmetry()} != {res.symmetry()}" res_blocks = res.to_array() if isinstance(res_blocks, np.ndarray): res_blocks = [res_blocks] block_checks = [] for blk_idx in range(res.nirrep()): assert compare_arrays(expected[blk_idx], res_blocks[blk_idx], 8, f"Block[{blk_idx}]")
def orthogonalize(C, S): nbf, nocc = C.shape eigenvectors = core.Matrix(nocc, nocc) eigvals = core.Vector(nocc) sqrt_eigvals = core.Vector(nocc) CTSC = core.triplet(C, S, C, True, False, False) CTSC.diagonalize(eigenvectors, eigvals, core.DiagonalizeOrder.Ascending) orthonormal = core.doublet(C, eigenvectors, False, False) sqrt_eigvals.np[:] = np.sqrt(eigvals.np) orthonormal.np[:, :] /= sqrt_eigvals.np[np.newaxis, :] return orthonormal
def _compute_fxc(PQrho, half_Saux, halfp_Saux, x_alpha, rho_thresh=1.e-8): """ Computes the gridless (P|fxc|Q) ALDA tensor. """ naux = PQrho.shape[0] # Level it out PQrho_lvl = core.triplet(half_Saux, PQrho, half_Saux, False, False, False) # Rotate into a diagonal basis rho = core.Vector("rho eigenvalues", naux) U = core.Matrix("rho eigenvectors", naux, naux) PQrho_lvl.diagonalize(U, rho, core.DiagonalizeOrder.Ascending) # "Gridless DFT" mask = rho.np < rho_thresh # Values too small cause singularities rho.np[mask] = rho_thresh dft_size = rho.shape[0] inp = {"RHO_A": rho} out = { "V": core.Vector(dft_size), "V_RHO_A": core.Vector(dft_size), "V_RHO_A_RHO_A": core.Vector(dft_size) } func_x = core.LibXCFunctional('XC_LDA_X', True) func_x.compute_functional(inp, out, dft_size, 2) out["V_RHO_A_RHO_A"].scale(1.0 - x_alpha) func_c = core.LibXCFunctional('XC_LDA_C_VWN', True) func_c.compute_functional(inp, out, dft_size, 2) out["V_RHO_A_RHO_A"].np[mask] = 0 # Rotate back Ul = U.clone() Ul.np[:] *= out["V_RHO_A_RHO_A"].np tmp = core.doublet(Ul, U, False, True) # Undo the leveling return core.triplet(halfp_Saux, tmp, halfp_Saux, False, False, False)
def _compute_fxc(PQrho, half_Saux, halfp_Saux, rho_thresh=1.e-8): """ Computes the gridless (P|fxc|Q) ALDA tensor. """ naux = PQrho.shape[0] # Level it out PQrho_lvl = core.triplet(half_Saux, PQrho, half_Saux, False, False, False) # Rotate into a diagonal basis rho = core.Vector("rho eigenvalues", naux) U = core.Matrix("rho eigenvectors", naux, naux) PQrho_lvl.diagonalize(U, rho, core.DiagonalizeOrder.Ascending) # "Gridless DFT" mask = rho.np < rho_thresh # Values too small cause singularities rho.np[mask] = rho_thresh dft_size = rho.shape[0] inp = {"RHO_A": rho} out = {"V": core.Vector(dft_size), "V_RHO_A": core.Vector(dft_size), "V_RHO_A_RHO_A": core.Vector(dft_size)} func_x = core.LibXCFunctional('XC_LDA_X', True) func_x.compute_functional(inp, out, dft_size, 2) func_c = core.LibXCFunctional('XC_LDA_C_VWN', True) func_c.compute_functional(inp, out, dft_size, 2) out["V_RHO_A_RHO_A"].np[mask] = 0 # Rotate back Ul = U.clone() Ul.np[:] *= out["V_RHO_A_RHO_A"].np tmp = core.doublet(Ul, U, False, True) # Undo the leveling return core.triplet(halfp_Saux, tmp, halfp_Saux, False, False, False)
def __call__(self, mol1_wfn, mol2_wfn): nbf = self.p.dimer_basis.nbf() nbf1 = mol1_wfn.nso() nbf2 = mol2_wfn.nso() U, S, VT = np.linalg.svd(mol1_wfn.Da()) C_left = np.dot(U, np.diag(np.sqrt(S))) C_right = np.dot(VT.T, np.diag(np.sqrt(S))) C_left_ = core.Matrix(nbf, max(nbf1, nbf2)) C_right_ = core.Matrix(nbf, max(nbf1, nbf2)) C_left_.np[:nbf1, :nbf1] = C_left[:, :] C_right_.np[:nbf1, :nbf1] = C_right[:, :] self.jk.C_clear() self.jk.C_left_add(C_left_) self.jk.C_right_add(C_right_) self.jk.compute() J = self.jk.J()[0] D1 = self.jk.D()[0] assert np.max(np.abs(D1.np[:nbf1, :nbf1] - mol1_wfn.Da().np)) < 1e-10 J_1to2 = J.np[nbf1:, nbf1:] elel_1to2 = 2 * np.sum(J_1to2 * mol2_wfn.Da()) nuel_1to2 = 2 * (self.p.dimer_V.vector_dot(D1) - self.p.monomer1_V.vector_dot(mol1_wfn.Da())) ovlp1 = core.doublet(self.p.dimer_S, D1, False, False) ####################################################################### U, S, VT = np.linalg.svd(mol2_wfn.Da()) C_left = np.dot(U, np.diag(np.sqrt(S))) C_right = np.dot(VT.T, np.diag(np.sqrt(S))) C_left_ = core.Matrix(nbf, max(nbf1, nbf2)) C_right_ = core.Matrix(nbf, max(nbf1, nbf2)) C_left_.np[-nbf2:, -nbf2:] = C_left[:, :] C_right_.np[-nbf2:, -nbf2:] = C_right[:, :] self.jk.C_clear() self.jk.C_left_add(C_left_) self.jk.C_right_add(C_right_) self.jk.compute() J = self.jk.J()[0] D2 = self.jk.D()[0] assert np.max(np.abs(D2.np[nbf1:, nbf1:] - mol2_wfn.Da().np)) < 1e-10 J_2to1 = J.np[:nbf1, :nbf1] elel_2to1 = 2 * np.sum(J_2to1 * mol1_wfn.Da()) nuel_2to1 = 2 * (self.p.dimer_V.vector_dot(D2) - self.p.monomer2_V.vector_dot(mol2_wfn.Da())) ovlp2 = core.doublet(self.p.dimer_S, D2, False, False) overlap = 4 * np.sum(ovlp1.np * ovlp2.np.T) #assert abs(elel_1to2 - elel_2to1) < 1e-10 electrostatic = self.p.nuclear_interaction_energy + nuel_1to2 + nuel_2to1 + elel_1to2 + elel_2to1 return electrostatic, overlap
def build_sapt_jk_cache(wfn_A, wfn_B, jk, do_print=True): """ Constructs the DCBS cache data required to compute ELST/EXCH/IND """ core.print_out("\n ==> Preparing SAPT Data Cache <== \n\n") jk.print_header() cache = {} cache["wfn_A"] = wfn_A cache["wfn_B"] = wfn_B # First grab the orbitals cache["Cocc_A"] = wfn_A.Ca_subset("AO", "OCC") cache["Cvir_A"] = wfn_A.Ca_subset("AO", "VIR") cache["Cocc_B"] = wfn_B.Ca_subset("AO", "OCC") cache["Cvir_B"] = wfn_B.Ca_subset("AO", "VIR") cache["eps_occ_A"] = wfn_A.epsilon_a_subset("AO", "OCC") cache["eps_vir_A"] = wfn_A.epsilon_a_subset("AO", "VIR") cache["eps_occ_B"] = wfn_B.epsilon_a_subset("AO", "OCC") cache["eps_vir_B"] = wfn_B.epsilon_a_subset("AO", "VIR") # Build the densities as HF takes an extra "step" cache["D_A"] = core.doublet( cache["Cocc_A"], cache["Cocc_A"], False, True) cache["D_B"] = core.doublet( cache["Cocc_B"], cache["Cocc_B"], False, True) cache["P_A"] = core.doublet( cache["Cvir_A"], cache["Cvir_A"], False, True) cache["P_B"] = core.doublet( cache["Cvir_B"], cache["Cvir_B"], False, True) # Potential ints mints = core.MintsHelper(wfn_A.basisset()) cache["V_A"] = mints.ao_potential() # cache["V_A"].axpy(1.0, wfn_A.Va()) mints = core.MintsHelper(wfn_B.basisset()) cache["V_B"] = mints.ao_potential() # cache["V_B"].axpy(1.0, wfn_B.Va()) # Anything else we might need cache["S"] = wfn_A.S().clone() # J and K matrices jk.C_clear() # Normal J/K for Monomer A jk.C_left_add(wfn_A.Ca_subset("SO", "OCC")) jk.C_right_add(wfn_A.Ca_subset("SO", "OCC")) # Normal J/K for Monomer B jk.C_left_add(wfn_B.Ca_subset("SO", "OCC")) jk.C_right_add(wfn_B.Ca_subset("SO", "OCC")) # K_O J/K C_O_A = core.triplet( cache["D_B"], cache["S"], cache["Cocc_A"], False, False, False) jk.C_left_add(C_O_A) jk.C_right_add(cache["Cocc_A"]) jk.compute() # Clone them as the JK object will overwrite. cache["J_A"] = jk.J()[0].clone() cache["K_A"] = jk.K()[0].clone() cache["J_B"] = jk.J()[1].clone() cache["K_B"] = jk.K()[1].clone() cache["J_O"] = jk.J()[2].clone() cache["K_O"] = jk.K()[2].clone() cache["K_O"].transpose_this() monA_nr = wfn_A.molecule().nuclear_repulsion_energy() monB_nr = wfn_B.molecule().nuclear_repulsion_energy() dimer_nr = wfn_A.molecule().extract_subsets( [1, 2]).nuclear_repulsion_energy() cache["nuclear_repulsion_energy"] = dimer_nr - monA_nr - monB_nr return cache
def exchange(cache, jk, do_print=True): """ Computes the E10 exchange (S^2 and S^inf) from a build_sapt_jk_cache datacache. """ if do_print: core.print_out("\n ==> E10 Exchange <== \n\n") # Build potenitals h_A = cache["V_A"].clone() h_A.axpy(2.0, cache["J_A"]) h_A.axpy(-1.0, cache["K_A"]) h_B = cache["V_B"].clone() h_B.axpy(2.0, cache["J_B"]) h_B.axpy(-1.0, cache["K_B"]) w_A = cache["V_A"].clone() w_A.axpy(2.0, cache["J_A"]) w_B = cache["V_B"].clone() w_B.axpy(2.0, cache["J_B"]) # Build inverse exchange metric nocc_A = cache["Cocc_A"].shape[1] nocc_B = cache["Cocc_B"].shape[1] SAB = core.triplet( cache["Cocc_A"], cache["S"], cache["Cocc_B"], True, False, False) num_occ = nocc_A + nocc_B Sab = core.Matrix(num_occ, num_occ) Sab.np[:nocc_A, nocc_A:] = SAB.np Sab.np[nocc_A:, :nocc_A] = SAB.np.T Sab.np[np.diag_indices_from(Sab.np)] += 1 Sab.power(-1.0, 1.e-14) Sab.np[np.diag_indices_from(Sab.np)] -= 1.0 Tmo_AA = core.Matrix.from_array(Sab.np[:nocc_A, :nocc_A]) Tmo_BB = core.Matrix.from_array(Sab.np[nocc_A:, nocc_A:]) Tmo_AB = core.Matrix.from_array(Sab.np[:nocc_A, nocc_A:]) T_A = np.dot(cache["Cocc_A"], Tmo_AA).dot(cache["Cocc_A"].np.T) T_B = np.dot(cache["Cocc_B"], Tmo_BB).dot(cache["Cocc_B"].np.T) T_AB = np.dot(cache["Cocc_A"], Tmo_AB).dot(cache["Cocc_B"].np.T) S = cache["S"] D_A = cache["D_A"] P_A = cache["P_A"] D_B = cache["D_B"] P_B = cache["P_B"] # Compute the J and K matrices jk.C_clear() jk.C_left_add(cache["Cocc_A"]) jk.C_right_add(core.doublet(cache["Cocc_A"], Tmo_AA, False, False)) jk.C_left_add(cache["Cocc_B"]) jk.C_right_add(core.doublet(cache["Cocc_A"], Tmo_AB, False, False)) jk.C_left_add(cache["Cocc_A"]) jk.C_right_add(core.Matrix.chain_dot(P_B, S, cache["Cocc_A"])) jk.compute() JT_A, JT_AB, Jij = jk.J() KT_A, KT_AB, Kij = jk.K() # Start S^2 Exch_s2 = 0.0 tmp = core.Matrix.chain_dot(D_A, S, D_B, S, P_A) Exch_s2 -= 2.0 * w_B.vector_dot(tmp) tmp = core.Matrix.chain_dot(D_B, S, D_A, S, P_B) Exch_s2 -= 2.0 * w_A.vector_dot(tmp) tmp = core.Matrix.chain_dot(P_A, S, D_B) Exch_s2 -= 2.0 * Kij.vector_dot(tmp) if do_print: core.print_out(print_sapt_var("Exch10(S^2) ", Exch_s2, short=True)) core.print_out("\n") # Start Sinf Exch10 = 0.0 Exch10 -= 2.0 * np.vdot(cache["D_A"], cache["K_B"]) Exch10 += 2.0 * np.vdot(T_A, h_B.np) Exch10 += 2.0 * np.vdot(T_B, h_A.np) Exch10 += 2.0 * np.vdot(T_AB, h_A.np + h_B.np) Exch10 += 4.0 * np.vdot(T_B, JT_AB.np - 0.5 * KT_AB.np) Exch10 += 4.0 * np.vdot(T_A, JT_AB.np - 0.5 * KT_AB.np) Exch10 += 4.0 * np.vdot(T_B, JT_A.np - 0.5 * KT_A.np) Exch10 += 4.0 * np.vdot(T_AB, JT_AB.np - 0.5 * KT_AB.np.T) if do_print: core.set_variable("Exch10", Exch10) core.print_out(print_sapt_var("Exch10", Exch10, short=True)) core.print_out("\n") return {"Exch10(S^2)": Exch_s2, "Exch10": Exch10}
def exchange(cache, jk, do_print=True): """ Computes the E10 exchange (S^2 and S^inf) from a build_sapt_jk_cache datacache. """ if do_print: core.print_out("\n ==> E10 Exchange <== \n\n") # Build potenitals h_A = cache["V_A"].clone() h_A.axpy(2.0, cache["J_A"]) h_A.axpy(-1.0, cache["K_A"]) h_B = cache["V_B"].clone() h_B.axpy(2.0, cache["J_B"]) h_B.axpy(-1.0, cache["K_B"]) w_A = cache["V_A"].clone() w_A.axpy(2.0, cache["J_A"]) w_B = cache["V_B"].clone() w_B.axpy(2.0, cache["J_B"]) # Build inverse exchange metric nocc_A = cache["Cocc_A"].shape[1] nocc_B = cache["Cocc_B"].shape[1] SAB = core.triplet(cache["Cocc_A"], cache["S"], cache["Cocc_B"], True, False, False) num_occ = nocc_A + nocc_B Sab = core.Matrix(num_occ, num_occ) Sab.np[:nocc_A, nocc_A:] = SAB.np Sab.np[nocc_A:, :nocc_A] = SAB.np.T Sab.np[np.diag_indices_from(Sab.np)] += 1 Sab.power(-1.0, 1.e-14) Sab.np[np.diag_indices_from(Sab.np)] -= 1.0 Tmo_AA = core.Matrix.from_array(Sab.np[:nocc_A, :nocc_A]) Tmo_BB = core.Matrix.from_array(Sab.np[nocc_A:, nocc_A:]) Tmo_AB = core.Matrix.from_array(Sab.np[:nocc_A, nocc_A:]) T_A = np.dot(cache["Cocc_A"], Tmo_AA).dot(cache["Cocc_A"].np.T) T_B = np.dot(cache["Cocc_B"], Tmo_BB).dot(cache["Cocc_B"].np.T) T_AB = np.dot(cache["Cocc_A"], Tmo_AB).dot(cache["Cocc_B"].np.T) S = cache["S"] D_A = cache["D_A"] P_A = cache["P_A"] D_B = cache["D_B"] P_B = cache["P_B"] # Compute the J and K matrices jk.C_clear() jk.C_left_add(cache["Cocc_A"]) jk.C_right_add(core.doublet(cache["Cocc_A"], Tmo_AA, False, False)) jk.C_left_add(cache["Cocc_B"]) jk.C_right_add(core.doublet(cache["Cocc_A"], Tmo_AB, False, False)) jk.C_left_add(cache["Cocc_A"]) jk.C_right_add(core.Matrix.chain_dot(P_B, S, cache["Cocc_A"])) jk.compute() JT_A, JT_AB, Jij = jk.J() KT_A, KT_AB, Kij = jk.K() # Start S^2 Exch_s2 = 0.0 tmp = core.Matrix.chain_dot(D_A, S, D_B, S, P_A) Exch_s2 -= 2.0 * w_B.vector_dot(tmp) tmp = core.Matrix.chain_dot(D_B, S, D_A, S, P_B) Exch_s2 -= 2.0 * w_A.vector_dot(tmp) tmp = core.Matrix.chain_dot(P_A, S, D_B) Exch_s2 -= 2.0 * Kij.vector_dot(tmp) if do_print: core.print_out(print_sapt_var("Exch10(S^2) ", Exch_s2, short=True)) core.print_out("\n") # Start Sinf Exch10 = 0.0 Exch10 -= 2.0 * np.vdot(cache["D_A"], cache["K_B"]) Exch10 += 2.0 * np.vdot(T_A, h_B.np) Exch10 += 2.0 * np.vdot(T_B, h_A.np) Exch10 += 2.0 * np.vdot(T_AB, h_A.np + h_B.np) Exch10 += 4.0 * np.vdot(T_B, JT_AB.np - 0.5 * KT_AB.np) Exch10 += 4.0 * np.vdot(T_A, JT_AB.np - 0.5 * KT_AB.np) Exch10 += 4.0 * np.vdot(T_B, JT_A.np - 0.5 * KT_A.np) Exch10 += 4.0 * np.vdot(T_AB, JT_AB.np - 0.5 * KT_AB.np.T) if do_print: core.set_variable("Exch10", Exch10) core.print_out(print_sapt_var("Exch10", Exch10, short=True)) core.print_out("\n") return {"Exch10(S^2)": Exch_s2, "Exch10": Exch10}
def build_sapt_jk_cache(wfn_A, wfn_B, jk, do_print=True): """ Constructs the DCBS cache data required to compute ELST/EXCH/IND """ core.print_out("\n ==> Preparing SAPT Data Cache <== \n\n") jk.print_header() cache = {} cache["wfn_A"] = wfn_A cache["wfn_B"] = wfn_B # First grab the orbitals cache["Cocc_A"] = wfn_A.Ca_subset("AO", "OCC") cache["Cvir_A"] = wfn_A.Ca_subset("AO", "VIR") cache["Cocc_B"] = wfn_B.Ca_subset("AO", "OCC") cache["Cvir_B"] = wfn_B.Ca_subset("AO", "VIR") cache["eps_occ_A"] = wfn_A.epsilon_a_subset("AO", "OCC") cache["eps_vir_A"] = wfn_A.epsilon_a_subset("AO", "VIR") cache["eps_occ_B"] = wfn_B.epsilon_a_subset("AO", "OCC") cache["eps_vir_B"] = wfn_B.epsilon_a_subset("AO", "VIR") # Build the densities as HF takes an extra "step" cache["D_A"] = core.doublet(cache["Cocc_A"], cache["Cocc_A"], False, True) cache["D_B"] = core.doublet(cache["Cocc_B"], cache["Cocc_B"], False, True) cache["P_A"] = core.doublet(cache["Cvir_A"], cache["Cvir_A"], False, True) cache["P_B"] = core.doublet(cache["Cvir_B"], cache["Cvir_B"], False, True) # Potential ints mints = core.MintsHelper(wfn_A.basisset()) cache["V_A"] = mints.ao_potential() # cache["V_A"].axpy(1.0, wfn_A.Va()) mints = core.MintsHelper(wfn_B.basisset()) cache["V_B"] = mints.ao_potential() # cache["V_B"].axpy(1.0, wfn_B.Va()) # Anything else we might need cache["S"] = wfn_A.S().clone() # J and K matrices jk.C_clear() # Normal J/K for Monomer A jk.C_left_add(wfn_A.Ca_subset("SO", "OCC")) jk.C_right_add(wfn_A.Ca_subset("SO", "OCC")) # Normal J/K for Monomer B jk.C_left_add(wfn_B.Ca_subset("SO", "OCC")) jk.C_right_add(wfn_B.Ca_subset("SO", "OCC")) # K_O J/K C_O_A = core.triplet(cache["D_B"], cache["S"], cache["Cocc_A"], False, False, False) jk.C_left_add(C_O_A) jk.C_right_add(cache["Cocc_A"]) jk.compute() # Clone them as the JK object will overwrite. cache["J_A"] = jk.J()[0].clone() cache["K_A"] = jk.K()[0].clone() cache["J_B"] = jk.J()[1].clone() cache["K_B"] = jk.K()[1].clone() cache["J_O"] = jk.J()[2].clone() cache["K_O"] = jk.K()[2].clone() cache["K_O"].transpose_this() monA_nr = wfn_A.molecule().nuclear_repulsion_energy() monB_nr = wfn_B.molecule().nuclear_repulsion_energy() dimer_nr = wfn_A.molecule().extract_subsets([1, 2 ]).nuclear_repulsion_energy() cache["nuclear_repulsion_energy"] = dimer_nr - monA_nr - monB_nr return cache
def _write_molden( self: core.Wavefunction, filename: Optional[str] = None, do_virtual: Optional[bool] = None, use_natural: bool = False, ): """Writes wavefunction information in *wfn* to *filename* in molden format. Will write natural orbitals from *density* (MO basis) if supplied. Warning! most post-SCF wavefunctions do not build the density as this is often much more costly than the energy. In addition, the wavefunction density attributes (Da and Db) return the SO density and must be transformed to the MO basis to use with this function. .. versionadded:: 0.5 *wfn* parameter passed explicitly :returns: None :type filename: :param filename: Destination file name for MOLDEN file. If unspecified (None), a file name will be generated from the molecule name. :type do_virtual: :param do_virtual: Do write all the MOs to the MOLDEN file (True) or discard the unoccupied MOs (False). Not valid for NO's. If unspecified (None), value taken from :term:`MOLDEN_WITH_VIRTUAL <MOLDEN_WITH_VIRTUAL (GLOBALS)>`. :type use_natural: :param use_natural: Write natural orbitals determined from density on wavefunction. :examples: 1. Molden file with the Kohn-Sham orbitals of a DFT calculation. >>> E, wfn = energy('b3lyp', return_wfn=True) >>> wfn.molden('mycalc.molden') 2. Molden file with the natural orbitals of a CCSD computation. For correlated methods, an energy call will not compute the density. "properties" or "gradient" must be called. >>> E, wfn = properties('ccsd', return_wfn=True) >>> wfn.molden('ccsd_no.molden', use_natural=True) 3. To supply a custom density matrix, manually set the Da and Db of the wavefunction. This is used, for example, to write natural orbitals coming from a root computed by a ``CIWavefunction`` computation, e.g., ``detci``, ``fci``, ``casscf``. The first two arguments of :py:meth:`~psi4.core.CIWavefunction.get_opdm` can be set to ``n, n`` where n => 0 selects the root to write out, provided these roots were computed, see :term:`NUM_ROOTS <NUM_ROOTS (DETCI)>`. The third argument controls the spin (``"A"``, ``"B"`` or ``"SUM"``) and the final boolean option determines whether inactive orbitals are included. >>> E, wfn = energy('detci', return_wfn=True) >>> wfn.Da() = wfn.get_opdm(0, 0, "A", True) >>> wfn.Db() = wfn.get_opdm(0, 0, "B", True) >>> molden(wfn, 'no_root1.molden', use_natural=True) """ if filename is None: filename = core.get_writer_file_prefix( self.molecule().name()) + ".molden" if do_virtual is None: do_virtual = bool(core.get_option("SCF", "MOLDEN_WITH_VIRTUAL")) basisset = self.basisset() mol = self.molecule() # Header and geometry (Atom, Atom #, Z, x, y, z) mol_string = '[Molden Format]\n[Atoms] (AU)\n' for atom in range(mol.natom()): mol_string += f"{mol.symbol(atom):2s} {atom+1:2d} {int(mol.Z(atom)):3d} {mol.x(atom):20.10f} {mol.y(atom):20.10f} {mol.z(atom):20.10f}\n" # Dump basis set mol_string += '[GTO]\n' for atom in range(mol.natom()): mol_string += f" {atom+1:d} 0\n" for rel_shell_idx in range(basisset.nshell_on_center(atom)): abs_shell_idx = basisset.shell_on_center(atom, rel_shell_idx) shell = basisset.shell(abs_shell_idx) mol_string += f" {shell.amchar:s}{shell.nprimitive:5d} 1.00\n" for prim in range(shell.nprimitive): mol_string += f"{shell.exp(prim):20.10f} {shell.original_coef(prim):20.10f}\n" mol_string += '\n' # if use_natural: # Alphas nmopi = self.nmopi() #MO_Da = core.Matrix("MO Alpha Density Matrix", nmopi, nmopi) #MO_Da.transform(self.Da(), self.Ca().transpose()) MO_Da = self.Da_subset("MO") #MO_Da.transform(self.Da(), self.Ca()) NO_Ra = core.Matrix("NO Alpha Rotation Matrix", nmopi, nmopi) occupation_a = core.Vector(nmopi) MO_Da.diagonalize(NO_Ra, occupation_a, core.DiagonalizeOrder.Descending) Ca = core.doublet(self.Ca(), NO_Ra, False, False) epsilon_a = occupation_a # Betas #MO_Db = core.Matrix("MO Beta Density Matrix", nmopi, nmopi) #MO_Db.transform(self.Db(), self.Cb().transpose()) MO_Db = self.Db_subset("MO") NO_Rb = core.Matrix("NO Beta Rotation Matrix", nmopi, nmopi) occupation_b = core.Vector(nmopi) MO_Db.diagonalize(NO_Rb, occupation_b, core.DiagonalizeOrder.Descending) Cb = core.doublet(self.Cb(), NO_Rb, False, False) epsilon_b = occupation_b else: Ca = self.Ca() Cb = self.Cb() occupation_a = self.occupation_a() occupation_b = self.occupation_b() epsilon_a = self.epsilon_a() epsilon_b = self.epsilon_b() # Convert C matrices to AO MO basis. Ca_subset costs information about which symmetry an orbital originally had, which is why we can't use it. aotoso = self.aotoso() Ca_ao_mo = core.doublet(aotoso, Ca, False, False).nph Cb_ao_mo = core.doublet(aotoso, Cb, False, False).nph ao_overlap = self.mintshelper().ao_overlap().np # Convert from Psi4 internal normalization to the unit normalization expected by Molden ao_normalizer = ao_overlap.diagonal()**(-1 / 2) Ca_ao_mo = core.Matrix.from_array([(i.T / ao_normalizer).T for i in Ca_ao_mo]) Cb_ao_mo = core.Matrix.from_array([(i.T / ao_normalizer).T for i in Cb_ao_mo]) # Reorder AO x MO matrix to fit Molden conventions ''' Reordering expected by Molden P: x, y, z 5D: D 0, D+1, D-1, D+2, D-2 6D: xx, yy, zz, xy, xz, yz 7F: F 0, F+1, F-1, F+2, F-2, F+3, F-3 10F: xxx, yyy, zzz, xyy, xxy, xxz, xzz, yzz, yyz, xyz 9G: G 0, G+1, G-1, G+2, G-2, G+3, G-3, G+4, G-4 15G: xxxx, yyyy, zzzz, xxxy, xxxz, yyyz, zzzx, zzzy, xxyy, xxzz, yyzz, xxyz, yyxz, zzxy Molden does not handle angular momenta higher than G ''' molden_cartesian_order = [ [2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # p [0, 3, 4, 1, 5, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0], # d [0, 4, 5, 3, 9, 6, 1, 8, 7, 2, 0, 0, 0, 0, 0], # f [0, 3, 4, 9, 12, 10, 5, 13, 14, 7, 1, 6, 11, 8, 2] # g ] nirrep = self.nirrep() count = 0 # Keeps track of count for reordering temp_a = Ca_ao_mo.clone() # Placeholders for original AO x MO matrices temp_b = Cb_ao_mo.clone() for i in range(basisset.nshell()): am = basisset.shell(i).am if (am == 1 and basisset.has_puream()) or ( am > 1 and am < 5 and basisset.shell(i).is_cartesian()): for j in range(basisset.shell(i).nfunction): for h in range(nirrep): for k in range(Ca_ao_mo.coldim()[h]): Ca_ao_mo.set(h, count + molden_cartesian_order[am - 1][j], k, temp_a.get(h, count + j, k)) Cb_ao_mo.set(h, count + molden_cartesian_order[am - 1][j], k, temp_b.get(h, count + j, k)) count += basisset.shell(i).nfunction # Dump MO information if basisset.has_puream(): # For historical reasons, D and F can go on the same line, but setting D without F implicitly sets F. G must be on its own. mol_string += '[5D7F]\n[9G]\n\n' ct = mol.point_group().char_table() mol_string += '[MO]\n' mo_dim = self.nmopi() if do_virtual else (self.doccpi() + self.soccpi()) # Alphas. If Alphas and Betas are the same, then only Alphas with double occupation will be written (see line marked "***") mos = [] for h in range(nirrep): for n in range(mo_dim[h]): mos.append((epsilon_a.get(h, n), (h, n))) # Sort mos based on energy def mosSort(element): return element[0] mos.sort(key=mosSort) for i in range(len(mos)): h, n = mos[i][1] mol_string += f" Sym= {ct.gamma(h).symbol():s}\n Ene= {epsilon_a.get(h, n):24.10e}\n Spin= Alpha\n" if self.same_a_b_orbs() and self.epsilon_a() == self.epsilon_b( ) and self.same_a_b_dens(): mol_string += f" Occup= {occupation_a.get(h, n) + occupation_b.get(h, n):24.10e}\n" else: mol_string += f" Occup= {occupation_a.get(h, n):24.10e}\n" for so in range(self.nso()): mol_string += f"{so+1:3d} {Ca_ao_mo.get(h, so, n):24.10e}\n" # Betas mos = [] if not self.same_a_b_orbs( ) or self.epsilon_a() != self.epsilon_b() or not self.same_a_b_dens(): for h in range(nirrep): for n in range(mo_dim[h]): mos.append((self.epsilon_b().get(h, n), (h, n))) mos.sort(key=mosSort) for i in range(len(mos)): h, n = mos[i][1] mol_string += f" Sym= {ct.gamma(h).symbol():s}\n Ene= {epsilon_b.get(h, n):24.10e}\n Spin= Beta\n " \ f"Occup= {occupation_b.get(h, n):24.10e}\n" for so in range(self.nso()): mol_string += f"{so+1:3d} {Cb_ao_mo.get(h, so, n):24.10e}\n" # Write Molden string to file with open(filename, 'w') as fn: fn.write(mol_string)
def _core_doublet(A, B, transA, transB): warnings.warn( "Using `psi4.core.Matrix.doublet` instead of `psi4.core.doublet` is deprecated, and in 1.4 it will stop working\n", category=FutureWarning, stacklevel=2) return core.doublet(A, B, transA, transB)
def mcscf_solver(ref_wfn): # Build CIWavefunction core.prepare_options_for_module("DETCI") ciwfn = core.CIWavefunction(ref_wfn) ciwfn.set_module("detci") # Hush a lot of CI output ciwfn.set_print(0) # Begin with a normal two-step step_type = 'Initial CI' total_step = core.Matrix("Total step", ciwfn.get_dimension('OA'), ciwfn.get_dimension('AV')) start_orbs = ciwfn.get_orbitals("ROT").clone() ciwfn.set_orbitals("ROT", start_orbs) # Grab da options mcscf_orb_grad_conv = core.get_option("DETCI", "MCSCF_R_CONVERGENCE") mcscf_e_conv = core.get_option("DETCI", "MCSCF_E_CONVERGENCE") mcscf_max_macroiteration = core.get_option("DETCI", "MCSCF_MAXITER") mcscf_type = core.get_option("DETCI", "MCSCF_TYPE") mcscf_d_file = core.get_option("DETCI", "CI_FILE_START") + 3 mcscf_nroots = core.get_option("DETCI", "NUM_ROOTS") mcscf_wavefunction_type = core.get_option("DETCI", "WFN") mcscf_ndet = ciwfn.ndet() mcscf_nuclear_energy = ciwfn.molecule().nuclear_repulsion_energy() mcscf_steplimit = core.get_option("DETCI", "MCSCF_MAX_ROT") mcscf_rotate = core.get_option("DETCI", "MCSCF_ROTATE") # DIIS info mcscf_diis_start = core.get_option("DETCI", "MCSCF_DIIS_START") mcscf_diis_freq = core.get_option("DETCI", "MCSCF_DIIS_FREQ") mcscf_diis_error_type = core.get_option("DETCI", "MCSCF_DIIS_ERROR_TYPE") mcscf_diis_max_vecs = core.get_option("DETCI", "MCSCF_DIIS_MAX_VECS") # One-step info mcscf_target_conv_type = core.get_option("DETCI", "MCSCF_ALGORITHM") mcscf_so_start_grad = core.get_option("DETCI", "MCSCF_SO_START_GRAD") mcscf_so_start_e = core.get_option("DETCI", "MCSCF_SO_START_E") mcscf_current_step_type = 'Initial CI' # Start with SCF energy and other params scf_energy = ciwfn.variable("HF TOTAL ENERGY") eold = scf_energy norb_iter = 1 converged = False ah_step = False qc_step = False approx_integrals_only = True # Fake info to start with the initial diagonalization ediff = 1.e-4 orb_grad_rms = 1.e-3 # Grab needed objects diis_obj = solvers.DIIS(mcscf_diis_max_vecs) mcscf_obj = ciwfn.mcscf_object() # Execute the rotate command for rot in mcscf_rotate: if len(rot) != 4: raise p4util.PsiException( "Each element of the MCSCF rotate command requires 4 arguements (irrep, orb1, orb2, theta)." ) irrep, orb1, orb2, theta = rot if irrep > ciwfn.Ca().nirrep(): raise p4util.PsiException( "MCSCF_ROTATE: Expression %s irrep number is larger than the number of irreps" % (str(rot))) if max(orb1, orb2) > ciwfn.Ca().coldim()[irrep]: raise p4util.PsiException( "MCSCF_ROTATE: Expression %s orbital number exceeds number of orbitals in irrep" % (str(rot))) theta = np.deg2rad(theta) x = ciwfn.Ca().nph[irrep][:, orb1].copy() y = ciwfn.Ca().nph[irrep][:, orb2].copy() xp = np.cos(theta) * x - np.sin(theta) * y yp = np.sin(theta) * x + np.cos(theta) * y ciwfn.Ca().nph[irrep][:, orb1] = xp ciwfn.Ca().nph[irrep][:, orb2] = yp # Limited RAS functionality if core.get_local_option( "DETCI", "WFN") == "RASSCF" and mcscf_target_conv_type != "TS": core.print_out( "\n Warning! Only the TS algorithm for RASSCF wavefunction is currently supported.\n" ) core.print_out(" Switching to the TS algorithm.\n\n") mcscf_target_conv_type = "TS" # Print out headers if mcscf_type == "CONV": mtype = " @MCSCF" core.print_out("\n ==> Starting MCSCF iterations <==\n\n") core.print_out( " Iter Total Energy Delta E Orb RMS CI RMS NCI NORB\n" ) elif mcscf_type == "DF": mtype = " @DF-MCSCF" core.print_out("\n ==> Starting DF-MCSCF iterations <==\n\n") core.print_out( " Iter Total Energy Delta E Orb RMS CI RMS NCI NORB\n" ) else: mtype = " @AO-MCSCF" core.print_out("\n ==> Starting AO-MCSCF iterations <==\n\n") core.print_out( " Iter Total Energy Delta E Orb RMS CI RMS NCI NORB\n" ) # Iterate ! for mcscf_iter in range(1, mcscf_max_macroiteration + 1): # Transform integrals, diagonalize H ciwfn.transform_mcscf_integrals(approx_integrals_only) nci_iter = ciwfn.diag_h(abs(ediff) * 1.e-2, orb_grad_rms * 1.e-3) # After the first diag we need to switch to READ ciwfn.set_ci_guess("DFILE") ciwfn.form_opdm() ciwfn.form_tpdm() ci_grad_rms = ciwfn.variable("DETCI AVG DVEC NORM") # Update MCSCF object Cocc = ciwfn.get_orbitals("DOCC") Cact = ciwfn.get_orbitals("ACT") Cvir = ciwfn.get_orbitals("VIR") opdm = ciwfn.get_opdm(-1, -1, "SUM", False) tpdm = ciwfn.get_tpdm("SUM", True) mcscf_obj.update(Cocc, Cact, Cvir, opdm, tpdm) current_energy = ciwfn.variable("MCSCF TOTAL ENERGY") ciwfn.reset_ci_H0block() orb_grad_rms = mcscf_obj.gradient_rms() ediff = current_energy - eold # Print iterations print_iteration(mtype, mcscf_iter, current_energy, ediff, orb_grad_rms, ci_grad_rms, nci_iter, norb_iter, mcscf_current_step_type) eold = current_energy if mcscf_current_step_type == 'Initial CI': mcscf_current_step_type = 'TS' # Check convergence if (orb_grad_rms < mcscf_orb_grad_conv) and (abs(ediff) < abs(mcscf_e_conv)) and\ (mcscf_iter > 3) and not qc_step: core.print_out("\n %s has converged!\n\n" % mtype) converged = True break # Which orbital convergence are we doing? if ah_step: converged, norb_iter, step = ah_iteration(mcscf_obj, print_micro=False) norb_iter += 1 if converged: mcscf_current_step_type = 'AH' else: core.print_out( " !Warning. Augmented Hessian did not converge. Taking an approx step.\n" ) step = mcscf_obj.approx_solve() mcscf_current_step_type = 'TS, AH failure' else: step = mcscf_obj.approx_solve() step_type = 'TS' maxstep = step.absmax() if maxstep > mcscf_steplimit: core.print_out( ' Warning! Maxstep = %4.2f, scaling to %4.2f\n' % (maxstep, mcscf_steplimit)) step.scale(mcscf_steplimit / maxstep) xstep = total_step.clone() total_step.add(step) # Do or add DIIS if (mcscf_iter >= mcscf_diis_start) and ("TS" in mcscf_current_step_type): # Figure out DIIS error vector if mcscf_diis_error_type == "GRAD": error = core.triplet(ciwfn.get_orbitals("OA"), mcscf_obj.gradient(), ciwfn.get_orbitals("AV"), False, False, True) else: error = step diis_obj.add(total_step, error) if not (mcscf_iter % mcscf_diis_freq): total_step = diis_obj.extrapolate() mcscf_current_step_type = 'TS, DIIS' # Build the rotation by continuous updates if mcscf_iter == 1: totalU = mcscf_obj.form_rotation_matrix(total_step) else: xstep.axpy(-1.0, total_step) xstep.scale(-1.0) Ustep = mcscf_obj.form_rotation_matrix(xstep) totalU = core.doublet(totalU, Ustep, False, False) # Build the rotation directly (not recommended) # orbs_mat = mcscf_obj.Ck(start_orbs, total_step) # Finally rotate and set orbitals orbs_mat = core.doublet(start_orbs, totalU, False, False) ciwfn.set_orbitals("ROT", orbs_mat) # Figure out what the next step should be if (orb_grad_rms < mcscf_so_start_grad) and (abs(ediff) < abs(mcscf_so_start_e)) and\ (mcscf_iter >= 2): if mcscf_target_conv_type == 'AH': approx_integrals_only = False ah_step = True elif mcscf_target_conv_type == 'OS': approx_integrals_only = False mcscf_current_step_type = 'OS, Prep' break else: continue #raise p4util.PsiException("") # If we converged do not do onestep if converged or (mcscf_target_conv_type != 'OS'): one_step_iters = [] # If we are not converged load in Dvec and build iters array else: one_step_iters = range(mcscf_iter + 1, mcscf_max_macroiteration + 1) dvec = ciwfn.D_vector() dvec.init_io_files(True) dvec.read(0, 0) dvec.symnormalize(1.0, 0) ci_grad = ciwfn.new_civector(1, mcscf_d_file + 1, True, True) ci_grad.set_nvec(1) ci_grad.init_io_files(True) # Loop for onestep for mcscf_iter in one_step_iters: # Transform integrals and update the MCSCF object ciwfn.transform_mcscf_integrals(ciwfn.H(), False) ciwfn.form_opdm() ciwfn.form_tpdm() # Update MCSCF object Cocc = ciwfn.get_orbitals("DOCC") Cact = ciwfn.get_orbitals("ACT") Cvir = ciwfn.get_orbitals("VIR") opdm = ciwfn.get_opdm(-1, -1, "SUM", False) tpdm = ciwfn.get_tpdm("SUM", True) mcscf_obj.update(Cocc, Cact, Cvir, opdm, tpdm) orb_grad_rms = mcscf_obj.gradient_rms() # Warning! Does not work for SA-MCSCF current_energy = mcscf_obj.current_total_energy() current_energy += mcscf_nuclear_energy ciwfn.set_variable("CI ROOT %d TOTAL ENERGY" % 1, current_energy) ciwfn.set_variable("CURRENT ENERGY", current_energy) ciwfn.set_energy(current_energy) docc_energy = mcscf_obj.current_docc_energy() ci_energy = mcscf_obj.current_ci_energy() # Compute CI gradient ciwfn.sigma(dvec, ci_grad, 0, 0) ci_grad.scale(2.0, 0) ci_grad.axpy(-2.0 * ci_energy, dvec, 0, 0) ci_grad_rms = ci_grad.norm(0) orb_grad_rms = mcscf_obj.gradient().rms() ediff = current_energy - eold print_iteration(mtype, mcscf_iter, current_energy, ediff, orb_grad_rms, ci_grad_rms, nci_iter, norb_iter, mcscf_current_step_type) mcscf_current_step_type = 'OS' eold = current_energy if (orb_grad_rms < mcscf_orb_grad_conv) and (abs(ediff) < abs(mcscf_e_conv)): core.print_out("\n %s has converged!\n\n" % mtype) converged = True break # Take a step converged, norb_iter, nci_iter, step = qc_iteration( dvec, ci_grad, ciwfn, mcscf_obj) # Rotate integrals to new frame total_step.add(step) orbs_mat = mcscf_obj.Ck(ciwfn.get_orbitals("ROT"), step) ciwfn.set_orbitals("ROT", orbs_mat) core.print_out(mtype + " Final Energy: %20.15f\n" % current_energy) # Die if we did not converge if (not converged): if core.get_global_option("DIE_IF_NOT_CONVERGED"): raise p4util.PsiException("MCSCF: Iterations did not converge!") else: core.print_out("\nWarning! MCSCF iterations did not converge!\n\n") # Print out CI vector information if mcscf_target_conv_type == 'OS': dvec.close_io_files() ci_grad.close_io_files() # For orbital invariant methods we transform the orbitals to the natural or # semicanonical basis. Frozen doubly occupied and virtual orbitals are not # modified. if core.get_option("DETCI", "WFN") == "CASSCF": # Do we diagonalize the opdm? if core.get_option("DETCI", "NAT_ORBS"): ciwfn.ci_nat_orbs() else: ciwfn.semicanonical_orbs() # Retransform intragrals and update CI coeffs., OPDM, and TPDM ciwfn.transform_mcscf_integrals(approx_integrals_only) ciwfn.set_print(1) ciwfn.set_ci_guess("H0_BLOCK") nci_iter = ciwfn.diag_h(mcscf_e_conv, mcscf_e_conv**0.5) ciwfn.form_opdm() ciwfn.form_tpdm() proc_util.print_ci_results(ciwfn, "MCSCF", scf_energy, current_energy, print_opdm_no=True) # Set final energy ciwfn.set_variable("CURRENT ENERGY", ciwfn.variable("MCSCF TOTAL ENERGY")) ciwfn.set_energy(ciwfn.variable("MCSCF TOTAL ENERGY")) # What do we need to cleanup? if core.get_option("DETCI", "MCSCF_CI_CLEANUP"): ciwfn.cleanup_ci() if core.get_option("DETCI", "MCSCF_DPD_CLEANUP"): ciwfn.cleanup_dpd() del diis_obj del mcscf_obj return ciwfn
def mcscf_solver(ref_wfn): # Build CIWavefunction core.prepare_options_for_module("DETCI") ciwfn = core.CIWavefunction(ref_wfn) # Hush a lot of CI output ciwfn.set_print(0) # Begin with a normal two-step step_type = 'Initial CI' total_step = core.Matrix("Total step", ciwfn.get_dimension('OA'), ciwfn.get_dimension('AV')) start_orbs = ciwfn.get_orbitals("ROT").clone() ciwfn.set_orbitals("ROT", start_orbs) # Grab da options mcscf_orb_grad_conv = core.get_option("DETCI", "MCSCF_R_CONVERGENCE") mcscf_e_conv = core.get_option("DETCI", "MCSCF_E_CONVERGENCE") mcscf_max_macroiteration = core.get_option("DETCI", "MCSCF_MAXITER") mcscf_type = core.get_option("DETCI", "MCSCF_TYPE") mcscf_d_file = core.get_option("DETCI", "CI_FILE_START") + 3 mcscf_nroots = core.get_option("DETCI", "NUM_ROOTS") mcscf_wavefunction_type = core.get_option("DETCI", "WFN") mcscf_ndet = ciwfn.ndet() mcscf_nuclear_energy = ciwfn.molecule().nuclear_repulsion_energy() mcscf_steplimit = core.get_option("DETCI", "MCSCF_MAX_ROT") mcscf_rotate = core.get_option("DETCI", "MCSCF_ROTATE") # DIIS info mcscf_diis_start = core.get_option("DETCI", "MCSCF_DIIS_START") mcscf_diis_freq = core.get_option("DETCI", "MCSCF_DIIS_FREQ") mcscf_diis_error_type = core.get_option("DETCI", "MCSCF_DIIS_ERROR_TYPE") mcscf_diis_max_vecs = core.get_option("DETCI", "MCSCF_DIIS_MAX_VECS") # One-step info mcscf_target_conv_type = core.get_option("DETCI", "MCSCF_ALGORITHM") mcscf_so_start_grad = core.get_option("DETCI", "MCSCF_SO_START_GRAD") mcscf_so_start_e = core.get_option("DETCI", "MCSCF_SO_START_E") mcscf_current_step_type = 'Initial CI' # Start with SCF energy and other params scf_energy = ciwfn.variable("HF TOTAL ENERGY") eold = scf_energy norb_iter = 1 converged = False ah_step = False qc_step = False approx_integrals_only = True # Fake info to start with the initial diagonalization ediff = 1.e-4 orb_grad_rms = 1.e-3 # Grab needed objects diis_obj = solvers.DIIS(mcscf_diis_max_vecs) mcscf_obj = ciwfn.mcscf_object() # Execute the rotate command for rot in mcscf_rotate: if len(rot) != 4: raise p4util.PsiException("Each element of the MCSCF rotate command requires 4 arguements (irrep, orb1, orb2, theta).") irrep, orb1, orb2, theta = rot if irrep > ciwfn.Ca().nirrep(): raise p4util.PsiException("MCSCF_ROTATE: Expression %s irrep number is larger than the number of irreps" % (str(rot))) if max(orb1, orb2) > ciwfn.Ca().coldim()[irrep]: raise p4util.PsiException("MCSCF_ROTATE: Expression %s orbital number exceeds number of orbitals in irrep" % (str(rot))) theta = np.deg2rad(theta) x = ciwfn.Ca().nph[irrep][:, orb1].copy() y = ciwfn.Ca().nph[irrep][:, orb2].copy() xp = np.cos(theta) * x - np.sin(theta) * y yp = np.sin(theta) * x + np.cos(theta) * y ciwfn.Ca().nph[irrep][:, orb1] = xp ciwfn.Ca().nph[irrep][:, orb2] = yp # Limited RAS functionality if core.get_local_option("DETCI", "WFN") == "RASSCF" and mcscf_target_conv_type != "TS": core.print_out("\n Warning! Only the TS algorithm for RASSCF wavefunction is currently supported.\n") core.print_out(" Switching to the TS algorithm.\n\n") mcscf_target_conv_type = "TS" # Print out headers if mcscf_type == "CONV": mtype = " @MCSCF" core.print_out("\n ==> Starting MCSCF iterations <==\n\n") core.print_out(" Iter Total Energy Delta E Orb RMS CI RMS NCI NORB\n") elif mcscf_type == "DF": mtype = " @DF-MCSCF" core.print_out("\n ==> Starting DF-MCSCF iterations <==\n\n") core.print_out(" Iter Total Energy Delta E Orb RMS CI RMS NCI NORB\n") else: mtype = " @AO-MCSCF" core.print_out("\n ==> Starting AO-MCSCF iterations <==\n\n") core.print_out(" Iter Total Energy Delta E Orb RMS CI RMS NCI NORB\n") # Iterate ! for mcscf_iter in range(1, mcscf_max_macroiteration + 1): # Transform integrals, diagonalize H ciwfn.transform_mcscf_integrals(approx_integrals_only) nci_iter = ciwfn.diag_h(abs(ediff) * 1.e-2, orb_grad_rms * 1.e-3) # After the first diag we need to switch to READ ciwfn.set_ci_guess("DFILE") ciwfn.form_opdm() ciwfn.form_tpdm() ci_grad_rms = ciwfn.variable("DETCI AVG DVEC NORM") # Update MCSCF object Cocc = ciwfn.get_orbitals("DOCC") Cact = ciwfn.get_orbitals("ACT") Cvir = ciwfn.get_orbitals("VIR") opdm = ciwfn.get_opdm(-1, -1, "SUM", False) tpdm = ciwfn.get_tpdm("SUM", True) mcscf_obj.update(Cocc, Cact, Cvir, opdm, tpdm) current_energy = ciwfn.variable("MCSCF TOTAL ENERGY") orb_grad_rms = mcscf_obj.gradient_rms() ediff = current_energy - eold # Print iterations print_iteration(mtype, mcscf_iter, current_energy, ediff, orb_grad_rms, ci_grad_rms, nci_iter, norb_iter, mcscf_current_step_type) eold = current_energy if mcscf_current_step_type == 'Initial CI': mcscf_current_step_type = 'TS' # Check convergence if (orb_grad_rms < mcscf_orb_grad_conv) and (abs(ediff) < abs(mcscf_e_conv)) and\ (mcscf_iter > 3) and not qc_step: core.print_out("\n %s has converged!\n\n" % mtype); converged = True break # Which orbital convergence are we doing? if ah_step: converged, norb_iter, step = ah_iteration(mcscf_obj, print_micro=False) norb_iter += 1 if converged: mcscf_current_step_type = 'AH' else: core.print_out(" !Warning. Augmented Hessian did not converge. Taking an approx step.\n") step = mcscf_obj.approx_solve() mcscf_current_step_type = 'TS, AH failure' else: step = mcscf_obj.approx_solve() step_type = 'TS' maxstep = step.absmax() if maxstep > mcscf_steplimit: core.print_out(' Warning! Maxstep = %4.2f, scaling to %4.2f\n' % (maxstep, mcscf_steplimit)) step.scale(mcscf_steplimit / maxstep) xstep = total_step.clone() total_step.add(step) # Do or add DIIS if (mcscf_iter >= mcscf_diis_start) and ("TS" in mcscf_current_step_type): # Figure out DIIS error vector if mcscf_diis_error_type == "GRAD": error = core.triplet(ciwfn.get_orbitals("OA"), mcscf_obj.gradient(), ciwfn.get_orbitals("AV"), False, False, True) else: error = step diis_obj.add(total_step, error) if not (mcscf_iter % mcscf_diis_freq): total_step = diis_obj.extrapolate() mcscf_current_step_type = 'TS, DIIS' # Build the rotation by continuous updates if mcscf_iter == 1: totalU = mcscf_obj.form_rotation_matrix(total_step) else: xstep.axpy(-1.0, total_step) xstep.scale(-1.0) Ustep = mcscf_obj.form_rotation_matrix(xstep) totalU = core.doublet(totalU, Ustep, False, False) # Build the rotation directly (not recommended) # orbs_mat = mcscf_obj.Ck(start_orbs, total_step) # Finally rotate and set orbitals orbs_mat = core.doublet(start_orbs, totalU, False, False) ciwfn.set_orbitals("ROT", orbs_mat) # Figure out what the next step should be if (orb_grad_rms < mcscf_so_start_grad) and (abs(ediff) < abs(mcscf_so_start_e)) and\ (mcscf_iter >= 2): if mcscf_target_conv_type == 'AH': approx_integrals_only = False ah_step = True elif mcscf_target_conv_type == 'OS': approx_integrals_only = False mcscf_current_step_type = 'OS, Prep' break else: continue #raise p4util.PsiException("") # If we converged do not do onestep if converged or (mcscf_target_conv_type != 'OS'): one_step_iters = [] # If we are not converged load in Dvec and build iters array else: one_step_iters = range(mcscf_iter + 1, mcscf_max_macroiteration + 1) dvec = ciwfn.D_vector() dvec.init_io_files(True) dvec.read(0, 0) dvec.symnormalize(1.0, 0) ci_grad = ciwfn.new_civector(1, mcscf_d_file + 1, True, True) ci_grad.set_nvec(1) ci_grad.init_io_files(True) # Loop for onestep for mcscf_iter in one_step_iters: # Transform integrals and update the MCSCF object ciwfn.transform_mcscf_integrals(ciwfn.H(), False) ciwfn.form_opdm() ciwfn.form_tpdm() # Update MCSCF object Cocc = ciwfn.get_orbitals("DOCC") Cact = ciwfn.get_orbitals("ACT") Cvir = ciwfn.get_orbitals("VIR") opdm = ciwfn.get_opdm(-1, -1, "SUM", False) tpdm = ciwfn.get_tpdm("SUM", True) mcscf_obj.update(Cocc, Cact, Cvir, opdm, tpdm) orb_grad_rms = mcscf_obj.gradient_rms() # Warning! Does not work for SA-MCSCF current_energy = mcscf_obj.current_total_energy() current_energy += mcscf_nuclear_energy ciwfn.set_variable("CI ROOT %d TOTAL ENERGY" % 1, current_energy) ciwfn.set_variable("CURRENT ENERGY", current_energy) ciwfn.set_energy(current_energy) docc_energy = mcscf_obj.current_docc_energy() ci_energy = mcscf_obj.current_ci_energy() # Compute CI gradient ciwfn.sigma(dvec, ci_grad, 0, 0) ci_grad.scale(2.0, 0) ci_grad.axpy(-2.0 * ci_energy, dvec, 0, 0) ci_grad_rms = ci_grad.norm(0) orb_grad_rms = mcscf_obj.gradient().rms() ediff = current_energy - eold print_iteration(mtype, mcscf_iter, current_energy, ediff, orb_grad_rms, ci_grad_rms, nci_iter, norb_iter, mcscf_current_step_type) mcscf_current_step_type = 'OS' eold = current_energy if (orb_grad_rms < mcscf_orb_grad_conv) and (abs(ediff) < abs(mcscf_e_conv)): core.print_out("\n %s has converged!\n\n" % mtype); converged = True break # Take a step converged, norb_iter, nci_iter, step = qc_iteration(dvec, ci_grad, ciwfn, mcscf_obj) # Rotate integrals to new frame total_step.add(step) orbs_mat = mcscf_obj.Ck(ciwfn.get_orbitals("ROT"), step) ciwfn.set_orbitals("ROT", orbs_mat) core.print_out(mtype + " Final Energy: %20.15f\n" % current_energy) # Die if we did not converge if (not converged): if core.get_global_option("DIE_IF_NOT_CONVERGED"): raise p4util.PsiException("MCSCF: Iterations did not converge!") else: core.print_out("\nWarning! MCSCF iterations did not converge!\n\n") # Print out CI vector information if mcscf_target_conv_type == 'OS': dvec.close_io_files() ci_grad.close_io_files() # For orbital invariant methods we transform the orbitals to the natural or # semicanonical basis. Frozen doubly occupied and virtual orbitals are not # modified. if core.get_option("DETCI", "WFN") == "CASSCF": # Do we diagonalize the opdm? if core.get_option("DETCI", "NAT_ORBS"): ciwfn.ci_nat_orbs() else: ciwfn.semicanonical_orbs() # Retransform intragrals and update CI coeffs., OPDM, and TPDM ciwfn.transform_mcscf_integrals(approx_integrals_only) nci_iter = ciwfn.diag_h(abs(ediff) * 1.e-2, orb_grad_rms * 1.e-3) ciwfn.set_ci_guess("DFILE") ciwfn.form_opdm() ciwfn.form_tpdm() proc_util.print_ci_results(ciwfn, "MCSCF", scf_energy, current_energy, print_opdm_no=True) # Set final energy ciwfn.set_variable("CURRENT ENERGY", ciwfn.variable("MCSCF TOTAL ENERGY")) ciwfn.set_energy(ciwfn.variable("MCSCF TOTAL ENERGY")) # What do we need to cleanup? if core.get_option("DETCI", "MCSCF_CI_CLEANUP"): ciwfn.cleanup_ci() if core.get_option("DETCI", "MCSCF_DPD_CLEANUP"): ciwfn.cleanup_dpd() del diis_obj del mcscf_obj return ciwfn