def test_explicit_rhf_outside_solver_off_diagonal_blocks(): mol = molecules.molecule_water_sto3g() mol.build() mf = pyscf.scf.RHF(mol) mf.kernel() mocoeffs = mf.mo_coeff moenergies = mf.mo_energy ao2mo = AO2MOpyscf(mocoeffs, mol.verbose, mol) ao2mo.perform_rhf_full() tei_mo = ao2mo.tei_mo[0] C = mocoeffs E = np.diag(moenergies) occupations = utils.occupations_from_pyscf_mol(mol, mocoeffs) nocc, nvirt, _, _ = occupations A = eqns.form_rpa_a_matrix_mo_singlet_full(E, tei_mo, nocc) B = eqns.form_rpa_b_matrix_mo_singlet_full(tei_mo, nocc) G = np.block([[A, B], [B, A]]) assert G.shape == (2 * nocc * nvirt, 2 * nocc * nvirt) G_inv = np.linalg.inv(G) components = 3 integrals_dipole_ao = mol.intor("cint1e_r_sph", comp=components) integrals_dipole_mo_ai = [] for component in range(components): integrals_dipole_mo_ai_component = np.dot( C[:, nocc:].T, np.dot(integrals_dipole_ao[component, ...], C[:, :nocc])).reshape(-1, order="F") integrals_dipole_mo_ai.append(integrals_dipole_mo_ai_component) integrals_dipole_mo_ai = np.stack(integrals_dipole_mo_ai, axis=0).T integrals_dipole_mo_ai_super = np.concatenate( (integrals_dipole_mo_ai, -integrals_dipole_mo_ai), axis=0) rhsvecs = integrals_dipole_mo_ai_super rspvecs = np.dot(G_inv, rhsvecs) polarizability = 4 * np.dot(rhsvecs.T, rspvecs) / 2 # pylint: disable=bad-whitespace result__0_00 = np.array([[7.93556221, 0.0, 0.0], [0.0, 3.06821077, 0.0], [0.0, 0.0, 0.05038621]]) atol = 1.0e-8 rtol = 0.0 np.testing.assert_allclose(polarizability, result__0_00, rtol=rtol, atol=atol)
def form_explicit_hessian(self, hamiltonian: Hamiltonian, spin: Spin, frequency: float) -> None: assert hasattr(self, "tei_mo") assert self.tei_mo is not None assert len(self.tei_mo) in (1, 2, 4, 6) assert isinstance(self.tei_mo_type, AO2MOTransformationType) assert hamiltonian == Hamiltonian.TDA assert isinstance(spin, Spin) assert isinstance(frequency, (float, type(None))) nocc_alph, nvirt_alph, nocc_beta, nvirt_beta = self.occupations nov_alph = nocc_alph * nvirt_alph nov_beta = nocc_beta * nvirt_beta if not self.is_uhf: # Set up "function pointers". if self.tei_mo_type == AO2MOTransformationType.full: assert len(self.tei_mo) == 1 tei_mo = self.tei_mo[0] elif self.tei_mo_type == AO2MOTransformationType.partial: assert len(self.tei_mo) == 2 tei_mo_ovov = self.tei_mo[0] tei_mo_oovv = self.tei_mo[1] if self.tei_mo_type == AO2MOTransformationType.full: if spin == Spin.singlet: A = form_rpa_a_matrix_mo_singlet_full(self.moenergies[0], tei_mo, nocc_alph) elif spin == Spin.triplet: A = form_rpa_a_matrix_mo_triplet_full(self.moenergies[0], tei_mo, nocc_alph) elif self.tei_mo_type == AO2MOTransformationType.partial: if spin == Spin.singlet: A = form_rpa_a_matrix_mo_singlet_partial( self.moenergies[0], tei_mo_ovov, tei_mo_oovv ) elif spin == Spin.triplet: A = form_rpa_a_matrix_mo_triplet_partial(self.moenergies[0], tei_mo_oovv) self.explicit_hessian = A else: # TODO UHF pass
def test_explicit_uhf_from_rhf_outside_solver(): mol = molecules.molecule_water_sto3g() mol.build() mf = pyscf.scf.RHF(mol) mf.kernel() mocoeffs = mf.mo_coeff moenergies = mf.mo_energy ao2mo = AO2MOpyscf(mocoeffs, mol.verbose, mol) ao2mo.perform_rhf_full() tei_mo = ao2mo.tei_mo[0] C_a = mocoeffs C_b = C_a.copy() E_a = np.diag(moenergies) # E_b = E_a.copy() occupations = utils.occupations_from_pyscf_mol(mol, mocoeffs) nocc_a, nvirt_a, nocc_b, nvirt_b = occupations # Same-spin and opposite-spin contributions should add together # properly for restricted wavefunction. A_s = eqns.form_rpa_a_matrix_mo_singlet_full(E_a, tei_mo, nocc_a) A_s_ss = eqns.form_rpa_a_matrix_mo_singlet_ss_full(E_a, tei_mo, nocc_a) A_s_os = eqns.form_rpa_a_matrix_mo_singlet_os_full(tei_mo, nocc_a, nocc_b) np.testing.assert_allclose(A_s, A_s_ss + A_s_os) B_s = eqns.form_rpa_b_matrix_mo_singlet_full(tei_mo, nocc_a) B_s_ss = eqns.form_rpa_b_matrix_mo_singlet_ss_full(tei_mo, nocc_a) B_s_os = eqns.form_rpa_b_matrix_mo_singlet_os_full(tei_mo, nocc_a, nocc_b) np.testing.assert_allclose(B_s, B_s_ss + B_s_os) # Since the "triplet" part contains no Coulomb contribution, and # (xx|yy) is only in the Coulomb part, there is no ss/os # separation for the triplet part. G_r = np.block([[A_s, B_s], [B_s, A_s]]) G_aa = np.block([[A_s_ss, B_s_ss], [B_s_ss, A_s_ss]]) G_bb = G_aa.copy() G_ab = np.block([[A_s_os, B_s_os], [B_s_os, A_s_os]]) G_ba = G_ab.copy() np.testing.assert_allclose(G_r, (G_aa + G_ab)) G_r_inv = np.linalg.inv(G_r) G_aa_inv = np.linalg.inv(G_aa) G_bb_inv = np.linalg.inv(G_bb) assert G_r_inv.shape == (2 * nocc_a * nvirt_a, 2 * nocc_a * nvirt_a) assert G_aa_inv.shape == (2 * nocc_a * nvirt_a, 2 * nocc_a * nvirt_a) assert G_bb_inv.shape == (2 * nocc_b * nvirt_b, 2 * nocc_b * nvirt_b) # Form the operator-independent part of the response vectors. left_alph = np.linalg.inv(G_aa - np.dot(G_ab, np.dot(G_bb_inv, G_ba))) left_beta = np.linalg.inv(G_bb - np.dot(G_ba, np.dot(G_aa_inv, G_ab))) integrals_dipole_ao = mol.intor("cint1e_r_sph", comp=3) integrals_dipole_mo_ai_r = [] integrals_dipole_mo_ai_a = [] integrals_dipole_mo_ai_b = [] for comp in range(3): integrals_dipole_mo_ai_comp_r = np.dot( C_a[:, nocc_a:].T, np.dot(integrals_dipole_ao[comp, ...], C_a[:, :nocc_a]) ) integrals_dipole_mo_ai_comp_a = np.dot( C_a[:, nocc_a:].T, np.dot(integrals_dipole_ao[comp, ...], C_a[:, :nocc_a]) ) integrals_dipole_mo_ai_comp_b = np.dot( C_b[:, nocc_b:].T, np.dot(integrals_dipole_ao[comp, ...], C_b[:, :nocc_b]) ) integrals_dipole_mo_ai_comp_r = np.reshape(integrals_dipole_mo_ai_comp_r, -1, order="F") integrals_dipole_mo_ai_comp_a = np.reshape(integrals_dipole_mo_ai_comp_a, -1, order="F") integrals_dipole_mo_ai_comp_b = np.reshape(integrals_dipole_mo_ai_comp_b, -1, order="F") integrals_dipole_mo_ai_r.append(integrals_dipole_mo_ai_comp_r) integrals_dipole_mo_ai_a.append(integrals_dipole_mo_ai_comp_a) integrals_dipole_mo_ai_b.append(integrals_dipole_mo_ai_comp_b) integrals_dipole_mo_ai_r = np.stack(integrals_dipole_mo_ai_r, axis=0).T integrals_dipole_mo_ai_a = np.stack(integrals_dipole_mo_ai_a, axis=0).T integrals_dipole_mo_ai_b = np.stack(integrals_dipole_mo_ai_b, axis=0).T integrals_dipole_mo_ai_r_super = np.concatenate( (integrals_dipole_mo_ai_r, -integrals_dipole_mo_ai_r), axis=0 ) integrals_dipole_mo_ai_a_super = np.concatenate( (integrals_dipole_mo_ai_a, -integrals_dipole_mo_ai_a), axis=0 ) integrals_dipole_mo_ai_b_super = np.concatenate( (integrals_dipole_mo_ai_b, -integrals_dipole_mo_ai_b), axis=0 ) # Form the operator-dependent part of the response vectors. right_alph = integrals_dipole_mo_ai_a_super - ( np.dot(G_ab, np.dot(G_bb_inv, integrals_dipole_mo_ai_b_super)) ) right_beta = integrals_dipole_mo_ai_b_super - ( np.dot(G_ba, np.dot(G_aa_inv, integrals_dipole_mo_ai_a_super)) ) rspvec_r = np.dot(G_r_inv, integrals_dipole_mo_ai_r_super) # The total response vector for each spin is the product of the # operator-independent (left) and operator-dependent (right) # parts. rspvec_a = np.dot(left_alph, right_alph) rspvec_b = np.dot(left_beta, right_beta) res_r = 4 * np.dot(integrals_dipole_mo_ai_r_super.T, rspvec_r) / 2 res_a = np.dot(integrals_dipole_mo_ai_a_super.T, rspvec_a) / 2 res_b = np.dot(integrals_dipole_mo_ai_b_super.T, rspvec_b) / 2 res_u = 2 * (res_a + res_b) atol = 1.0e-8 rtol = 0.0 np.testing.assert_allclose(res_u, res_r, rtol=rtol, atol=atol) print(res_r) print(res_u)
def form_explicit_hessian(self, hamiltonian: Hamiltonian, spin: Spin, frequency: float) -> None: assert self.tei_mo is not None assert len(self.tei_mo) in (1, 2, 4, 6) assert isinstance(self.tei_mo_type, AO2MOTransformationType) assert isinstance(hamiltonian, Hamiltonian) assert isinstance(spin, Spin) assert isinstance(frequency, (float, type(None))) nocc_alph, nvirt_alph, nocc_beta, nvirt_beta = self.occupations nov_alph = nocc_alph * nvirt_alph nov_beta = nocc_beta * nvirt_beta superoverlap_alph = ( np.block( [ [np.eye(nov_alph), np.zeros(shape=(nov_alph, nov_alph))], [np.zeros(shape=(nov_alph, nov_alph)), -np.eye(nov_alph)], ] ) * frequency ) if not self.is_uhf: # Set up "function pointers". if self.tei_mo_type == AO2MOTransformationType.full: assert len(self.tei_mo) == 1 tei_mo = self.tei_mo[0] elif self.tei_mo_type == AO2MOTransformationType.partial: assert len(self.tei_mo) == 2 tei_mo_ovov = self.tei_mo[0] tei_mo_oovv = self.tei_mo[1] if self.tei_mo_type == AO2MOTransformationType.full: if hamiltonian == Hamiltonian.RPA and spin == Spin.singlet: A = form_rpa_a_matrix_mo_singlet_full(self.moenergies[0], tei_mo, nocc_alph) B = form_rpa_b_matrix_mo_singlet_full(tei_mo, nocc_alph) elif hamiltonian == Hamiltonian.RPA and spin == Spin.triplet: A = form_rpa_a_matrix_mo_triplet_full(self.moenergies[0], tei_mo, nocc_alph) B = form_rpa_b_matrix_mo_triplet_full(tei_mo, nocc_alph) elif hamiltonian == Hamiltonian.TDA and spin == Spin.singlet: A = form_rpa_a_matrix_mo_singlet_full(self.moenergies[0], tei_mo, nocc_alph) B = np.zeros(shape=(nov_alph, nov_alph)) elif hamiltonian == Hamiltonian.TDA and spin == Spin.triplet: A = form_rpa_a_matrix_mo_triplet_full(self.moenergies[0], tei_mo, nocc_alph) B = np.zeros(shape=(nov_alph, nov_alph)) elif self.tei_mo_type == AO2MOTransformationType.partial: if hamiltonian == Hamiltonian.RPA and spin == Spin.singlet: A = form_rpa_a_matrix_mo_singlet_partial( self.moenergies[0], tei_mo_ovov, tei_mo_oovv ) B = form_rpa_b_matrix_mo_singlet_partial(tei_mo_ovov) elif hamiltonian == Hamiltonian.RPA and spin == Spin.triplet: A = form_rpa_a_matrix_mo_triplet_partial(self.moenergies[0], tei_mo_oovv) B = form_rpa_b_matrix_mo_triplet_partial(tei_mo_ovov) elif hamiltonian == Hamiltonian.TDA and spin == Spin.singlet: A = form_rpa_a_matrix_mo_singlet_partial( self.moenergies[0], tei_mo_ovov, tei_mo_oovv ) B = np.zeros(shape=(nov_alph, nov_alph)) elif hamiltonian == Hamiltonian.TDA and spin == Spin.triplet: A = form_rpa_a_matrix_mo_triplet_partial(self.moenergies[0], tei_mo_oovv) B = np.zeros(shape=(nov_alph, nov_alph)) G = np.block([[A, B], [B, A]]) self.explicit_hessian = G - superoverlap_alph else: # For UHF there are both "operator-dependent" and # operator-indepenent parts of the orbital Hessian because # the opposite-spin property gradient couples in. Here, # only form the 4 blocks of the "super-Hessian" (a # supermatrix of supermatrices); the equations will get # pieced together when it is time ot form the response # vectors. # Set up "function pointers". if self.tei_mo_type == AO2MOTransformationType.full: assert len(self.tei_mo) == 4 tei_mo_aaaa = self.tei_mo[0] tei_mo_aabb = self.tei_mo[1] tei_mo_bbaa = self.tei_mo[2] tei_mo_bbbb = self.tei_mo[3] elif self.tei_mo_type == AO2MOTransformationType.partial: assert len(self.tei_mo) == 6 tei_mo_ovov_aaaa = self.tei_mo[0] tei_mo_ovov_aabb = self.tei_mo[1] tei_mo_ovov_bbaa = self.tei_mo[2] tei_mo_ovov_bbbb = self.tei_mo[3] tei_mo_oovv_aaaa = self.tei_mo[4] tei_mo_oovv_bbbb = self.tei_mo[5] else: pass E_a = self.moenergies[0] E_b = self.moenergies[1] # TODO clean this up... if self.tei_mo_type == AO2MOTransformationType.full: if hamiltonian == Hamiltonian.RPA and spin == Spin.singlet: A_ss_a = form_rpa_a_matrix_mo_singlet_ss_full(E_a, tei_mo_aaaa, nocc_alph) A_os_a = form_rpa_a_matrix_mo_singlet_os_full(tei_mo_aabb, nocc_alph, nocc_beta) B_ss_a = form_rpa_b_matrix_mo_singlet_ss_full(tei_mo_aaaa, nocc_alph) B_os_a = form_rpa_b_matrix_mo_singlet_os_full(tei_mo_aabb, nocc_alph, nocc_beta) A_ss_b = form_rpa_a_matrix_mo_singlet_ss_full(E_b, tei_mo_bbbb, nocc_beta) A_os_b = form_rpa_a_matrix_mo_singlet_os_full(tei_mo_bbaa, nocc_beta, nocc_alph) B_ss_b = form_rpa_b_matrix_mo_singlet_ss_full(tei_mo_bbbb, nocc_beta) B_os_b = form_rpa_b_matrix_mo_singlet_os_full(tei_mo_bbaa, nocc_beta, nocc_alph) elif hamiltonian == Hamiltonian.RPA and spin == Spin.triplet: # Since the "triplet" part contains no Coulomb contribution, and # (xx|yy) is only in the Coulomb part, there is no ss/os # separation for the triplet part. zeros_ab = np.zeros(shape=(nov_alph, nov_beta)) zeros_ba = zeros_ab.T A_ss_a = form_rpa_a_matrix_mo_triplet_full(E_a, tei_mo_aaaa, nocc_alph) A_os_a = zeros_ab B_ss_a = form_rpa_b_matrix_mo_triplet_full(tei_mo_aaaa, nocc_alph) B_os_a = zeros_ab A_ss_b = form_rpa_a_matrix_mo_triplet_full(E_b, tei_mo_bbbb, nocc_beta) A_os_b = zeros_ba B_ss_b = form_rpa_b_matrix_mo_triplet_full(tei_mo_bbbb, nocc_beta) B_os_b = zeros_ba elif hamiltonian == Hamiltonian.TDA and spin == Spin.singlet: zeros_aa = np.zeros(shape=(nov_alph, nov_alph)) zeros_ab = np.zeros(shape=(nov_alph, nov_beta)) zeros_ba = zeros_ab.T zeros_bb = np.zeros(shape=(nov_beta, nov_beta)) A_ss_a = form_rpa_a_matrix_mo_singlet_ss_full(E_a, tei_mo_aaaa, nocc_alph) A_os_a = form_rpa_a_matrix_mo_singlet_os_full(tei_mo_aabb, nocc_alph, nocc_beta) B_ss_a = zeros_aa B_os_a = zeros_ab A_ss_b = form_rpa_a_matrix_mo_singlet_ss_full(E_b, tei_mo_bbbb, nocc_beta) A_os_b = form_rpa_a_matrix_mo_singlet_os_full(tei_mo_bbaa, nocc_beta, nocc_alph) B_ss_b = zeros_bb B_os_b = zeros_ba elif hamiltonian == Hamiltonian.TDA and spin == Spin.triplet: zeros_aa = np.zeros(shape=(nov_alph, nov_alph)) zeros_ab = np.zeros(shape=(nov_alph, nov_beta)) zeros_ba = zeros_ab.T zeros_bb = np.zeros(shape=(nov_beta, nov_beta)) A_ss_a = form_rpa_a_matrix_mo_triplet_full(E_a, tei_mo_aaaa, nocc_alph) A_os_a = zeros_ab B_ss_a = zeros_aa B_os_a = zeros_ab A_ss_b = form_rpa_a_matrix_mo_triplet_full(E_b, tei_mo_bbbb, nocc_beta) A_os_b = zeros_ba B_ss_b = zeros_bb B_os_b = zeros_ba elif self.tei_mo_type == AO2MOTransformationType.partial: if hamiltonian == Hamiltonian.RPA and spin == Spin.singlet: A_ss_a = form_rpa_a_matrix_mo_singlet_ss_partial( E_a, tei_mo_ovov_aaaa, tei_mo_oovv_aaaa ) A_os_a = form_rpa_a_matrix_mo_singlet_os_partial(tei_mo_ovov_aabb) B_ss_a = form_rpa_b_matrix_mo_singlet_ss_partial(tei_mo_ovov_aaaa) B_os_a = form_rpa_b_matrix_mo_singlet_os_partial(tei_mo_ovov_aabb) A_ss_b = form_rpa_a_matrix_mo_singlet_ss_partial( E_b, tei_mo_ovov_bbbb, tei_mo_oovv_bbbb ) A_os_b = form_rpa_a_matrix_mo_singlet_os_partial(tei_mo_ovov_bbaa) B_ss_b = form_rpa_b_matrix_mo_singlet_ss_partial(tei_mo_ovov_bbbb) B_os_b = form_rpa_b_matrix_mo_singlet_os_partial(tei_mo_ovov_bbaa) elif hamiltonian == Hamiltonian.RPA and spin == Spin.triplet: # Since the "triplet" part contains no Coulomb contribution, and # (xx|yy) is only in the Coulomb part, there is no ss/os # separation for the triplet part. zeros_ab = np.zeros(shape=(nov_alph, nov_beta)) zeros_ba = zeros_ab.T A_ss_a = form_rpa_a_matrix_mo_triplet_partial(E_a, tei_mo_oovv_aaaa) A_os_a = zeros_ab B_ss_a = form_rpa_b_matrix_mo_triplet_partial(tei_mo_ovov_aaaa) B_os_a = zeros_ab A_ss_b = form_rpa_a_matrix_mo_triplet_partial(E_b, tei_mo_oovv_bbbb) A_os_b = zeros_ba B_ss_b = form_rpa_b_matrix_mo_triplet_partial(tei_mo_ovov_bbbb) B_os_b = zeros_ba elif hamiltonian == Hamiltonian.TDA and spin == Spin.singlet: zeros_aa = np.zeros(shape=(nov_alph, nov_alph)) zeros_ab = np.zeros(shape=(nov_alph, nov_beta)) zeros_ba = zeros_ab.T zeros_bb = np.zeros(shape=(nov_beta, nov_beta)) A_ss_a = form_rpa_a_matrix_mo_singlet_ss_partial( E_a, tei_mo_ovov_aaaa, tei_mo_oovv_aaaa ) A_os_a = form_rpa_a_matrix_mo_singlet_os_partial(tei_mo_ovov_aabb) B_ss_a = zeros_aa B_os_a = zeros_ab A_ss_b = form_rpa_a_matrix_mo_singlet_ss_partial( E_b, tei_mo_ovov_bbbb, tei_mo_oovv_bbbb ) A_os_b = form_rpa_a_matrix_mo_singlet_os_partial(tei_mo_ovov_bbaa) B_ss_b = zeros_bb B_os_b = zeros_ba elif hamiltonian == Hamiltonian.TDA and spin == Spin.triplet: zeros_aa = np.zeros(shape=(nov_alph, nov_alph)) zeros_ab = np.zeros(shape=(nov_alph, nov_beta)) zeros_ba = zeros_ab.T zeros_bb = np.zeros(shape=(nov_beta, nov_beta)) A_ss_a = form_rpa_a_matrix_mo_triplet_partial(E_a, tei_mo_oovv_aaaa) A_os_a = zeros_ab B_ss_a = zeros_aa B_os_a = zeros_ab A_ss_b = form_rpa_a_matrix_mo_triplet_partial(E_b, tei_mo_oovv_bbbb) A_os_b = zeros_ba B_ss_b = zeros_bb B_os_b = zeros_ba superoverlap_beta = np.block( [ [np.eye(nov_beta), np.zeros(shape=(nov_beta, nov_beta))], [np.zeros(shape=(nov_beta, nov_beta)), -np.eye(nov_beta)], ] ) superoverlap_beta = superoverlap_beta * frequency G_aa = np.block([[A_ss_a, B_ss_a], [B_ss_a, A_ss_a]]) G_ab = np.block([[A_os_a, B_os_a], [B_os_a, A_os_a]]) G_ba = np.block([[A_os_b, B_os_b], [B_os_b, A_os_b]]) G_bb = np.block([[A_ss_b, B_ss_b], [B_ss_b, A_ss_b]]) self.explicit_hessian = ( G_aa - superoverlap_alph, G_ab, G_ba, G_bb - superoverlap_beta, )
def form_explicit_hessian(self, hamiltonian=None, spin=None, frequency=None): assert hasattr(self, "tei_mo") assert self.tei_mo is not None assert len(self.tei_mo) in (1, 2, 4, 6) assert self.tei_mo_type in ("full", "partial") if not hamiltonian: hamiltonian = self.hamiltonian if not spin: spin = self.spin assert isinstance(hamiltonian, str) assert isinstance(spin, str) hamiltonian = hamiltonian.lower() spin = spin.lower() assert hamiltonian == "tda" assert spin in ("singlet", "triplet") self.hamiltonian = hamiltonian self.spin = spin nocc_alph, nvirt_alph, nocc_beta, nvirt_beta = self.occupations nov_alph = nocc_alph * nvirt_alph nov_beta = nocc_beta * nvirt_beta if not self.is_uhf: # Set up "function pointers". if self.tei_mo_type == "full": assert len(self.tei_mo) == 1 tei_mo = self.tei_mo[0] elif self.tei_mo_type == "partial": assert len(self.tei_mo) == 2 tei_mo_ovov = self.tei_mo[0] tei_mo_oovv = self.tei_mo[1] if self.tei_mo_type == "full": if spin == "singlet": A = form_rpa_a_matrix_mo_singlet_full( self.moenergies[0], tei_mo, nocc_alph) elif spin == "triplet": A = form_rpa_a_matrix_mo_triplet_full( self.moenergies[0], tei_mo, nocc_alph) elif self.tei_mo_type == "partial": if spin == "singlet": A = form_rpa_a_matrix_mo_singlet_partial( self.moenergies[0], tei_mo_ovov, tei_mo_oovv) elif spin == "triplet": A = form_rpa_a_matrix_mo_triplet_partial( self.moenergies[0], tei_mo_oovv) self.explicit_hessian = A else: # TODO UHF pass