def form_rpa_a_matrix_mo_singlet_ss_full(E_MO: np.ndarray, TEI_MO: np.ndarray, nocc: int) -> np.ndarray: r"""Form the same-spin part of the A (CIS) matrix in the MO basis. [singlet] The equation for element :math:`\{ia,jb\}` is :math:`????`. """ norb = E_MO.shape[0] nvirt = norb - nocc nov = nocc * nvirt ediff = form_vec_energy_differences( np.diag(E_MO)[:nocc], np.diag(E_MO)[nocc:]) A = np.empty(shape=(nov, nov)) for i in range(nocc): for a in range(nvirt): ia = i * nvirt + a for j in range(nocc): for b in range(nvirt): jb = j * nvirt + b A[ia, jb] = TEI_MO[a + nocc, i, j, b + nocc] - TEI_MO[a + nocc, b + nocc, j, i] A += np.diag(ediff) return A
def form_rpa_a_matrix_mo_singlet_full(E_MO: np.ndarray, TEI_MO: np.ndarray, nocc: int) -> np.ndarray: r"""Form the A (CIS) matrix in the MO basis. [singlet] The equation for element :math:`\{ia,jb\}` is :math:`\left<aj||ib\right> = \left<aj|ib\right> - \left<aj|bi\right> = [ai|jb] - [ab|ji] = 2(ai|jb) - (ab|ji)`. It also includes the virt-occ energy difference on the diagonal. """ norb = E_MO.shape[0] nvirt = norb - nocc nov = nocc * nvirt ediff = form_vec_energy_differences( np.diag(E_MO)[:nocc], np.diag(E_MO)[nocc:]) A = np.empty(shape=(nov, nov)) for i in range(nocc): for a in range(nvirt): ia = i * nvirt + a for j in range(nocc): for b in range(nvirt): jb = j * nvirt + b A[ia, jb] = (2 * TEI_MO[a + nocc, i, j, b + nocc] - TEI_MO[a + nocc, b + nocc, j, i]) A += np.diag(ediff) return A
def print_results_nwchem(self) -> str: # TODO UHF nocc_tot, nvirt_tot, _, _ = self.solver.occupations moene = np.diag(self.solver.moenergies[0]) moene_occ = moene[:nocc_tot] moene_virt = moene[nocc_tot:] ediff = form_vec_energy_differences(moene_occ, moene_virt) * HARTREE_TO_EV idxsort = np.argsort(ediff) ediff_sorted = ediff[idxsort] indices_unrestricted_orbwin = form_indices_orbwin(nocc_tot, nvirt_tot) indices_sorted = [indices_unrestricted_orbwin[i] for i in idxsort] ndiff = 10 lines = [] lines.append(f" {ndiff:>2d} smallest eigenvalue differences (eV) ") lines.append( "--------------------------------------------------------") lines.append( " No. Spin Occ Vir Irrep E(Occ) E(Vir) E(Diff)") lines.append( "--------------------------------------------------------") for idx in range(ndiff): iocc, ivirt = indices_sorted[idx] lines.append( f"{idx + 1:>5d}{1:>5d}{iocc + 1:>5d}{ivirt + 1:>5d} " f'{"X":<5}{moene[iocc]:>10.3f}{moene[ivirt]:>10.3f}{ediff_sorted[idx]:>10.3f}' ) lines.append( "--------------------------------------------------------") return "\n".join(lines)
def form_rpa_a_matrix_mo_singlet_partial( E_MO: np.ndarray, TEI_MO_iajb: np.ndarray, TEI_MO_ijab: np.ndarray ) -> np.ndarray: r"""Form the A (CIS) matrix in the MO basis. [singlet] The equation for element :math:`\{ia,jb\}` is :math:`\left<aj||ib\right> = \left<aj|ib\right> - \left<aj|bi\right> = [ai|jb] - [ab|ji] = 2(ai|jb) - (ab|ji)`. It also includes the virt-occ energy difference on the diagonal. """ shape_iajb = TEI_MO_iajb.shape shape_ijab = TEI_MO_ijab.shape assert len(shape_iajb) == len(shape_ijab) == 4 assert shape_iajb[0] == shape_iajb[2] == shape_ijab[0] == shape_ijab[1] assert shape_iajb[1] == shape_iajb[3] == shape_ijab[2] == shape_ijab[3] nocc = shape_iajb[0] nvirt = shape_iajb[1] norb = nocc + nvirt assert len(E_MO.shape) == 2 assert E_MO.shape[0] == E_MO.shape[1] == norb nov = nocc * nvirt ediff = form_vec_energy_differences(np.diag(E_MO)[:nocc], np.diag(E_MO)[nocc:]) A = 2 * TEI_MO_iajb A -= TEI_MO_ijab.swapaxes(1, 2) A.shape = (nov, nov) A += np.diag(ediff) return A
def form_rpa_a_matrix_mo_singlet_ss_partial( E_MO: np.ndarray, TEI_MO_iajb: np.ndarray, TEI_MO_ijab: np.ndarray ) -> np.ndarray: r"""Form the same-spin part of the A (CIS) matrix in the MO basis. [singlet] The equation for element :math:`\{ia,jb\}` is :math:`????`. """ shape_iajb = TEI_MO_iajb.shape shape_ijab = TEI_MO_ijab.shape assert len(shape_iajb) == len(shape_ijab) == 4 assert shape_iajb[0] == shape_iajb[2] == shape_ijab[0] == shape_ijab[1] assert shape_iajb[1] == shape_iajb[3] == shape_ijab[2] == shape_ijab[3] nocc = shape_iajb[0] nvirt = shape_iajb[1] norb = nocc + nvirt assert len(E_MO.shape) == 2 assert E_MO.shape[0] == E_MO.shape[1] == norb nov = nocc * nvirt ediff = form_vec_energy_differences(np.diag(E_MO)[:nocc], np.diag(E_MO)[nocc:]) A = TEI_MO_iajb.copy() A -= TEI_MO_ijab.swapaxes(1, 2) A.shape = (nov, nov) A += np.diag(ediff) return A
def form_rpa_a_matrix_mo_triplet_partial(E_MO, TEI_MO_ijab): r"""Form the A (CIS) matrix in the MO basis. [triplet] The equation for element :math:`\{ia,jb\}` is :math:`- \left<aj|bi\right> = - [ab|ji] = - (ab|ji)`. It also includes the virt-occ energy difference on the diagonal. """ shape_ijab = TEI_MO_ijab.shape assert len(shape_ijab) == 4 assert shape_ijab[0] == shape_ijab[1] assert shape_ijab[2] == shape_ijab[3] nocc = shape_ijab[0] nvirt = shape_ijab[2] norb = nocc + nvirt assert len(E_MO.shape) == 2 assert E_MO.shape[0] == E_MO.shape[1] == norb nov = nocc * nvirt ediff = form_vec_energy_differences( np.diag(E_MO)[:nocc], np.diag(E_MO)[nocc:]) A = np.zeros((nocc, nvirt, nocc, nvirt)) A -= TEI_MO_ijab.swapaxes(1, 2) A.shape = (nov, nov) A += np.diag(ediff) return A
def form_uncoupled_results(self): # We avoid the formation of the full Hessian, but the energy # differences on the diagonal are still needed. Form them # here. The dynamic frequency contribution will be handled # inside the main loop. nocc_a, _, nocc_b, _ = self.solver.occupations moene_alph = np.diag(self.solver.moenergies[0]) moene_occ_alph = moene_alph[:nocc_a] moene_virt_alph = moene_alph[nocc_a:] ediff_alph = form_vec_energy_differences(moene_occ_alph, moene_virt_alph) ediff_supervector_alph = np.concatenate((ediff_alph, ediff_alph)) ediff_supervector_alph_static = ediff_supervector_alph[..., np.newaxis] nov_alph = len(ediff_alph) if self.solver.is_uhf: moene_beta = np.diag(self.solver.moenergies[1]) moene_occ_beta = moene_beta[:nocc_b] moene_virt_beta = moene_beta[nocc_b:] ediff_beta = form_vec_energy_differences(moene_occ_beta, moene_virt_beta) ediff_supervector_beta = np.concatenate((ediff_beta, ediff_beta)) ediff_supervector_beta_static = ediff_supervector_beta[..., np.newaxis] nov_beta = len(ediff_beta) self.uncoupled_results = [] for f in range(len(self.solver.frequencies)): frequency = self.solver.frequencies[f] ediff_supervector_alph = ediff_supervector_alph_static.copy() ediff_supervector_alph[:nov_alph] = ( ediff_supervector_alph_static[:nov_alph] - frequency) ediff_supervector_alph[nov_alph:] = ( ediff_supervector_alph_static[nov_alph:] + frequency) if self.solver.is_uhf: ediff_supervector_beta = ediff_supervector_beta_static.copy() ediff_supervector_beta[:nov_beta] = ( ediff_supervector_beta_static[:nov_beta] - frequency) ediff_supervector_beta[nov_beta:] = ( ediff_supervector_beta_static[nov_beta:] + frequency) # dim_rows -> (number of operators) * (number of components for each operator) # dim_cols -> total number of response vectors dim_rows = sum(self.solver.operators[i]. mo_integrals_ai_supervector_alph.shape[0] for i in range(len(self.solver.operators))) dim_cols = dim_rows # FIXME results = np.zeros( shape=(dim_rows, dim_cols), dtype=self.solver.operators[0]. mo_integrals_ai_supervector_alph.dtype, ) # Form the result blocks between each pair of # operators. Ignore any potential symmetry in the final # result matrix for now. result_blocks = [] row_starts = [] col_starts = [] row_start = 0 for iop1, op1 in enumerate(self.solver.operators): col_start = 0 for iop2, op2 in enumerate(self.solver.operators): result_block = 0.0 result_block_alph = form_results( op1.mo_integrals_ai_supervector_alph, op2.mo_integrals_ai_supervector_alph / ediff_supervector_alph, ) result_block += result_block_alph if self.solver.is_uhf: result_block_beta = form_results( op1.mo_integrals_ai_supervector_beta, op2.mo_integrals_ai_supervector_beta / ediff_supervector_beta, ) result_block += result_block_beta result_blocks.append(result_block) row_starts.append(row_start) col_starts.append(col_start) col_start += op2.mo_integrals_ai_supervector_alph.shape[0] row_start += op1.mo_integrals_ai_supervector_alph.shape[0] # Put each of the result blocks back in the main results # matrix. assert len(row_starts) == len(col_starts) == len(result_blocks) for idx, result_block in enumerate(result_blocks): nr, nc = result_block.shape rs, cs = row_starts[idx], col_starts[idx] results[rs:rs + nr, cs:cs + nc] = result_block # The / 2 is because of the supervector part. if self.solver.is_uhf: results = 2 * results / 2 else: results = 4 * results / 2 self.uncoupled_results.append(results)