|
| 1 | +import numpy as np |
| 2 | +import psi4 |
| 3 | +import json |
| 4 | + |
| 5 | +def jsondump(wfn, frozen_docc, active_docc, active_socc, active_uocc, int_cutoff = 1e-13, fname="forte_molecule.json"): |
| 6 | + """ |
| 7 | + Function to build a JSON file compatible with QForte. |
| 8 | + Data is extracted from a Psi4 Wavefunction object. |
| 9 | + Assumes ROHF, and that unpaired electrons don't exist outside the |
| 10 | + active space |
| 11 | + """ |
| 12 | + |
| 13 | + print(f"\nSaving system info to {fname}.\n") |
| 14 | + |
| 15 | + mol = wfn.molecule() |
| 16 | + mints = psi4.core.MintsHelper(wfn.basisset()) |
| 17 | + scalars = wfn.scalar_variables() |
| 18 | + E_nuc = scalars["NUCLEAR REPULSION ENERGY"] |
| 19 | + |
| 20 | + ao_oeis = mints.ao_kinetic().np + mints.ao_potential().np |
| 21 | + ao_teis = mints.ao_eri().np |
| 22 | + num_frozen = np.sum(frozen_docc) |
| 23 | + num_active = np.sum(active_docc + active_socc + active_uocc) |
| 24 | + |
| 25 | + #Reorder C so that instead of orbital energy, it is sorted core->active->virtual |
| 26 | + #Active space is chosen for aufbau ordering (i.e. doccs, soccs, uoccs) |
| 27 | + |
| 28 | + orbitals = [] |
| 29 | + for irrep, block in enumerate(wfn.epsilon_a_subset("MO", "ACTIVE").nph): |
| 30 | + for orbital in block: |
| 31 | + orbitals.append([orbital, irrep]) |
| 32 | + |
| 33 | + orbitals.sort() |
| 34 | + orb_irreps_to_int = [] |
| 35 | + for [eps, irrep] in orbitals: |
| 36 | + orb_irreps_to_int.append(irrep) |
| 37 | + |
| 38 | + categories = {"frozen_docc": [], "active_docc": [], "active_socc": [], "active_uocc": [], "frozen_uocc": []} |
| 39 | + |
| 40 | + irrep_dict = {} |
| 41 | + for i, irrep in enumerate(orb_irreps_to_int): |
| 42 | + if irrep not in irrep_dict: |
| 43 | + irrep_dict[irrep] = [] |
| 44 | + irrep_dict[irrep].append(i) |
| 45 | + |
| 46 | + # Assign orbitals to categories based on count constraints |
| 47 | + for irrep, orbitals in irrep_dict.items(): |
| 48 | + count_frozen = frozen_docc[irrep] |
| 49 | + count_active_docc = active_docc[irrep] |
| 50 | + count_active_socc = active_socc[irrep] |
| 51 | + count_active_uocc = active_uocc[irrep] |
| 52 | + |
| 53 | + categories["frozen_docc"].extend(orbitals[:count_frozen]) |
| 54 | + categories["active_docc"].extend(orbitals[count_frozen:count_frozen + count_active_docc]) |
| 55 | + categories["active_socc"].extend(orbitals[count_frozen + count_active_docc:count_frozen + count_active_docc + count_active_socc]) |
| 56 | + categories["active_uocc"].extend(orbitals[count_frozen + count_active_docc + count_active_socc:count_frozen + count_active_docc + count_active_socc + count_active_uocc]) |
| 57 | + categories["frozen_uocc"].extend(orbitals[count_frozen + count_active_docc + count_active_socc + count_active_uocc:]) |
| 58 | + |
| 59 | + for key in categories: |
| 60 | + categories[key].sort() |
| 61 | + |
| 62 | + new_order = (categories["frozen_docc"] + |
| 63 | + categories["active_docc"] + |
| 64 | + categories["active_socc"] + |
| 65 | + categories["active_uocc"] + |
| 66 | + categories["frozen_uocc"]) |
| 67 | + |
| 68 | + C = wfn.Ca_subset("AO", "ALL").np[:, new_order] |
| 69 | + |
| 70 | + |
| 71 | + |
| 72 | + #Compute frozen core energy and frozen core one electron integral. (E_fc does NOT include nuclear repulsion.) |
| 73 | + Pc = np.einsum('pi,si->ps', C[:,:num_frozen], C[:,:num_frozen]) |
| 74 | + ao_hc = ao_oeis + 2*np.einsum('psuv,ps->uv', ao_teis, Pc) - np.einsum('puvs,ps->uv', ao_teis, Pc) |
| 75 | + E_fc = np.trace(Pc.T@(ao_hc + ao_oeis)) |
| 76 | + |
| 77 | + mo_oeis = np.einsum("ui,vj,uv->ij", C, C, ao_oeis)[num_frozen:num_active+num_frozen, |
| 78 | + num_frozen:num_active+num_frozen] |
| 79 | + |
| 80 | + mo_teis = np.einsum("pi,qj,rk,sl,pqrs->ijkl", C, C, C, C, ao_teis)[num_frozen:num_active+num_frozen, |
| 81 | + num_frozen:num_active+num_frozen, |
| 82 | + num_frozen:num_active+num_frozen, |
| 83 | + num_frozen:num_active+num_frozen] |
| 84 | + |
| 85 | + |
| 86 | + |
| 87 | + nalpha = wfn.nalpha() - np.sum(frozen_docc) |
| 88 | + nbeta = wfn.nbeta() - np.sum(frozen_docc) |
| 89 | + so_irreps = [] |
| 90 | + for i in new_order: |
| 91 | + so_irreps += [orb_irreps_to_int[i],orb_irreps_to_int[i]] |
| 92 | + |
| 93 | + print(f"({nalpha+nbeta}, {num_active}) active space.\n") |
| 94 | + print(f"Ms = {(nalpha - nbeta)/2}\n") |
| 95 | + print(f"Nuclear repulsion energy: {E_nuc}") |
| 96 | + print(f"Frozen core energy: {E_fc}") |
| 97 | + print(f"Total scalar energy: {E_fc + E_nuc}") |
| 98 | + |
| 99 | + external_data = {} |
| 100 | + external_data["scalar_energy"] = {} |
| 101 | + external_data["scalar_energy"]["data"] = E_fc + E_nuc |
| 102 | + external_data["oei"] = {} |
| 103 | + external_data["oei"]["data"] = [] |
| 104 | + |
| 105 | + external_data["tei"] = {} |
| 106 | + external_data["tei"]["data"] = [] |
| 107 | + |
| 108 | + kept = [] |
| 109 | + neglected = [0] |
| 110 | + for p in range(num_active): |
| 111 | + pa = p*2 |
| 112 | + pb = p*2 + 1 |
| 113 | + for q in range(num_active): |
| 114 | + qa = q*2 |
| 115 | + qb = q*2 + 1 |
| 116 | + oei = float(mo_oeis[p,q]) |
| 117 | + if abs(oei) > int_cutoff: |
| 118 | + kept.append(oei) |
| 119 | + external_data["oei"]["data"].append((pa, qa, oei)) |
| 120 | + external_data["oei"]["data"].append((pb, qb, oei)) |
| 121 | + else: |
| 122 | + neglected.append(oei) |
| 123 | + for r in range(num_active): |
| 124 | + ra = r*2 |
| 125 | + rb = r*2 + 1 |
| 126 | + for s in range(num_active): |
| 127 | + sa = s*2 |
| 128 | + sb = s*2 + 1 |
| 129 | + |
| 130 | + tei_J = -float(mo_teis[p,s,q,r]) |
| 131 | + tei_K = -float(mo_teis[p,r,q,s]) |
| 132 | + |
| 133 | + if abs(tei_J - tei_K) > int_cutoff: |
| 134 | + kept.append(tei_J - tei_K) |
| 135 | + external_data["tei"]["data"].append((pa,qa,ra,sa, tei_J - tei_K)) |
| 136 | + external_data["tei"]["data"].append((pb,qb,rb,sb, tei_J - tei_K)) |
| 137 | + else: |
| 138 | + neglected.append(tei_J - tei_K) |
| 139 | + |
| 140 | + if abs(tei_J) > int_cutoff: |
| 141 | + kept.append(tei_J) |
| 142 | + external_data["tei"]["data"].append((pa,qa,ra,sa,tei_J)) |
| 143 | + external_data["tei"]["data"].append((pb,qb,rb,sb,tei_J)) |
| 144 | + else: |
| 145 | + neglected.append(tei_J) |
| 146 | + |
| 147 | + print(f"\nIntegral cutoff: {int_cutoff}\n") |
| 148 | + print(f"{len(kept)}/{pow(num_active,2)*2 + pow(num_active,4)*4} integrals stored.") |
| 149 | + print(f"Smallest included electron integral: {np.amin(abs(np.array(kept)))}") |
| 150 | + print(f"Largest neglected electron integral: {np.amax(abs(np.array(neglected)))}\n") |
| 151 | + |
| 152 | + external_data["nso"] = {} |
| 153 | + external_data["nso"]["data"] = 2 * int(num_active) |
| 154 | + external_data["na"] = {} |
| 155 | + external_data["na"]["data"] = int(nalpha) |
| 156 | + external_data["nb"] = {} |
| 157 | + external_data["nb"]["data"] = int(nbeta) |
| 158 | + external_data["point_group"] = {} |
| 159 | + external_data["point_group"]["data"] = mol.symmetry_from_input().lower() |
| 160 | + external_data["symmetry"] = {} |
| 161 | + external_data["symmetry"]["data"] = so_irreps |
| 162 | + |
| 163 | + with open(fname, "w") as f: |
| 164 | + json.dump(external_data, f, indent = 0) |
| 165 | + |
| 166 | + print(f"\nSystem info saved to {fname}.\n") |
| 167 | + |
| 168 | + |
| 169 | + |
| 170 | + |
| 171 | + |
| 172 | + |
0 commit comments