Beispiel #1
0
    def __init__(self, molecule, options, basisset):
        # verify that the minimal version is used if CPPE is provided
        # from outside the Psi4 ecosystem
        min_version = "0.3.1"
        if parse_version(cppe.__version__) < parse_version(min_version):
            raise ModuleNotFoundError("CPPE version {} is required at least. "
                                      "Version {}"
                                      " was found.".format(min_version,
                                                           cppe.__version__))
        # setup the initial CppeState
        self.molecule = molecule
        self.options = options
        self.pe_ecp = self.options.pop("pe_ecp", False)
        self.basisset = basisset
        self.mints = core.MintsHelper(self.basisset)

        def callback(output):
            core.print_out(f"{output}\n")

        self.cppe_state = cppe.CppeState(self.options, psi4mol_to_cppemol(self.molecule), callback)
        core.print_out("CPPE Options:\n")
        core.print_out(f"PE(ECP) repulsive potentials = {self.pe_ecp}\n")
        for k in cppe.valid_option_keys:
            core.print_out(f"{k} = {self.cppe_state.options[k]}\n")
        core.print_out("-------------------------\n\n")
        self.cppe_state.calculate_static_energies_and_fields()
        # obtain coordinates of polarizable sites
        self._enable_induction = False
        if self.cppe_state.get_polarizable_site_number():
            self._enable_induction = True
            coords = self.cppe_state.positions_polarizable
            self.polarizable_coords = core.Matrix.from_array(coords)
        self.V_es = None
        if self.pe_ecp:
            self._setup_pe_ecp()
    def __init__(self, orbital, use_c):
        self.orbital = orbital
        self.nbf = self.orbital.nbf()
        self.use_c = use_c

        self.mints = pc.MintsHelper(orbital)
        self.eri = np.array(self.mints.ao_eri())
Beispiel #3
0
    def __init__(self, molecule, options, basisset):
        # verify that the minimal version is used if CPPE is provided
        # from outside the Psi4 ecosystem
        min_version = "0.2.0"
        if parse_version(cppe.__version__) < parse_version(min_version):
            raise ModuleNotFoundError("CPPE version {} is required at least. "
                                      "Version {}"
                                      " was found.".format(min_version,
                                                           cppe.__version__))
        # setup the initial CppeState
        self.molecule = molecule
        self.options = options
        self.basisset = basisset
        self.mints = core.MintsHelper(self.basisset)

        def callback(output):
            core.print_out("{}\n".format(output))

        self.cppe_state = cppe.CppeState(self.options, psi4mol_to_cppemol(self.molecule), callback)
        self.cppe_state.calculate_static_energies_and_fields()
        # obtain coordinates of polarizable sites
        self._enable_induction = False
        if self.cppe_state.get_polarizable_site_number():
            self._enable_induction = True
            coords = np.array([site.position for site in self.cppe_state.potentials if site.is_polarizable])
            self.polarizable_coords = core.Matrix.from_array(coords)
        self.V_es = None
Beispiel #4
0
    def _setup_pe_ecp(self):
        mol_clone = self.molecule.clone()
        geom, _, elems, _, _ = mol_clone.to_arrays()
        n_qmatoms = len(elems)
        geom = geom.tolist()
        elems = elems.tolist()
        for p in self.cppe_state.potentials:
            if p.element == "X":
                continue
            elems.append(f"{p.element}_pe")
            geom.append([p.x, p.y, p.z])
        qmmm_mol = core.Molecule.from_arrays(geom=geom,
                                             elbl=elems,
                                             units="Bohr",
                                             fix_com=True,
                                             fix_orientation=True,
                                             fix_symmetry="c1")
        n_qmmmatoms = len(elems)

        def __basisspec_pe_ecp(mol, role):
            global_basis = core.get_global_option("BASIS")
            for i in range(n_qmatoms):
                mol.set_basis_by_number(i, global_basis, role=role)
            for i in range(n_qmatoms, n_qmmmatoms):
                mol.set_basis_by_number(i, "pe_ecp", role=role)
            return {}

        libmintsbasisset.basishorde["PE_ECP_BASIS"] = __basisspec_pe_ecp
        self.pe_ecp_basis = core.BasisSet.build(qmmm_mol, "BASIS",
                                                "PE_ECP_BASIS")
        ecp_mints = core.MintsHelper(self.pe_ecp_basis)
        self.V_pe_ecp = ecp_mints.ao_ecp().np
    def __init__(self, orbital, auxiliary, use_c):
        self.orbital = orbital
        self.auxiliary = auxiliary
        self.zero_basis = pc.BasisSet.zero_ao_basis_set()
        self.nbf = self.orbital.nbf()
        self.naux = self.auxiliary.nbf()
        self.use_c = use_c

        # Build required integrals
        self.mints = pc.MintsHelper(orbital)

        # Form (P|Q) ^ (-0.5)
        metric = self.mints.ao_eri(self.auxiliary, self.zero_basis,
                                   self.auxiliary, self.zero_basis)
        metric.power(-0.5, 1.e-14)
        self.metric = np.array(metric).squeeze()

        # Form AO Integrals
        Qpq = np.asarray(
            self.mints.ao_eri(self.auxiliary, self.zero_basis, self.orbital,
                              self.orbital)).squeeze()

        # Apply metric contraction
        self.Ppq = np.dot(self.metric,
                          Qpq.reshape(self.naux,
                                      -1)).reshape(self.naux, self.nbf,
                                                   self.nbf)
Beispiel #6
0
def scf_initialize(self):
    """Specialized initialization, compute integrals and does everything to prepare for iterations"""

    self.iteration_ = 0
    efp_enabled = hasattr(self.molecule(), 'EFP')

    if core.get_option('SCF', "PRINT") > 0:
        core.print_out("  ==> Pre-Iterations <==\n\n")
        self.print_preiterations()

    if efp_enabled:
        # EFP: Set QM system, options, and callback. Display efp geom in [A]
        efpobj = self.molecule().EFP
        core.print_out(efpobj.banner())
        core.print_out(efpobj.geometry_summary(units_to_bohr=constants.bohr2angstroms))

        efpptc, efpcoords, efpopts = get_qm_atoms_opts(self.molecule())
        efpobj.set_point_charges(efpptc, efpcoords)
        efpobj.set_opts(efpopts, label='psi', append='psi')

        efpobj.set_electron_density_field_fn(field_fn)

    if self.attempt_number_ == 1:
        mints = core.MintsHelper(self.basisset())
        if core.get_global_option('RELATIVISTIC') in ['X2C', 'DKH']:
            mints.set_rel_basisset(self.get_basisset('BASIS_RELATIVISTIC'))

        mints.one_electron_integrals()
        self.integrals()

        core.timer_on("HF: Form core H")
        self.form_H()
        core.timer_off("HF: Form core H")

        if efp_enabled:
            # EFP: Add in permanent moment contribution and cache
            core.timer_on("HF: Form Vefp")
            verbose = core.get_option('SCF', "PRINT")
            Vefp = modify_Fock_permanent(self.molecule(), mints, verbose=verbose-1)
            Vefp = core.Matrix.from_array(Vefp)
            self.H().add(Vefp)
            Horig = self.H().clone()
            self.Horig = Horig
            core.print_out("  QM/EFP: iterating Total Energy including QM/EFP Induction\n")
            core.timer_off("HF: Form Vefp")

        core.timer_on("HF: Form S/X")
        self.form_Shalf()
        core.timer_off("HF: Form S/X")

        core.timer_on("HF: Guess")
        self.guess()
        core.timer_off("HF: Guess")

    else:
        # We're reading the orbitals from the previous set of iterations.
        self.form_D()
        self.set_energies("Total Energy", self.compute_initial_E())
Beispiel #7
0
    def _initialize_mints(self, basis, v_only=False):
        mints = core.MintsHelper(basis)
        V = mints.ao_potential()
        if v_only:
            return V

        T = mints.ao_kinetic()
        S = mints.ao_overlap()
        return V, T, S
Beispiel #8
0
def check_iwl_file_from_scf_type(scf_type, wfn):
    """
    Ensures that a IWL file has been written based on input SCF type.
    """


    if scf_type in ['DF', 'DISK_DF', 'MEM_DF', 'CD', 'PK', 'DIRECT']:
        mints = core.MintsHelper(wfn.basisset())
        if core.get_global_option("RELATIVISTIC") in ["X2C", "DKH"]:
            rel_bas = core.BasisSet.build(wfn.molecule(), "BASIS_RELATIVISTIC",
                                          core.get_option("SCF", "BASIS_RELATIVISTIC"),
                                          "DECON", core.get_global_option('BASIS'),
                                          puream=wfn.basisset().has_puream())
            mints.set_basisset('BASIS_RELATIVISTIC',rel_bas)

        mints.set_print(1)
        mints.integrals()
Beispiel #9
0
    def compute_energy(self):

        print('Building MO integrals.')
        # Integral generation from Psi4's MintsHelper
        t = time.time()
        mints = pc.MintsHelper(self.basis)
        Co = pc.Matrix.from_array(self.C[:, :self.ndocc])
        Cv = pc.Matrix.from_array(self.C[:, self.ndocc:])
        MO = np.asarray(mints.mo_eri(Co, Cv, Co, Cv))

        Eocc = self.eps[:self.ndocc]
        Evirt = self.eps[self.ndocc:]

        print('Shape of MO integrals: %s' % str(MO.shape))
        print('\n...finished SCF and integral build in %.3f seconds.\n' %
              (time.time() - t))

        print('Computing MP2 energy...')
        t = time.time()
        e_denom = 1 / (Eocc.reshape(-1, 1, 1, 1) - Evirt.reshape(-1, 1, 1) +
                       Eocc.reshape(-1, 1) - Evirt)

        # Get the two spin cases
        MP2corr_OS = np.einsum('iajb,iajb,iajb->', MO, MO, e_denom)
        MP2corr_SS = np.einsum('iajb,iajb,iajb->', MO - MO.swapaxes(1, 3), MO,
                               e_denom)
        print('...MP2 energy computed in %.3f seconds.\n' % (time.time() - t))

        MP2corr_E = MP2corr_SS + MP2corr_OS
        MP2_E = self.SCF_E + MP2corr_E

        SCS_MP2corr_E = MP2corr_SS / 3 + MP2corr_OS * (6. / 5)
        SCS_MP2_E = self.SCF_E + SCS_MP2corr_E

        print('MP2 SS correlation energy:         %16.10f' % MP2corr_SS)
        print('MP2 OS correlation energy:         %16.10f' % MP2corr_OS)

        print('\nMP2 correlation energy:            %16.10f' % MP2corr_E)
        print('MP2 total energy:                  %16.10f' % MP2_E)

        print('\nSCS-MP2 correlation energy:        %16.10f' % MP2corr_SS)
        print('SCS-MP2 total energy:              %16.10f' % SCS_MP2_E)

        return MP2_E
Beispiel #10
0
    def __init__(self, molecule, options, basisset):
        # setup the initial CppeState
        self.molecule = molecule
        self.options = options
        self.basisset = basisset
        self.mints = core.MintsHelper(self.basisset)

        def callback(output):
            core.print_out("{}\n".format(output))

        self.cppe_state = cppe.CppeState(self.options, psi4mol_to_cppemol(self.molecule), callback)
        self.cppe_state.calculate_static_energies_and_fields()
        # obtain coordinates of polarizable sites
        self._enable_induction = False
        if self.cppe_state.get_polarizable_site_number():
            self._enable_induction = True
            coords = np.array([site.position for site in self.cppe_state.potentials if site.is_polarizable])
            self.polarizable_coords = core.Matrix.from_array(coords)
        self.V_es = None
Beispiel #11
0
def scf_finalize_energy(self):
    """Performs stability analysis and calls back SCF with new guess
    if needed, Returns the SCF energy. This function should be called
    once orbitals are ready for energy/property computations, usually
    after iterations() is called.

    """

    # post-scf vv10 correlation
    if core.get_option(
            'SCF', "DFT_VV10_POSTSCF") and self.functional().vv10_b() > 0.0:
        self.functional().set_lock(False)
        self.functional().set_do_vv10(True)
        self.functional().set_lock(True)
        core.print_out(
            "  ==> Computing Non-Self-Consistent VV10 Energy Correction <==\n\n"
        )
        SCFE = 0.0
        self.form_V()
        SCFE += self.compute_E()
        self.set_energies("Total Energy", SCFE)

    # Perform wavefunction stability analysis before doing
    # anything on a wavefunction that may not be truly converged.
    if core.get_option('SCF', 'STABILITY_ANALYSIS') != "NONE":

        # Don't bother computing needed integrals if we can't do anything with them.
        if self.functional().needs_xc():
            raise ValidationError(
                "Stability analysis not yet supported for XC functionals.")

        # We need the integral file, make sure it is written and
        # compute it if needed
        if core.get_option('SCF', 'REFERENCE') != "UHF":
            psio = core.IO.shared_object()
            #psio.open(constants.PSIF_SO_TEI, 1)  # PSIO_OPEN_OLD
            #try:
            #    psio.tocscan(constants.PSIF_SO_TEI, "IWL Buffers")
            #except TypeError:
            #    # "IWL Buffers" actually found but psio_tocentry can't be returned to Py
            #    psio.close(constants.PSIF_SO_TEI, 1)
            #else:
            #    # tocscan returned None
            #    psio.close(constants.PSIF_SO_TEI, 1)

            # logic above foiled by psio_tocentry not returning None<--nullptr in pb11 2.2.1
            #   so forcibly recomputing for now until stability revamp
            core.print_out("    SO Integrals not on disk. Computing...")
            mints = core.MintsHelper(self.basisset())
            #next 2 lines fix a bug that prohibits relativistic stability analysis
            if core.get_global_option('RELATIVISTIC') in ['X2C', 'DKH']:
                mints.set_rel_basisset(self.get_basisset('BASIS_RELATIVISTIC'))
            mints.integrals()
            core.print_out("done.\n")

            # Q: Not worth exporting all the layers of psio, right?

        follow = self.stability_analysis()

        while follow and self.attempt_number_ <= core.get_option(
                'SCF', 'MAX_ATTEMPTS'):
            self.attempt_number_ += 1
            core.print_out(
                "    Running SCF again with the rotated orbitals.\n")

            if self.initialized_diis_manager_:
                self.diis_manager().reset_subspace()
            # reading the rotated orbitals in before starting iterations
            self.form_D()
            self.set_energies("Total Energy", self.compute_initial_E())
            self.iterations()
            follow = self.stability_analysis()

        if follow and self.attempt_number_ > core.get_option(
                'SCF', 'MAX_ATTEMPTS'):
            core.print_out(
                "    There's still a negative eigenvalue. Try modifying FOLLOW_STEP_SCALE\n"
            )
            core.print_out(
                "    or increasing MAX_ATTEMPTS (not available for PK integrals).\n"
            )

    # At this point, we are not doing any more SCF cycles
    #   and we can compute and print final quantities.

    if hasattr(self.molecule(), 'EFP'):
        efpobj = self.molecule().EFP

        efpobj.compute()  # do_gradient=do_gradient)
        efpene = efpobj.get_energy(label='psi')
        efp_wfn_independent_energy = efpene['total'] - efpene['ind']
        self.set_energies("EFP", efpene['total'])

        SCFE = self.get_energies("Total Energy")
        SCFE += efp_wfn_independent_energy
        self.set_energies("Total Energy", SCFE)
        core.print_out(efpobj.energy_summary(scfefp=SCFE, label='psi'))

        core.set_variable(
            'EFP ELST ENERGY',
            efpene['electrostatic'] + efpene['charge_penetration'] +
            efpene['electrostatic_point_charges'])
        core.set_variable('EFP IND ENERGY', efpene['polarization'])
        core.set_variable('EFP DISP ENERGY', efpene['dispersion'])
        core.set_variable('EFP EXCH ENERGY', efpene['exchange_repulsion'])
        core.set_variable('EFP TOTAL ENERGY', efpene['total'])
        core.set_variable('CURRENT ENERGY', efpene['total'])

    core.print_out("\n  ==> Post-Iterations <==\n\n")

    self.check_phases()
    self.compute_spin_contamination()
    self.frac_renormalize()
    reference = core.get_option("SCF", "REFERENCE")

    energy = self.get_energies("Total Energy")

    #    fail_on_maxiter = core.get_option("SCF", "FAIL_ON_MAXITER")
    #    if converged or not fail_on_maxiter:
    #
    #        if print_lvl > 0:
    #            self.print_orbitals()
    #
    #        if converged:
    #            core.print_out("  Energy converged.\n\n")
    #        else:
    #            core.print_out("  Energy did not converge, but proceeding anyway.\n\n")

    if core.get_option('SCF', 'PRINT') > 0:
        self.print_orbitals()

    is_dfjk = core.get_global_option('SCF_TYPE').endswith('DF')
    core.print_out("  @%s%s Final Energy: %20.14f" %
                   ('DF-' if is_dfjk else '', reference, energy))
    # if (perturb_h_) {
    #     core.print_out(" with %f %f %f perturbation" %
    #                    (dipole_field_strength_[0], dipole_field_strength_[1], dipole_field_strength_[2]))
    # }
    core.print_out("\n\n")
    self.print_energies()

    self.clear_external_potentials()
    if core.get_option('SCF', 'PCM'):
        calc_type = core.PCM.CalcType.Total
        if core.get_option("PCM", "PCM_SCF_TYPE") == "SEPARATE":
            calc_type = core.PCM.CalcType.NucAndEle
        Dt = self.Da().clone()
        Dt.add(self.Db())
        _, Vpcm = self.get_PCM().compute_PCM_terms(Dt, calc_type)
        self.push_back_external_potential(Vpcm)

    # Properties
    #  Comments so that autodoc utility will find these PSI variables
    #  Process::environment.globals["SCF DIPOLE X"] =
    #  Process::environment.globals["SCF DIPOLE Y"] =
    #  Process::environment.globals["SCF DIPOLE Z"] =
    #  Process::environment.globals["SCF QUADRUPOLE XX"] =
    #  Process::environment.globals["SCF QUADRUPOLE XY"] =
    #  Process::environment.globals["SCF QUADRUPOLE XZ"] =
    #  Process::environment.globals["SCF QUADRUPOLE YY"] =
    #  Process::environment.globals["SCF QUADRUPOLE YZ"] =
    #  Process::environment.globals["SCF QUADRUPOLE ZZ"] =

    # Orbitals are always saved, in case an MO guess is requested later
    # save_orbitals()

    # Shove variables into global space
    for k, v in self.variables().items():
        core.set_variable(k, v)

    self.finalize()
    if self.V_potential():
        self.V_potential().clear_collocation_cache()

    core.print_out("\nComputation Completed\n")

    return energy
Beispiel #12
0
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.Matrix.doublet(
        cache["Cocc_A"], cache["Cocc_A"], False, True)
    cache["D_B"] = core.Matrix.doublet(
        cache["Cocc_B"], cache["Cocc_B"], False, True)

    cache["P_A"] = core.Matrix.doublet(
        cache["Cvir_A"], cache["Cvir_A"], False, True)
    cache["P_B"] = core.Matrix.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.Matrix.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
Beispiel #13
0
def scf_initialize(self):
    """Specialized initialization, compute integrals and does everything to prepare for iterations"""

    # Figure out memory distributions

    # Get memory in terms of doubles
    total_memory = (core.get_memory() /
                    8) * core.get_global_option("SCF_MEM_SAFETY_FACTOR")

    # Figure out how large the DFT collocation matrices are
    vbase = self.V_potential()
    if vbase:
        collocation_size = vbase.grid().collocation_size()
        if vbase.functional().ansatz() == 1:
            collocation_size *= 4  # First derivs
        elif vbase.functional().ansatz() == 2:
            collocation_size *= 10  # Second derivs
    else:
        collocation_size = 0

    # Change allocation for collocation matrices based on DFT type
    scf_type = core.get_global_option('SCF_TYPE').upper()
    nbf = self.get_basisset("ORBITAL").nbf()
    naux = self.get_basisset("DF_BASIS_SCF").nbf()
    if "DIRECT" == scf_type:
        jk_size = total_memory * 0.1
    elif scf_type.endswith('DF'):
        jk_size = naux * nbf * nbf
    else:
        jk_size = nbf**4

    # Give remaining to collocation
    if total_memory > jk_size:
        collocation_memory = total_memory - jk_size
    # Give up to 10% to collocation
    elif (total_memory * 0.1) > collocation_size:
        collocation_memory = collocation_size
    else:
        collocation_memory = total_memory * 0.1

    if collocation_memory > collocation_size:
        collocation_memory = collocation_size

    # Set constants
    self.iteration_ = 0
    self.memory_jk_ = int(total_memory - collocation_memory)
    self.memory_collocation_ = int(collocation_memory)

    # Print out initial docc/socc/etc data
    if core.get_option('SCF', "PRINT") > 0:
        core.print_out("  ==> Pre-Iterations <==\n\n")
        self.print_preiterations()

    # Initialize EFP
    efp_enabled = hasattr(self.molecule(), 'EFP')
    if efp_enabled:
        # EFP: Set QM system, options, and callback. Display efp geom in [A]
        efpobj = self.molecule().EFP
        core.print_out(efpobj.banner())
        core.print_out(
            efpobj.geometry_summary(units_to_bohr=constants.bohr2angstroms))

        efpptc, efpcoords, efpopts = get_qm_atoms_opts(self.molecule())
        efpobj.set_point_charges(efpptc, efpcoords)
        efpobj.set_opts(efpopts, label='psi', append='psi')

        efpobj.set_electron_density_field_fn(field_fn)

    # Initilize all integratals and perform the first guess
    if self.attempt_number_ == 1:
        mints = core.MintsHelper(self.basisset())
        if core.get_global_option('RELATIVISTIC') in ['X2C', 'DKH']:
            mints.set_rel_basisset(self.get_basisset('BASIS_RELATIVISTIC'))

        mints.one_electron_integrals()
        self.initialize_jk(self.memory_jk_)
        if self.V_potential():
            self.V_potential().build_collocation_cache(
                self.memory_collocation_)

        core.timer_on("HF: Form core H")
        self.form_H()
        core.timer_off("HF: Form core H")

        if efp_enabled:
            # EFP: Add in permanent moment contribution and cache
            core.timer_on("HF: Form Vefp")
            verbose = core.get_option('SCF', "PRINT")
            Vefp = modify_Fock_permanent(self.molecule(),
                                         mints,
                                         verbose=verbose - 1)
            Vefp = core.Matrix.from_array(Vefp)
            self.H().add(Vefp)
            Horig = self.H().clone()
            self.Horig = Horig
            core.print_out(
                "  QM/EFP: iterating Total Energy including QM/EFP Induction\n"
            )
            core.timer_off("HF: Form Vefp")

        core.timer_on("HF: Form S/X")
        self.form_Shalf()
        core.timer_off("HF: Form S/X")

        core.timer_on("HF: Guess")
        self.guess()
        core.timer_off("HF: Guess")

    else:
        # We're reading the orbitals from the previous set of iterations.
        self.form_D()
        self.set_energies("Total Energy", self.compute_initial_E())

    # turn off VV10 for iterations
    if core.get_option(
            'SCF', "DFT_VV10_POSTSCF") and self.functional().vv10_b() > 0.0:
        core.print_out("  VV10: post-SCF option active \n \n")
        self.functional().set_lock(False)
        self.functional().set_do_vv10(False)
        self.functional().set_lock(True)
Beispiel #14
0
def scf_iterate(self, e_conv=None, d_conv=None):

    is_dfjk = core.get_global_option('SCF_TYPE').endswith('DF')
    verbose = core.get_option('SCF', "PRINT")
    reference = core.get_option('SCF', "REFERENCE")

    # self.member_data_ signals are non-local, used internally by c-side fns
    self.diis_enabled_ = _validate_diis()
    self.MOM_excited_ = _validate_MOM()
    self.diis_start_ = core.get_option('SCF', 'DIIS_START')
    damping_enabled = _validate_damping()
    soscf_enabled = _validate_soscf()
    frac_enabled = _validate_frac()
    efp_enabled = hasattr(self.molecule(), 'EFP')

    if self.iteration_ < 2:
        core.print_out("  ==> Iterations <==\n\n")
        core.print_out(
            "%s                        Total Energy        Delta E     RMS |[F,P]|\n\n"
            % ("   " if is_dfjk else ""))

    # SCF iterations!
    SCFE_old = 0.0
    SCFE = 0.0
    Drms = 0.0
    while True:
        self.iteration_ += 1

        diis_performed = False
        soscf_performed = False
        self.frac_performed_ = False
        #self.MOM_performed_ = False  # redundant from common_init()

        self.save_density_and_energy()

        if efp_enabled:
            # EFP: Add efp contribution to Fock matrix
            self.H().copy(self.Horig)
            global mints_psi4_yo
            mints_psi4_yo = core.MintsHelper(self.basisset())
            Vefp = modify_Fock_induced(self.molecule().EFP,
                                       mints_psi4_yo,
                                       verbose=verbose - 1)
            Vefp = core.Matrix.from_array(Vefp)
            self.H().add(Vefp)

        SCFE = 0.0
        self.clear_external_potentials()

        core.timer_on("HF: Form G")
        self.form_G()
        core.timer_off("HF: Form G")

        # reset fractional SAD occupation
        if (self.iteration_ == 0) and self.reset_occ_:
            self.reset_occupation()

        upcm = 0.0
        if core.get_option('SCF', 'PCM'):
            calc_type = core.PCM.CalcType.Total
            if core.get_option("PCM", "PCM_SCF_TYPE") == "SEPARATE":
                calc_type = core.PCM.CalcType.NucAndEle
            Dt = self.Da().clone()
            Dt.add(self.Db())
            upcm, Vpcm = self.get_PCM().compute_PCM_terms(Dt, calc_type)
            SCFE += upcm
            self.push_back_external_potential(Vpcm)
        self.set_variable("PCM POLARIZATION ENERGY", upcm)
        self.set_energies("PCM Polarization", upcm)

        core.timer_on("HF: Form F")
        self.form_F()
        core.timer_off("HF: Form F")

        if verbose > 3:
            self.Fa().print_out()
            self.Fb().print_out()

        SCFE += self.compute_E()
        if efp_enabled:
            global efp_Dt_psi4_yo

            # EFP: Add efp contribution to energy
            efp_Dt_psi4_yo = self.Da().clone()
            efp_Dt_psi4_yo.add(self.Db())
            SCFE += self.molecule().EFP.get_wavefunction_dependent_energy()

        self.set_energies("Total Energy", SCFE)
        Ediff = SCFE - SCFE_old
        SCFE_old = SCFE

        status = []

        # We either do SOSCF or DIIS
        if (soscf_enabled and (self.iteration_ > 3) and
            (Drms < core.get_option('SCF', 'SOSCF_START_CONVERGENCE'))):

            Drms = self.compute_orbital_gradient(
                False, core.get_option('SCF', 'DIIS_MAX_VECS'))
            diis_performed = False
            if self.functional().needs_xc():
                base_name = "SOKS, nmicro="
            else:
                base_name = "SOSCF, nmicro="

            if not _converged(Ediff, Drms, e_conv=e_conv, d_conv=d_conv):
                nmicro = self.soscf_update(
                    core.get_option('SCF', 'SOSCF_CONV'),
                    core.get_option('SCF', 'SOSCF_MIN_ITER'),
                    core.get_option('SCF', 'SOSCF_MAX_ITER'),
                    core.get_option('SCF', 'SOSCF_PRINT'))
                if nmicro > 0:
                    # if zero, the soscf call bounced for some reason
                    self.find_occupation()
                    status.append(base_name + str(nmicro))
                    soscf_performed = True  # Stops DIIS
                else:
                    if verbose > 0:
                        core.print_out(
                            "Did not take a SOSCF step, using normal convergence methods\n"
                        )
                    soscf_performed = False  # Back to DIIS

            else:
                # need to ensure orthogonal orbitals and set epsilon
                status.append(base_name + "conv")
                core.timer_on("HF: Form C")
                self.form_C()
                core.timer_off("HF: Form C")
                soscf_performed = True  # Stops DIIS

        if not soscf_performed:
            # Normal convergence procedures if we do not do SOSCF

            core.timer_on("HF: DIIS")
            diis_performed = False
            add_to_diis_subspace = False

            if self.diis_enabled_ and self.iteration_ >= self.diis_start_:
                add_to_diis_subspace = True

            Drms = self.compute_orbital_gradient(
                add_to_diis_subspace, core.get_option('SCF', 'DIIS_MAX_VECS'))

            if (self.diis_enabled_ and self.iteration_ >= self.diis_start_ +
                    core.get_option('SCF', 'DIIS_MIN_VECS') - 1):
                diis_performed = self.diis()

            if diis_performed:
                status.append("DIIS")

            core.timer_off("HF: DIIS")

            if verbose > 4 and diis_performed:
                core.print_out("  After DIIS:\n")
                self.Fa().print_out()
                self.Fb().print_out()

            # frac, MOM invoked here from Wfn::HF::find_occupation
            core.timer_on("HF: Form C")
            self.form_C()
            core.timer_off("HF: Form C")

        if self.MOM_performed_:
            status.append("MOM")

        if self.frac_performed_:
            status.append("FRAC")

        core.timer_on("HF: Form D")
        self.form_D()
        core.timer_off("HF: Form D")

        core.set_variable("SCF ITERATION ENERGY", SCFE)

        # After we've built the new D, damp the update
        if (damping_enabled and self.iteration_ > 1
                and Drms > core.get_option('SCF', 'DAMPING_CONVERGENCE')):
            damping_percentage = core.get_option('SCF', "DAMPING_PERCENTAGE")
            self.damping_update(damping_percentage * 0.01)
            status.append("DAMP={}%".format(round(damping_percentage)))

        if verbose > 3:
            self.Ca().print_out()
            self.Cb().print_out()
            self.Da().print_out()
            self.Db().print_out()

        # Print out the iteration
        core.print_out("   @%s%s iter %3d: %20.14f   %12.5e   %-11.5e %s\n" %
                       ("DF-" if is_dfjk else "", reference, self.iteration_,
                        SCFE, Ediff, Drms, '/'.join(status)))

        # if a an excited MOM is requested but not started, don't stop yet
        if self.MOM_excited_ and not self.MOM_performed_:
            continue

        # if a fractional occupation is requested but not started, don't stop yet
        if frac_enabled and not self.frac_performed_:
            continue

        # Call any postiteration callbacks

        if _converged(Ediff, Drms, e_conv=e_conv, d_conv=d_conv):
            break
        if self.iteration_ >= core.get_option('SCF', 'MAXITER'):
            raise SCFConvergenceError("""SCF iterations""", self.iteration_,
                                      self, Ediff, Drms)
Beispiel #15
0
def scf_iterate(self, e_conv=None, d_conv=None):

    is_dfjk = core.get_global_option('SCF_TYPE').endswith('DF')
    verbose = core.get_option('SCF', "PRINT")
    reference = core.get_option('SCF', "REFERENCE")

    # self.member_data_ signals are non-local, used internally by c-side fns
    self.diis_enabled_ = self.validate_diis()
    self.MOM_excited_ = _validate_MOM()
    self.diis_start_ = core.get_option('SCF', 'DIIS_START')
    damping_enabled = _validate_damping()
    soscf_enabled = _validate_soscf()
    frac_enabled = _validate_frac()
    efp_enabled = hasattr(self.molecule(), 'EFP')

    # SCF iterations!
    SCFE_old = 0.0
    Dnorm = 0.0
    while True:
        self.iteration_ += 1

        diis_performed = False
        soscf_performed = False
        self.frac_performed_ = False
        #self.MOM_performed_ = False  # redundant from common_init()

        self.save_density_and_energy()

        if efp_enabled:
            # EFP: Add efp contribution to Fock matrix
            self.H().copy(self.Horig)
            global mints_psi4_yo
            mints_psi4_yo = core.MintsHelper(self.basisset())
            Vefp = modify_Fock_induced(self.molecule().EFP,
                                       mints_psi4_yo,
                                       verbose=verbose - 1)
            Vefp = core.Matrix.from_array(Vefp)
            self.H().add(Vefp)

        SCFE = 0.0
        self.clear_external_potentials()

        core.timer_on("HF: Form G")
        self.form_G()
        core.timer_off("HF: Form G")

        incfock_performed = hasattr(
            self.jk(), "do_incfock_iter") and self.jk().do_incfock_iter()

        upcm = 0.0
        if core.get_option('SCF', 'PCM'):
            calc_type = core.PCM.CalcType.Total
            if core.get_option("PCM", "PCM_SCF_TYPE") == "SEPARATE":
                calc_type = core.PCM.CalcType.NucAndEle
            Dt = self.Da().clone()
            Dt.add(self.Db())
            upcm, Vpcm = self.get_PCM().compute_PCM_terms(Dt, calc_type)
            SCFE += upcm
            self.push_back_external_potential(Vpcm)
        self.set_variable("PCM POLARIZATION ENERGY", upcm)  # P::e PCM
        self.set_energies("PCM Polarization", upcm)

        upe = 0.0
        if core.get_option('SCF', 'PE'):
            Dt = self.Da().clone()
            Dt.add(self.Db())
            upe, Vpe = self.pe_state.get_pe_contribution(Dt, elec_only=False)
            SCFE += upe
            self.push_back_external_potential(Vpe)
        self.set_variable("PE ENERGY", upe)  # P::e PE
        self.set_energies("PE Energy", upe)

        core.timer_on("HF: Form F")
        # SAD: since we don't have orbitals yet, we might not be able
        # to form the real Fock matrix. Instead, build an initial one
        if (self.iteration_ == 0) and self.sad_:
            self.form_initial_F()
        else:
            self.form_F()
        core.timer_off("HF: Form F")

        if verbose > 3:
            self.Fa().print_out()
            self.Fb().print_out()

        SCFE += self.compute_E()
        if efp_enabled:
            global efp_Dt_psi4_yo

            # EFP: Add efp contribution to energy
            efp_Dt_psi4_yo = self.Da().clone()
            efp_Dt_psi4_yo.add(self.Db())
            SCFE += self.molecule().EFP.get_wavefunction_dependent_energy()

        self.set_energies("Total Energy", SCFE)
        core.set_variable("SCF ITERATION ENERGY", SCFE)
        Ediff = SCFE - SCFE_old
        SCFE_old = SCFE

        status = []

        # Check if we are doing SOSCF
        if (soscf_enabled and (self.iteration_ >= 3) and
            (Dnorm < core.get_option('SCF', 'SOSCF_START_CONVERGENCE'))):
            Dnorm = self.compute_orbital_gradient(
                False, core.get_option('SCF', 'DIIS_MAX_VECS'))
            diis_performed = False
            if self.functional().needs_xc():
                base_name = "SOKS, nmicro="
            else:
                base_name = "SOSCF, nmicro="

            if not _converged(Ediff, Dnorm, e_conv=e_conv, d_conv=d_conv):
                nmicro = self.soscf_update(
                    core.get_option('SCF', 'SOSCF_CONV'),
                    core.get_option('SCF', 'SOSCF_MIN_ITER'),
                    core.get_option('SCF', 'SOSCF_MAX_ITER'),
                    core.get_option('SCF', 'SOSCF_PRINT'))
                # if zero, the soscf call bounced for some reason
                soscf_performed = (nmicro > 0)

                if soscf_performed:
                    self.find_occupation()
                    status.append(base_name + str(nmicro))
                else:
                    if verbose > 0:
                        core.print_out(
                            "Did not take a SOSCF step, using normal convergence methods\n"
                        )

            else:
                # need to ensure orthogonal orbitals and set epsilon
                status.append(base_name + "conv")
                core.timer_on("HF: Form C")
                self.form_C()
                core.timer_off("HF: Form C")
                soscf_performed = True  # Stops DIIS

        if not soscf_performed:
            # Normal convergence procedures if we do not do SOSCF

            # SAD: form initial orbitals from the initial Fock matrix, and
            # reset the occupations. The reset is necessary because SAD
            # nalpha_ and nbeta_ are not guaranteed physical.
            # From here on, the density matrices are correct.
            if (self.iteration_ == 0) and self.sad_:
                self.form_initial_C()
                self.reset_occupation()
                self.find_occupation()

            else:
                # Run DIIS
                core.timer_on("HF: DIIS")
                diis_performed = False
                add_to_diis_subspace = self.diis_enabled_ and self.iteration_ >= self.diis_start_

                Dnorm = self.compute_orbital_gradient(
                    add_to_diis_subspace,
                    core.get_option('SCF', 'DIIS_MAX_VECS'))

                if add_to_diis_subspace:
                    for engine_used in self.diis(Dnorm):
                        status.append(engine_used)

                core.timer_off("HF: DIIS")

                if verbose > 4 and diis_performed:
                    core.print_out("  After DIIS:\n")
                    self.Fa().print_out()
                    self.Fb().print_out()

                # frac, MOM invoked here from Wfn::HF::find_occupation
                core.timer_on("HF: Form C")
                level_shift = core.get_option("SCF", "LEVEL_SHIFT")
                if level_shift > 0 and Dnorm > core.get_option(
                        'SCF', 'LEVEL_SHIFT_CUTOFF'):
                    status.append("SHIFT")
                    self.form_C(level_shift)
                else:
                    self.form_C()
                core.timer_off("HF: Form C")

                if self.MOM_performed_:
                    status.append("MOM")

                if self.frac_performed_:
                    status.append("FRAC")

                if incfock_performed:
                    status.append("INCFOCK")

                # Reset occupations if necessary
                if (self.iteration_ == 0) and self.reset_occ_:
                    self.reset_occupation()
                    self.find_occupation()

        # Form new density matrix
        core.timer_on("HF: Form D")
        self.form_D()
        core.timer_off("HF: Form D")

        self.set_variable("SCF ITERATION ENERGY", SCFE)
        core.set_variable("SCF D NORM", Dnorm)

        # After we've built the new D, damp the update
        if (damping_enabled and self.iteration_ > 1
                and Dnorm > core.get_option('SCF', 'DAMPING_CONVERGENCE')):
            damping_percentage = core.get_option('SCF', "DAMPING_PERCENTAGE")
            self.damping_update(damping_percentage * 0.01)
            status.append("DAMP={}%".format(round(damping_percentage)))

        if core.has_option_changed("SCF", "ORBITALS_WRITE"):
            filename = core.get_option("SCF", "ORBITALS_WRITE")
            self.to_file(filename)

        if verbose > 3:
            self.Ca().print_out()
            self.Cb().print_out()
            self.Da().print_out()
            self.Db().print_out()

        # Print out the iteration
        core.print_out(
            "   @%s%s iter %3s: %20.14f   %12.5e   %-11.5e %s\n" %
            ("DF-" if is_dfjk else "", reference, "SAD" if
             ((self.iteration_ == 0) and self.sad_) else self.iteration_, SCFE,
             Ediff, Dnorm, '/'.join(status)))

        # if a an excited MOM is requested but not started, don't stop yet
        # Note that MOM_performed_ just checks initialization, and our convergence measures used the pre-MOM orbitals
        if self.MOM_excited_ and ((not self.MOM_performed_) or self.iteration_
                                  == core.get_option('SCF', "MOM_START")):
            continue

        # if a fractional occupation is requested but not started, don't stop yet
        if frac_enabled and not self.frac_performed_:
            continue

        # Call any postiteration callbacks
        if not ((self.iteration_ == 0) and self.sad_) and _converged(
                Ediff, Dnorm, e_conv=e_conv, d_conv=d_conv):
            break
        if self.iteration_ >= core.get_option('SCF', 'MAXITER'):
            raise SCFConvergenceError("""SCF iterations""", self.iteration_,
                                      self, Ediff, Dnorm)
Beispiel #16
0
def run_psi4(para: dict):
    try:
        if (not para.__contains__("multiplicity")):
            para["multiplicity"] = 1

        if (not para.__contains__("charge")):
            para["charge"] = 0

        if (not para.__contains__("basis")):
            para["basis"] = 0

        if (not para.__contains__("EQ_TOLERANCE")):
            para["EQ_TOLERANCE"] = 1e-8

        str_mol = para["mol"]
        str_mol = str_mol + "\n symmetry c1"

        mol = geometry(str_mol, "mol")

        core.IO.set_default_namespace("mol")
        mol.set_multiplicity(para["multiplicity"])
        mol.set_molecular_charge(para["charge"])

        core.set_global_option("BASIS", para["basis"])

        core.set_global_option("SCF_TYPE", "pk")

        core.set_global_option("SOSCF", "false")

        core.set_global_option("FREEZE_CORE", "false")

        core.set_global_option("DF_SCF_GUESS", "false")

        core.set_global_option("OPDM", "true")

        core.set_global_option("TPDM", "true")

        core.set_global_option("MAXITER", 1e6)

        core.set_global_option("NUM_AMPS_PRINT", 1e6)

        core.set_global_option("R_CONVERGENCE", 1e-6)

        core.set_global_option("D_CONVERGENCE", 1e-6)

        core.set_global_option("E_CONVERGENCE", 1e-6)

        core.set_global_option("DAMPING_PERCENTAGE", 0)

        if mol.multiplicity == 1:
            core.set_global_option("REFERENCE", "rhf")
            core.set_global_option("GUESS", "sad")
        else:
            core.set_global_option("REFERENCE", "rohf")
            core.set_global_option("GUESS", "gwh")
        hf_energy, hf_wavefunction = energy('scf', return_wfn=True)
    except Exception as exception:
        return (-1, traceback.format_exc())
    else:
        nuclear_repulsion = mol.nuclear_repulsion_energy()
        canonical_orbitals = numpy.asarray(hf_wavefunction.Ca())

        mints = core.MintsHelper(hf_wavefunction.basisset())
        fermion_str = get_molecular_hamiltonian(mints, canonical_orbitals,
                                                nuclear_repulsion,
                                                para["EQ_TOLERANCE"])
        return (0, fermion_str)
Beispiel #17
0
def cpscf_linear_response(wfn, *args, **kwargs):
    """
    Compute the static properties from a reference wavefunction. The currently implemented properties are
      - dipole polarizability
      - quadrupole polarizability

    Parameters
    ----------
    wfn : psi4 wavefunction
        The reference wavefunction.
    args : list
        The list of arguments. For each argument, such as ``dipole polarizability``, will return the corresponding
        response. The user may also choose to pass a list or tuple of custom vectors.
    kwargs : dict
        Options that control how the response is computed. The following options are supported (with default values):
          - ``conv_tol``: 1e-5
          - ``max_iter``: 10
          - ``print_lvl``: 2

    Returns
    -------
    responses : list
        The list of responses.
    """
    mints = core.MintsHelper(wfn.basisset())

    # list of dictionaries to control response calculations, count how many user-supplied vectors we have
    complete_dict = []
    n_user = 0

    for arg in args:

        # for each string keyword, append the appropriate dictionary (vide supra) to our list
        if isinstance(arg, str):
            ret = property_dicts.get(arg)
            if ret:
                complete_dict.append(ret)
            else:
                raise ValidationError(
                    'Do not understand {}. Abort.'.format(arg))

        # the user passed a list of vectors. absorb them into a dictionary
        elif isinstance(arg, tuple) or isinstance(arg, list):
            complete_dict.append({
                'name':
                'User Vectors',
                'length':
                len(arg),
                'vectors':
                arg,
                'vector names': [
                    'User Vector {}_{}'.format(n_user, i)
                    for i in range(len(arg))
                ]
            })
            n_user += len(arg)

        # single vector passed. stored in a dictionary as a list of length 1 (can be handled as the case above that way)
        # note: the length is set to '0' to designate that it was not really passed as a list
        else:
            complete_dict.append({
                'name':
                'User Vector',
                'length':
                0,
                'vectors': [arg],
                'vector names': ['User Vector {}'.format(n_user)]
            })
            n_user += 1

    # vectors will be passed to the cphf solver, vector_names stores the corresponding names
    vectors = []
    vector_names = []

    # construct the list of vectors. for the keywords, fetch the appropriate tensors from MintsHelper
    for prop in complete_dict:
        if 'User' in prop['name']:
            for name, vec in zip(prop['vector names'], prop['vectors']):
                vectors.append(vec)
                vector_names.append(name)

        else:
            tmp_vectors = prop['mints_function'](mints)
            for tmp in tmp_vectors:
                tmp.scale(-2.0)  # RHF only
                vectors.append(tmp)
                vector_names.append(tmp.name)

    # do we have any vectors to work with?
    if len(vectors) == 0:
        raise ValidationError('I have no vectors to work with. Aborting.')

    # print information on module, vectors that will be used
    _print_header(complete_dict, n_user)

    # fetch wavefunction information
    nbf = wfn.nmo()
    ndocc = wfn.nalpha()
    nvirt = nbf - ndocc

    c_occ = wfn.Ca_subset("AO", "OCC")
    c_vir = wfn.Ca_subset("AO", "VIR")

    # the vectors need to be in the MO basis. if they have the shape nbf x nbf, transform.
    for i in range(len(vectors)):
        shape = vectors[i].shape

        if shape == (nbf, nbf):
            vectors[i] = core.triplet(c_occ, vectors[i], c_vir, True, False,
                                      False)

        # verify that this vector already has the correct shape
        elif shape != (ndocc, nvirt):
            raise ValidationError(
                'ERROR: "{}" has an unrecognized shape. Must be either ({}, {}) or ({}, {})'
                .format(vector_names[i], nbf, nbf, ndocc, nvirt))

    # compute response vectors for each input vector
    params = [
        kwargs.pop("conv_tol", 1.e-5),
        kwargs.pop("max_iter", 10),
        kwargs.pop("print_lvl", 2)
    ]

    responses = wfn.cphf_solve(vectors, *params)

    # zip vectors, responses for easy access
    vectors = {k: v for k, v in zip(vector_names, vectors)}
    responses = {k: v for k, v in zip(vector_names, responses)}

    # compute response values, format output
    output = []
    for prop in complete_dict:

        # try to replicate the data structure of the input
        if 'User' in prop['name']:
            if prop['length'] == 0:
                output.append(responses[prop['vector names'][0]])
            else:
                buf = []
                for name in prop['vector names']:
                    buf.append(responses[name])
                output.append(buf)

        else:
            names = prop['vector names']
            dim = len(names)

            buf = np.zeros((dim, dim))

            for i, i_name in enumerate(names):
                for j, j_name in enumerate(names):
                    buf[i, j] = -1.0 * vectors[i_name].vector_dot(
                        responses[j_name])

            output.append(buf)

    _print_output(complete_dict, output)

    return output
Beispiel #18
0
def scf_initialize(self):
    """Specialized initialization, compute integrals and does everything to prepare for iterations"""

    # Figure out memory distributions

    # Get memory in terms of doubles
    total_memory = (core.get_memory() /
                    8) * core.get_global_option("SCF_MEM_SAFETY_FACTOR")

    # Figure out how large the DFT collocation matrices are
    vbase = self.V_potential()
    if vbase:
        collocation_size = vbase.grid().collocation_size()
        if vbase.functional().ansatz() == 1:
            collocation_size *= 4  # First derivs
        elif vbase.functional().ansatz() == 2:
            collocation_size *= 10  # Second derivs
    else:
        collocation_size = 0

    # Change allocation for collocation matrices based on DFT type
    jk = _build_jk(self, total_memory)
    jk_size = jk.memory_estimate()

    # Give remaining to collocation
    if total_memory > jk_size:
        collocation_memory = total_memory - jk_size
    # Give up to 10% to collocation
    elif (total_memory * 0.1) > collocation_size:
        collocation_memory = collocation_size
    else:
        collocation_memory = total_memory * 0.1

    if collocation_memory > collocation_size:
        collocation_memory = collocation_size

    # Set constants
    self.iteration_ = 0
    self.memory_jk_ = int(total_memory - collocation_memory)
    self.memory_collocation_ = int(collocation_memory)

    if self.get_print():
        core.print_out("  ==> Integral Setup <==\n\n")

    # Initialize EFP
    efp_enabled = hasattr(self.molecule(), 'EFP')
    if efp_enabled:
        # EFP: Set QM system, options, and callback. Display efp geom in [A]
        efpobj = self.molecule().EFP
        core.print_out(efpobj.banner())
        core.print_out(
            efpobj.geometry_summary(units_to_bohr=constants.bohr2angstroms))

        efpptc, efpcoords, efpopts = get_qm_atoms_opts(self.molecule())
        efpobj.set_point_charges(efpptc, efpcoords)
        efpobj.set_opts(efpopts, label='psi', append='psi')

        efpobj.set_electron_density_field_fn(efp_field_fn)

    # Initialize all integrals and perform the first guess
    if self.attempt_number_ == 1:
        mints = core.MintsHelper(self.basisset())

        self.initialize_jk(self.memory_jk_, jk=jk)
        if self.V_potential():
            self.V_potential().build_collocation_cache(
                self.memory_collocation_)
        core.timer_on("HF: Form core H")
        self.form_H()
        core.timer_off("HF: Form core H")

        if efp_enabled:
            # EFP: Add in permanent moment contribution and cache
            core.timer_on("HF: Form Vefp")
            verbose = core.get_option('SCF', "PRINT")
            Vefp = modify_Fock_permanent(self.molecule(),
                                         mints,
                                         verbose=verbose - 1)
            Vefp = core.Matrix.from_array(Vefp)
            self.H().add(Vefp)
            Horig = self.H().clone()
            self.Horig = Horig
            core.print_out(
                "  QM/EFP: iterating Total Energy including QM/EFP Induction\n"
            )
            core.timer_off("HF: Form Vefp")

        core.timer_on("HF: Form S/X")
        self.form_Shalf()
        core.timer_off("HF: Form S/X")

        core.print_out("\n  ==> Pre-Iterations <==\n\n")

        core.timer_on("HF: Guess")
        self.guess()
        core.timer_off("HF: Guess")
        # Print out initial docc/socc/etc data
        if self.get_print():
            lack_occupancy = core.get_local_option('SCF', 'GUESS') in ['SAD']
            if core.get_global_option('GUESS') in ['SAD']:
                lack_occupancy = core.get_local_option('SCF',
                                                       'GUESS') in ['AUTO']
                self.print_preiterations(small=lack_occupancy)
            else:
                self.print_preiterations(small=lack_occupancy)

    else:
        # We're reading the orbitals from the previous set of iterations.
        self.form_D()
        self.set_energies("Total Energy", self.compute_initial_E())

    # turn off VV10 for iterations
    if core.get_option(
            'SCF', "DFT_VV10_POSTSCF") and self.functional().vv10_b() > 0.0:
        core.print_out("  VV10: post-SCF option active \n \n")
        self.functional().set_lock(False)
        self.functional().set_do_vv10(False)
        self.functional().set_lock(True)

    # Print iteration header
    is_dfjk = core.get_global_option('SCF_TYPE').endswith('DF')
    diis_rms = core.get_option('SCF', 'DIIS_RMS_ERROR')
    core.print_out("  ==> Iterations <==\n\n")
    core.print_out(
        "%s                        Total Energy        Delta E     %s |[F,P]|\n\n"
        % ("   " if is_dfjk else "", "RMS" if diis_rms else "MAX"))
Beispiel #19
0
def cpscf_linear_response(wfn, *args, **kwargs):
    """
    Compute the static properties from a reference wavefunction. The currently implemented properties are
      - dipole polarizability
      - quadrupole polarizability

    Parameters
    ----------
    wfn : psi4 wavefunction
        The reference wavefunction.
    args : list
        The list of arguments. For each argument, such as ``dipole polarizability``, will return the corresponding
        response.
    kwargs : dict
        Options that control how the response is computed. The following options are supported (with default values):
          - ``conv_tol``: 1e-5
          - ``max_iter``: 10
          - ``print_lvl``: 2

    Returns
    -------
    responses : list
        The list of response tensors.
    """
    mints = core.MintsHelper(wfn.basisset())

    # list of dictionaries to control response calculations
    complete_dict = []

    for arg in args:
        # for each string keyword, append the appropriate dictionary (vide supra) to our list
        if not isinstance(arg, str):
            # TODO: better to raise TypeError?
            raise ValidationError("Property name must be of type string.")
        ret = property_dicts.get(arg)
        if ret:
            complete_dict.append(ret)
        else:
            raise ValidationError(f"Do not understand '{arg}'.")

    # vectors will be passed to the cphf solver, vector_names stores the corresponding names
    vectors = []
    vector_names = []
    restricted = wfn.same_a_b_orbs()

    # construct the list of vectors. for the keywords, fetch the appropriate tensors from MintsHelper
    for prop in complete_dict:
        tmp_vectors = prop['mints_function'](mints)
        for tmp in tmp_vectors:
            tmp.scale(-1.0)
            vectors.append(tmp)
            vector_names.append(tmp.name)

    # do we have any vectors to work with?
    if len(vectors) == 0:
        raise ValidationError('No vectors to work with. Aborting.')

    # print information on module, vectors that will be used
    _print_header(complete_dict)

    nbf = wfn.basisset().nbf()
    Co = [wfn.Ca_subset("AO", "OCC"), wfn.Cb_subset("AO", "OCC")]
    Cv = [wfn.Ca_subset("AO", "VIR"), wfn.Cb_subset("AO", "VIR")]
    vectors_transformed = []
    for vector in vectors:
        if vector.shape != (nbf, nbf):
            raise ValidationError(
                f"Vector must be of shape ({nbf}, {nbf}) for transformation"
                " to the SO basis.")
        v_a = core.triplet(Co[0], vector, Cv[0], True, False, False)
        vectors_transformed.append(v_a)
        if not restricted:
            v_b = core.triplet(Co[1], vector, Cv[1], True, False, False)
            vectors_transformed.append(v_b)

    # compute response vectors for each input vector
    params = [
        kwargs.pop("conv_tol", 1.e-5),
        kwargs.pop("max_iter", 10),
        kwargs.pop("print_lvl", 2)
    ]

    responses_list = wfn.cphf_solve(vectors_transformed, *params)

    # zip vectors, responses for easy access
    if restricted:
        vectors = {
            f"{k}_a": v
            for k, v in zip(vector_names, vectors_transformed)
        }
        responses = {f"{k}_a": v for k, v in zip(vector_names, responses_list)}
    else:
        vectors = {
            f"{k}_a": v
            for k, v in zip(vector_names, vectors_transformed[::2])
        }
        vectors.update({
            f"{k}_b": v
            for k, v in zip(vector_names, vectors_transformed[1::2])
        })
        responses = {
            f"{k}_a": v
            for k, v in zip(vector_names, responses_list[::2])
        }
        responses.update(
            {f"{k}_b": v
             for k, v in zip(vector_names, responses_list[1::2])})
    # compute response values, format output
    output = []
    pref = -4.0 if restricted else -2.0
    for prop in complete_dict:
        names = prop['vector names']
        dim = len(names)
        buf = np.zeros((dim, dim))
        for i, i_name in enumerate(names):
            for j, j_name in enumerate(names):
                buf[i, j] = pref * vectors[f"{i_name}_a"].vector_dot(
                    responses[f"{j_name}_a"])
                if not restricted:
                    buf[i, j] += pref * vectors[f"{i_name}_b"].vector_dot(
                        responses[f"{j_name}_b"])
        output.append(buf)

    _print_output(complete_dict, output)

    return output
Beispiel #20
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
Beispiel #21
0
def compute_sapt_sf(dimer, jk, wfn_A, wfn_B, do_print=True):
    """
    Computes Elst and Spin-Flip SAPT0 for ROHF wavefunctions
    """

    if do_print:
        core.print_out("\n  ==> Preparing SF-SAPT Data Cache <== \n\n")
        jk.print_header()

    ### Build intermediates

    # Pull out Wavefunction A quantities
    ndocc_A = wfn_A.doccpi().sum()
    nsocc_A = wfn_A.soccpi().sum()

    Cocc_A = np.asarray(wfn_A.Ca_subset("AO", "OCC"))
    Ci = Cocc_A[:, :ndocc_A]
    Ca = Cocc_A[:, ndocc_A:]
    Pi = np.dot(Ci, Ci.T)
    Pa = np.dot(Ca, Ca.T)

    mints = core.MintsHelper(wfn_A.basisset())
    V_A = mints.ao_potential()

    # Pull out Wavefunction B quantities
    ndocc_B = wfn_B.doccpi().sum()
    nsocc_B = wfn_B.soccpi().sum()

    Cocc_B = np.asarray(wfn_B.Ca_subset("AO", "OCC"))
    Cj = Cocc_B[:, :ndocc_B]
    Cb = Cocc_B[:, ndocc_B:]
    Pj = np.dot(Cj, Cj.T)
    Pb = np.dot(Cb, Cb.T)

    mints = core.MintsHelper(wfn_B.basisset())
    V_B = mints.ao_potential()

    # Pull out generic quantities
    S = np.asarray(wfn_A.S())

    intermonomer_nuclear_repulsion = dimer.nuclear_repulsion_energy()
    intermonomer_nuclear_repulsion -= wfn_A.molecule(
    ).nuclear_repulsion_energy()
    intermonomer_nuclear_repulsion -= wfn_B.molecule(
    ).nuclear_repulsion_energy()

    num_el_A = (2 * ndocc_A + nsocc_A)
    num_el_B = (2 * ndocc_B + nsocc_B)

    ### Build JK Terms
    if do_print:
        core.print_out("\n  ==> Computing required JK matrices <== \n\n")

    # Writen so that we can reorganize order to save on DF-JK cost.
    pairs = [("ii", Ci, None, Ci), ("ij", Ci, _chain_dot(Ci.T, S, Cj), Cj),
             ("jj", Cj, None, Cj), ("aa", Ca, None, Ca),
             ("aj", Ca, _chain_dot(Ca.T, S, Cj), Cj),
             ("ib", Ci, _chain_dot(Ci.T, S, Cb), Cb), ("bb", Cb, None, Cb),
             ("ab", Ca, _chain_dot(Ca.T, S, Cb), Cb)]

    # Reorganize
    names = [x[0] for x in pairs]
    Cleft = [x[1] for x in pairs]
    rotations = [x[2] for x in pairs]
    Cright = [x[3] for x in pairs]

    tmp_J, tmp_K = _sf_compute_JK(jk, Cleft, Cright, rotations)

    J = {key: val for key, val in zip(names, tmp_J)}
    K = {key: val for key, val in zip(names, tmp_K)}

    ### Compute Terms
    if do_print:
        core.print_out(
            "\n  ==> Computing Spin-Flip Exchange and Electrostatics <== \n\n")

    w_A = V_A + 2 * J["ii"] + J["aa"]
    w_B = V_B + 2 * J["jj"] + J["bb"]

    h_Aa = V_A + 2 * J["ii"] + J["aa"] - K["ii"] - K["aa"]
    h_Ab = V_A + 2 * J["ii"] + J["aa"] - K["ii"]

    h_Ba = V_B + 2 * J["jj"] + J["bb"] - K["jj"]
    h_Bb = V_B + 2 * J["jj"] + J["bb"] - K["jj"] - K["bb"]

    ### Build electrostatics

    # socc/socc term
    two_el_repulsion = np.vdot(Pa, J["bb"])
    attractive_a = np.vdot(V_A, Pb) * nsocc_A / num_el_A
    attractive_b = np.vdot(V_B, Pa) * nsocc_B / num_el_B
    nuclear_repulsion = intermonomer_nuclear_repulsion * nsocc_A * nsocc_B / (
        num_el_A * num_el_B)
    elst_abab = two_el_repulsion + attractive_a + attractive_b + nuclear_repulsion

    # docc/socc term
    two_el_repulsion = np.vdot(Pi, J["bb"])
    attractive_a = np.vdot(V_A, Pb) * ndocc_A / num_el_A
    attractive_b = np.vdot(V_B, Pi) * nsocc_B / num_el_B
    nuclear_repulsion = intermonomer_nuclear_repulsion * ndocc_A * nsocc_B / (
        num_el_A * num_el_B)
    elst_ibib = 2 * (two_el_repulsion + attractive_a + attractive_b +
                     nuclear_repulsion)

    # socc/docc term
    two_el_repulsion = np.vdot(Pa, J["jj"])
    attractive_a = np.vdot(V_A, Pj) * nsocc_A / num_el_A
    attractive_b = np.vdot(V_B, Pa) * ndocc_B / num_el_B
    nuclear_repulsion = intermonomer_nuclear_repulsion * nsocc_A * ndocc_B / (
        num_el_A * num_el_B)
    elst_jaja = 2 * (two_el_repulsion + attractive_a + attractive_b +
                     nuclear_repulsion)

    # docc/docc term
    two_el_repulsion = np.vdot(Pi, J["jj"])
    attractive_a = np.vdot(V_A, Pj) * ndocc_A / num_el_A
    attractive_b = np.vdot(V_B, Pi) * ndocc_B / num_el_B
    nuclear_repulsion = intermonomer_nuclear_repulsion * ndocc_A * ndocc_B / (
        num_el_A * num_el_B)
    elst_ijij = 4 * (two_el_repulsion + attractive_a + attractive_b +
                     nuclear_repulsion)

    elst = elst_abab + elst_ibib + elst_jaja + elst_ijij
    # print(print_sapt_var("Elst,10", elst))

    ### Start diagonal exchange

    exch_diag = 0.0
    exch_diag -= np.vdot(Pj, 2 * K["ii"] + K["aa"])
    exch_diag -= np.vdot(Pb, K["ii"])
    exch_diag -= np.vdot(_chain_dot(Pi, S, Pj), (h_Aa + h_Ab + h_Ba + h_Bb))
    exch_diag -= np.vdot(_chain_dot(Pa, S, Pj), (h_Aa + h_Ba))
    exch_diag -= np.vdot(_chain_dot(Pi, S, Pb), (h_Ab + h_Bb))

    exch_diag += 2.0 * np.vdot(_chain_dot(Pj, S, Pi, S, Pb), w_A)
    exch_diag += 2.0 * np.vdot(_chain_dot(Pj, S, Pi, S, Pj), w_A)
    exch_diag += np.vdot(_chain_dot(Pb, S, Pi, S, Pb), w_A)
    exch_diag += np.vdot(_chain_dot(Pj, S, Pa, S, Pj), w_A)

    exch_diag += 2.0 * np.vdot(_chain_dot(Pi, S, Pj, S, Pi), w_B)
    exch_diag += 2.0 * np.vdot(_chain_dot(Pi, S, Pj, S, Pa), w_B)
    exch_diag += np.vdot(_chain_dot(Pi, S, Pb, S, Pi), w_B)
    exch_diag += np.vdot(_chain_dot(Pa, S, Pj, S, Pa), w_B)

    exch_diag -= 2.0 * np.vdot(_chain_dot(Pi, S, Pj), K["ij"])
    exch_diag -= 2.0 * np.vdot(_chain_dot(Pa, S, Pj), K["ij"])
    exch_diag -= 2.0 * np.vdot(_chain_dot(Pi, S, Pb), K["ij"])

    exch_diag -= np.vdot(_chain_dot(Pa, S, Pj), K["aj"])
    exch_diag -= np.vdot(_chain_dot(Pi, S, Pb), K["ib"])
    # print(print_sapt_var("Exch10,offdiagonal", exch_diag))

    ### Start off-diagonal exchange

    exch_offdiag = 0.0
    exch_offdiag -= np.vdot(Pb, K["aa"])
    exch_offdiag -= np.vdot(_chain_dot(Pa, S, Pb), (h_Aa + h_Bb))
    exch_offdiag += np.vdot(_chain_dot(Pa, S, Pj), K["bb"])
    exch_offdiag += np.vdot(_chain_dot(Pi, S, Pb), K["aa"])

    exch_offdiag += 2.0 * np.vdot(_chain_dot(Pj, S, Pa, S, Pb), w_A)
    exch_offdiag += np.vdot(_chain_dot(Pb, S, Pa, S, Pb), w_A)

    exch_offdiag += 2.0 * np.vdot(_chain_dot(Pi, S, Pb, S, Pa), w_B)
    exch_offdiag += np.vdot(_chain_dot(Pa, S, Pb, S, Pa), w_B)

    exch_offdiag -= 2.0 * np.vdot(_chain_dot(Pa, S, Pb), K["ij"])
    exch_offdiag -= 2.0 * np.vdot(_chain_dot(Pa, S, Pb), K["ib"])
    exch_offdiag -= 2.0 * np.vdot(_chain_dot(Pa, S, Pj), K["ab"])
    exch_offdiag -= 2.0 * np.vdot(_chain_dot(Pa, S, Pj), K["ib"])

    exch_offdiag -= np.vdot(_chain_dot(Pa, S, Pb), K["ab"])
    # print(print_sapt_var("Exch10,off-diagonal", exch_offdiag))
    # print(print_sapt_var("Exch10(S^2)", exch_offdiag + exch_diag))

    ret_values = OrderedDict({
        "Elst10":
        elst,
        "Exch10(S^2) [diagonal]":
        exch_diag,
        "Exch10(S^2) [off-diagonal]":
        exch_offdiag,
        "Exch10(S^2) [highspin]":
        exch_offdiag + exch_diag,
    })

    return ret_values
Beispiel #22
0
def harmonic_analysis(hess,
                      geom,
                      mass,
                      basisset,
                      irrep_labels,
                      project_trans=True,
                      project_rot=True):
    """Like so much other Psi4 goodness, originally by @andysim

    Parameters
    ----------
    hess : ndarray of float
        (3*nat, 3*nat) non-mass-weighted Hessian in atomic units, [Eh/a0/a0].
    geom : ndarray of float
        (nat, 3) geometry [a0] at which Hessian computed.
    mass : ndarray of float
        (nat,) atomic masses [u].
    basisset : psi4.core.BasisSet
        Basis set object (can be dummy, e.g., STO-3G) for SALCs.
    irrep_labels : list of str
        Irreducible representation labels.
    project_trans : bool, optional
        Idealized translations projected out of final vibrational analysis.
    project_rot : bool, optional
        Idealized rotations projected out of final vibrational analysis.

    Returns
    -------
    dict, text
        Returns dictionary of vibration QCAspect objects (fields: lbl units data comment).
        Also returns text suitable for printing.

    .. _`table:vibaspectinfo`:

    +---------------+--------------------------------------------+-----------+------------------------------------------------------+
    | key           | description (lbl & comment)                | units     | data (real/imaginary modes)                          |
    +===============+============================================+===========+======================================================+
    | omega         | frequency                                  | cm^-1     | nd.array(ndof) complex (real/imag)                   |
    +---------------+--------------------------------------------+-----------+------------------------------------------------------+
    | q             | normal mode, normalized mass-weighted      | a0 u^1/2  | ndarray(ndof, ndof) float                            |
    +---------------+--------------------------------------------+-----------+------------------------------------------------------+
    | w             | normal mode, un-mass-weighted              | a0        | ndarray(ndof, ndof) float                            |
    +---------------+--------------------------------------------+-----------+------------------------------------------------------+
    | x             | normal mode, normalized un-mass-weighted   | a0        | ndarray(ndof, ndof) float                            |
    +---------------+--------------------------------------------+-----------+------------------------------------------------------+
    | degeneracy    | degree of degeneracy                       |           | ndarray(ndof) int                                    |
    +---------------+--------------------------------------------+-----------+------------------------------------------------------+
    | TRV           | translation/rotation/vibration             |           | ndarray(ndof) str 'TR' or 'V' or '-' for partial     |
    +---------------+--------------------------------------------+-----------+------------------------------------------------------+
    | gamma         | irreducible representation                 |           | ndarray(ndof) str irrep or None if unclassifiable    |
    +---------------+--------------------------------------------+-----------+------------------------------------------------------+
    | mu            | reduced mass                               | u         | ndarray(ndof) float (+/+)                            |
    +---------------+--------------------------------------------+-----------+------------------------------------------------------+
    | k             | force constant                             | mDyne/A   | ndarray(ndof) float (+/-)                            |
    +---------------+--------------------------------------------+-----------+------------------------------------------------------+
    | DQ0           | RMS deviation v=0                          | a0 u^1/2  | ndarray(ndof) float (+/0)                            |
    +---------------+--------------------------------------------+-----------+------------------------------------------------------+
    | Qtp0          | Turning point v=0                          | a0 u^1/2  | ndarray(ndof) float (+/0)                            |
    +---------------+--------------------------------------------+-----------+------------------------------------------------------+
    | Xtp0          | Turning point v=0                          | a0        | ndarray(ndof) float (+/0)                            |
    +---------------+--------------------------------------------+-----------+------------------------------------------------------+
    | theta_vib     | char temp                                  | K         | ndarray(ndof) float (+/0)                            |
    +---------------+--------------------------------------------+-----------+------------------------------------------------------+

    Examples
    --------
    # displacement of first atom in highest energy mode
    >>> vibinfo['x'].data[:, -1].reshape(nat, 3)[0]

    # remove translations & rotations
    >>> vibonly = filter_nonvib(vibinfo)

    """
    from psi4 import core

    if (mass.shape[0] == geom.shape[0] == (hess.shape[0] // 3) ==
        (hess.shape[1] // 3)) and (geom.shape[1] == 3):
        pass
    else:
        raise ValidationError(
            """Dimension mismatch among mass ({}), geometry ({}), and Hessian ({})"""
            .format(mass.shape, geom.shape, hess.shape))

    def mat_symm_info(a, atol=1e-14, lbl='array', stol=None):
        symm = np.allclose(a, a.T, atol=atol)
        herm = np.allclose(a, a.conj().T, atol=atol)
        ivrt = a.shape[0] - np.linalg.matrix_rank(a, tol=stol)
        return """  {:32} Symmetric? {}   Hermitian? {}   Lin Dep Dim? {:2}""".format(
            lbl + ':', symm, herm, ivrt)

    def vec_in_space(vec, space, tol=1.0e-4):
        merged = np.vstack((space, vec))
        u, s, v = np.linalg.svd(merged)
        return (s[-1] < tol)

    vibinfo = {}
    text = []

    nat = len(mass)
    text.append("""\n\n  ==> Harmonic Vibrational Analysis <==\n""")

    if nat == 1:
        nrt_expected = 3
    elif np.linalg.matrix_rank(geom) == 1:
        nrt_expected = 5
    else:
        nrt_expected = 6

    nmwhess = hess.copy()
    text.append(
        mat_symm_info(nmwhess, lbl='non-mass-weighted Hessian') + ' (0)')

    # get SALC object, possibly w/o trans & rot
    mints = core.MintsHelper(basisset)
    cdsalcs = mints.cdsalcs(0xFF, project_trans, project_rot)

    Uh = collections.OrderedDict()
    for h, lbl in enumerate(irrep_labels):
        tmp = np.asarray(cdsalcs.matrix_irrep(h))
        if tmp.size > 0:
            Uh[lbl] = tmp

    # form projector of translations and rotations
    space = ('T' if project_trans else '') + ('R' if project_rot else '')
    TRspace = _get_TR_space(mass, geom, space=space, tol=LINEAR_A_TOL)
    nrt = TRspace.shape[0]
    text.append(
        '  projection of translations ({}) and rotations ({}) removed {} degrees of freedom ({})'
        .format(project_trans, project_rot, nrt, nrt_expected))

    P = np.identity(3 * nat)
    for irt in TRspace:
        P -= np.outer(irt, irt)
    text.append(mat_symm_info(P, lbl='total projector') + ' ({})'.format(nrt))

    # mass-weight & solve
    sqrtmmm = np.repeat(np.sqrt(mass), 3)
    sqrtmmminv = np.divide(1.0, sqrtmmm)
    mwhess = np.einsum('i,ij,j->ij', sqrtmmminv, nmwhess, sqrtmmminv)
    text.append(mat_symm_info(mwhess, lbl='mass-weighted Hessian') + ' (0)')

    pre_force_constant_au = np.linalg.eigvalsh(mwhess)

    idx = np.argsort(pre_force_constant_au)
    pre_force_constant_au = pre_force_constant_au[idx]
    uconv_cm_1 = np.sqrt(qcel.constants.na * qcel.constants.hartree2J *
                         1.0e19) / (2 * np.pi * qcel.constants.c *
                                    qcel.constants.bohr2angstroms)
    pre_frequency_cm_1 = np.lib.scimath.sqrt(
        pre_force_constant_au) * uconv_cm_1

    pre_lowfreq = np.where(np.real(pre_frequency_cm_1) < 100.0)[0]
    pre_lowfreq = np.append(
        pre_lowfreq, np.arange(nrt_expected))  # catch at least nrt modes
    for lf in set(pre_lowfreq):
        vlf = pre_frequency_cm_1[lf]
        if vlf.imag > vlf.real:
            text.append(
                '  pre-proj  low-frequency mode: {:9.4f}i [cm^-1]'.format(
                    vlf.real, vlf.imag))
        else:
            text.append(
                '  pre-proj  low-frequency mode: {:9.4f}  [cm^-1]'.format(
                    vlf.real, ''))
    text.append('  pre-proj  all modes:' +
                str(_format_omega(pre_frequency_cm_1, 4)))

    # project & solve
    mwhess_proj = np.dot(P.T, mwhess).dot(P)
    text.append(
        mat_symm_info(mwhess_proj, lbl='projected mass-weighted Hessian') +
        ' ({})'.format(nrt))

    #print('projhess = ', np.array_repr(mwhess_proj))
    force_constant_au, qL = np.linalg.eigh(mwhess_proj)

    # expected order for vibrations is steepest downhill to steepest uphill
    idx = np.argsort(force_constant_au)
    force_constant_au = force_constant_au[idx]
    qL = qL[:, idx]
    qL = _phase_cols_to_max_element(qL)
    vibinfo['q'] = QCAspect('normal mode', 'a0 u^1/2', qL,
                            'normalized mass-weighted')

    # frequency, LAB II.17
    frequency_cm_1 = np.lib.scimath.sqrt(force_constant_au) * uconv_cm_1
    vibinfo['omega'] = QCAspect('frequency', 'cm^-1', frequency_cm_1, '')

    # degeneracies
    ufreq, uinv, ucts = np.unique(np.around(frequency_cm_1, 2),
                                  return_inverse=True,
                                  return_counts=True)
    vibinfo['degeneracy'] = QCAspect('degeneracy', '', ucts[uinv], '')

    # look among the symmetry subspaces h for one to which the normco
    #   of vib does *not* add an extra dof to the vector space
    active = []
    irrep_classification = []
    for idx, vib in enumerate(frequency_cm_1):

        if vec_in_space(qL[:, idx], TRspace, 1.0e-4):
            active.append('TR')
            irrep_classification.append(None)

        else:
            active.append('V')

            for h in Uh.keys():
                if vec_in_space(qL[:, idx], Uh[h], 1.0e-4):
                    irrep_classification.append(h)
                    break
            else:
                irrep_classification.append(None)

                # catch partial Hessians
                if np.linalg.norm(vib) < 1.e-3:
                    active[-1] = '-'

    vibinfo['TRV'] = QCAspect('translation/rotation/vibration', '', active, '')
    vibinfo['gamma'] = QCAspect('irreducible representation', '',
                                irrep_classification, '')

    lowfreq = np.where(np.real(frequency_cm_1) < 100.0)[0]
    lowfreq = np.append(lowfreq,
                        np.arange(nrt_expected))  # catch at least nrt modes
    for lf in set(lowfreq):
        vlf = frequency_cm_1[lf]
        if vlf.imag > vlf.real:
            text.append(
                '  post-proj low-frequency mode: {:9.4f}i [cm^-1] ({})'.format(
                    vlf.imag, active[lf]))
        else:
            text.append(
                '  post-proj low-frequency mode: {:9.4f}  [cm^-1] ({})'.format(
                    vlf.real, active[lf]))
    text.append('  post-proj  all modes:' +
                str(_format_omega(frequency_cm_1, 4)) + '\n')
    if project_trans and not project_rot:
        text.append(
            '  Note that "Vibration"s include {} un-projected rotation-like modes.'
            .format(nrt_expected - 3))
    elif not project_trans and not project_rot:
        text.append(
            '  Note that "Vibration"s include {} un-projected rotation-like and translation-like modes.'
            .format(nrt_expected))

    # general conversion factors, LAB II.11
    uconv_K = (qcel.constants.h * qcel.constants.na *
               1.0e21) / (8 * np.pi * np.pi * qcel.constants.c)
    uconv_S = np.sqrt(
        (qcel.constants.c * (2 * np.pi * qcel.constants.bohr2angstroms)**2) /
        (qcel.constants.h * qcel.constants.na * 1.0e21))

    # normco & reduced mass, LAB II.14 & II.15
    wL = np.einsum('i,ij->ij', sqrtmmminv, qL)
    vibinfo['w'] = QCAspect('normal mode', 'a0', wL, 'un-mass-weighted')

    reduced_mass_u = np.divide(1.0, np.linalg.norm(wL, axis=0)**2)
    vibinfo['mu'] = QCAspect('reduced mass', 'u', reduced_mass_u, '')

    xL = np.sqrt(reduced_mass_u) * wL
    vibinfo['x'] = QCAspect('normal mode', 'a0', xL,
                            'normalized un-mass-weighted')

    # force constants, LAB II.16 (real compensates for earlier sqrt)
    uconv_mdyne_a = (0.1 *
                     (2 * np.pi * qcel.constants.c)**2) / qcel.constants.na
    force_constant_mdyne_a = reduced_mass_u * (
        frequency_cm_1 * frequency_cm_1).real * uconv_mdyne_a
    vibinfo['k'] = QCAspect('force constant', 'mDyne/A',
                            force_constant_mdyne_a, '')

    force_constant_cm_1_bb = reduced_mass_u * (
        frequency_cm_1 * frequency_cm_1).real * uconv_S * uconv_S
    QCAspect('force constant', 'cm^-1/a0^2', force_constant_cm_1_bb,
             "Hooke's Law")

    # turning points, LAB II.20 (real & zero since turning point silly for imag modes)
    nu = 0
    turning_point_rnc = np.sqrt(2.0 * nu + 1.0)

    with np.errstate(divide='ignore'):
        turning_point_bohr_u = turning_point_rnc / (
            np.sqrt(frequency_cm_1.real) * uconv_S)
    turning_point_bohr_u[turning_point_bohr_u == np.inf] = 0.
    vibinfo['Qtp0'] = QCAspect('Turning point v=0', 'a0 u^1/2',
                               turning_point_bohr_u, '')

    with np.errstate(divide='ignore'):
        turning_point_bohr = turning_point_rnc / (
            np.sqrt(frequency_cm_1.real * reduced_mass_u) * uconv_S)
    turning_point_bohr[turning_point_bohr == np.inf] = 0.
    vibinfo['Xtp0'] = QCAspect('Turning point v=0', 'a0', turning_point_bohr,
                               '')

    rms_deviation_bohr_u = turning_point_bohr_u / np.sqrt(2.0)
    vibinfo['DQ0'] = QCAspect('RMS deviation v=0', 'a0 u^1/2',
                              rms_deviation_bohr_u, '')

    # characteristic vibrational temperature, RAK thermo & https://en.wikipedia.org/wiki/Vibrational_temperature
    #   (imag freq zeroed)
    uconv_K = 100 * qcel.constants.h * qcel.constants.c / qcel.constants.kb
    vib_temperature_K = frequency_cm_1.real * uconv_K
    vibinfo['theta_vib'] = QCAspect('char temp', 'K', vib_temperature_K, '')

    return vibinfo, '\n'.join(text)
    def __init__(self,
                 molecule,
                 basis,
                 numpy_memory=2.e9,
                 scf_type="PK",
                 use_c=False):

        # Set defaults
        maxiter = 40
        E_conv = 1.0E-6
        D_conv = 1.0E-3

        # Integral generation from Psi4's MintsHelper
        start_time = time.time()
        self.basis_name = basis
        self.molecule = molecule
        self.basis = pc.BasisSet.build(self.molecule, "ORBITAL", basis)
        self.mints = pc.MintsHelper(self.basis)
        self.S = np.asarray(self.mints.ao_overlap())

        # Get nbf and ndocc for closed shell molecules
        self.nbf = self.S.shape[0]
        self.nel = sum(
            self.molecule.Z(n) for n in range(self.molecule.natom()))
        self.nel -= self.molecule.molecular_charge()

        if not (self.nel / 2.0).is_integer():
            raise ValueError(
                "RHF: Molecule did not have an even number of electrons!")

        self.ndocc = int(self.nel / 2.0)

        print('\nNumber of occupied orbitals: %d' % self.ndocc)
        print('Number of basis functions: %d' % self.nbf)

        # Run a quick check to make sure everything will fit into memory
        I_Size = (self.nbf**4) * 8.e-9
        print("\nSize of the ERI tensor will be %4.2f GB." % I_Size)

        # Estimate memory usage
        memory_footprint = I_Size * 1.5
        if I_Size > numpy_memory:
            pc.clean()
            raise Exception(
                "Estimated memory utilization (%4.2f GB) exceeds numpy_memory \
                            limit of %4.2f GB." %
                (memory_footprint, numpy_memory))

        # Compute required quantities for SCF
        self.V = np.asarray(self.mints.ao_potential())
        self.T = np.asarray(self.mints.ao_kinetic())
        # self.I = np.asarray(self.mints.ao_eri())
        self.JK = jk.build_JK(self.molecule, self.basis_name, scf_type, use_c)

        self.Enuc = self.molecule.nuclear_repulsion_energy()

        print('\nTotal time taken for integrals: %.3f seconds.' %
              (time.time() - start_time))

        t = time.time()

        # Build H_core
        self.H = self.T + self.V

        # Orthogonalizer A = S^(-1/2) using Psi4's matrix power.
        A = self.mints.ao_overlap()
        A.power(-0.5, 1.e-16)
        self.A = np.asarray(A)

        print('\nTotal time taken for setup: %.3f seconds' %
              (time.time() - start_time))
Beispiel #24
0
def _write_nbo(self, name: str):
    """Write wavefunction information in *wfn* to *name* in NBO format.

    Parameters
    ----------
    name
        Destination file name for NBO file.

    """
    basisset = self.basisset()
    mints = core.MintsHelper(basisset)
    mol = self.molecule()

    # Populate header and coordinates.
    NBO_file = f" $GENNBO NATOMS = {mol.natom()} NBAS = {basisset.nbf()} BODM "
    if self.nalpha() != self.nbeta():
        NBO_file += f" OPEN"
    NBO_file += " $END\n $NBO       $END\n $COORD\n"
    NBO_file += " GENNBO expects one comment line here. So, here's a comment line.\n"
    for atom in range(mol.natom()):
        NBO_file += f"{mol.true_atomic_number(atom):2d}  {int(mol.Z(atom)):2d}  {constants.bohr2angstroms * mol.x(atom):20.12f} {constants.bohr2angstroms * mol.y(atom):20.12f} {constants.bohr2angstroms * mol.z(atom):20.12f}\n"
    NBO_file += " $END\n"

    # Populate basis function information.
    pure_order = [
        [1],  # s
        [103, 101, 102],  # p
        [255, 252, 253, 254, 251],  # d: z2 xz yz x2-y2 xy
        [351, 352, 353, 354, 355, 356, 357],  # f
        [451, 452, 453, 454, 455, 456, 457, 458, 459],  #g
        [551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561]  #h
    ]
    # For historical reasons, the code loops over shells first and then basis functions within the shell.
    # This turns out to not give the same ordering as looping over basis functions directly.
    NBO_file += " $BASIS\n"
    center_string = ""
    label_string = ""
    count = 0
    for i in range(basisset.nshell()):
        shell = basisset.shell(i)
        am = shell.am
        for j in range(shell.nfunction):
            if not (count % 10):
                center_string += "\n  CENTER =" if not i else "\n          "
                label_string += "\n   LABEL =" if not i else "\n          "
            center_string += f" {shell.ncenter + 1:6d}"
            if basisset.has_puream():
                label = pure_order[am][j]
            else:
                label = 100 * am + j + 1
            label_string += f" {label:6d}"
            count += 1
    NBO_file += center_string + label_string + "\n $END\n"

    # Populate contraction information.Start with exponents.
    NBO_file += f" $CONTRACT\n  NSHELL = {basisset.nshell():6d}\n    NEXP = {basisset.nprimitive():6d}\n"
    function_nums = ""
    prim_nums = ""
    prim_indices = ""
    exponents = ""
    prim_index = 0
    coefficients = []  # [(AM int, coefficient), ...]
    for i in range(basisset.nshell()):
        if not (i % 10):
            function_nums += "\n   NCOMP =" if not i else "\n          "
            prim_nums += "\n   NPRIM =" if not i else "\n          "
            prim_indices += "\n    NPTR =" if not i else "\n          "
        shell = basisset.shell(i)
        nprim = shell.nprimitive
        function_nums += f" {shell.nfunction:6d}"
        prim_nums += f" {nprim:6d}"
        prim_indices += f" {prim_index + 1:6d}"
        for j in range(nprim):
            if not (prim_index % 4):
                exponents += "\n     EXP =" if not prim_index else "\n          "
            exponents += f"{shell.exp(j):15.6E}"
            prim_index += 1
            coefficients.append((shell.am, shell.coef(j)))
    NBO_file += function_nums + prim_nums + prim_indices + exponents

    # Populate contraction coefficients.Because some basis sets(Poples with S and P) use the same
    # coefficients for multiple angular momenta, we must supply coefficients for all primitives, for all
    # angular momenta.This leads to many zero elements.
    am_labels = ["S", "P", "D", "F", "G", "H"]
    for current_nbo_section_am in range(basisset.max_am() + 1):
        for i, (shell_am, coefficient) in enumerate(coefficients):
            if not (i % 4):
                NBO_file += f"\n      C{am_labels[current_nbo_section_am]} =" if not i else "\n          "
            if shell_am != current_nbo_section_am:
                coefficient = 0
            NBO_file += f"{coefficient:15.6E}"
    NBO_file += "\n $END"

    # That finishes most of the basis information. Next is the overlap. It would be great if we could just dump Psi's AO
    # overlap matrix, but we can 't. Per CCA guidelines, Psi' s Cartesian d and higher AM AOs aren't normalized to 1.
    # While NBO can "fix" this itself, it changes other AO quantities to match and gets the Fock matrix wrong.
    # Let's normalize ourselves instead.
    ao_overlap = mints.ao_overlap().np
    nbf = ao_overlap.shape[0]
    ao_normalizer = ao_overlap.diagonal()**(-1 / 2)

    def normalize(matrix, normalizer):
        return ((matrix * normalizer).T * normalizer).T

    normalized_ao_overlap = normalize(ao_overlap, ao_normalizer)

    def write_ao_quantity(*args):
        string = ""
        count = 0
        for quantity in args:
            for i in range(nbf):
                for j in range(nbf):
                    if not (count % 5):
                        string += "\n  "
                    string += f"{quantity[i][j]:15.6E}"
                    count += 1
        return string

    NBO_file += "\n $OVERLAP"
    NBO_file += write_ao_quantity(normalized_ao_overlap)
    NBO_file += "\n $END"

    normalized_alpha_density = normalize(self.Da_subset("AO"),
                                         1 / ao_normalizer)
    normalized_beta_density = normalize(self.Db_subset("AO"),
                                        1 / ao_normalizer)
    normalized_alpha_fock = normalize(self.Fa_subset("AO"), ao_normalizer)

    NBO_file += "\n $DENSITY"
    if self.same_a_b_dens():
        density = normalized_alpha_density + normalized_beta_density
        NBO_file += write_ao_quantity(density)
    else:
        NBO_file += write_ao_quantity(normalized_alpha_density,
                                      normalized_beta_density)
    NBO_file += "\n $END"

    NBO_file += "\n $FOCK"
    if not self.same_a_b_dens():
        normalized_beta_fock = normalize(self.Fb_subset("AO"), ao_normalizer)
        NBO_file += write_ao_quantity(normalized_alpha_fock,
                                      normalized_beta_fock)
    else:
        NBO_file += write_ao_quantity(normalized_alpha_fock)
    NBO_file += "\n $END"

    # The last step is to write the MO coefficients.
    NBO_file += "\n $LCAOMO"

    def write_C_matrix(C, count):
        # The C coefficients supplied the missing multiplication by the ao_normalizer in the overlap matrix before.
        # For NBO, we need that multiplication gone.
        C = (C.np.T / ao_normalizer).T
        string = ""
        for i in range(self.nmo()):
            for mu in range(nbf):
                count += 1
                if (count % 5 == 1):
                    string += ("\n  ")
                string += f"{C[mu][i]:15.6E}"
        # Pad linear dependencies
        for i in range((nbf - self.nmo()) * nbf):
            count += 1
            if (count % 5 == 1):
                string += ("\n  ")
            string += f"{0:15.6E}"
        return count, string

    count, alpha_LCAOMO = write_C_matrix(self.Ca_subset("AO", "ALL"), 0)
    NBO_file += alpha_LCAOMO
    if not self.same_a_b_orbs():
        NBO_file += write_C_matrix(self.Cb_subset("AO", "ALL"), count)[1]
    NBO_file += "\n $END\n"

    #Now time to write !
    with open(name, 'w') as f:
        f.write(NBO_file)
Beispiel #25
0
    def __init__(self, molecule, basis, ribasis, polarizable_atoms, point_charges,
                 polarizabilities=_theoretical_polarizabilities,
                 cutoff_alpha=4.0,
                 same_site_integrals='exact',
                 dipole_damping='Thole',
                 monopole_damping='Thole',
                 verbose=1):
        """
        two-electron, one-electron and zero-electron contributions to electronic
        Hamiltonian due to the presence of polarizable sites

        Parameters
        ----------
        molecule            :  psi4.core.Molecule
          QM part
        basis               :  psi4.core.BasisSet
          basis set for QM part
        ribasis             :  psi4.core.BasisSet
          auxiliary basis set for QM part (used for resolution of identity)
        polarizable_atoms   :  psi4.core.Molecule
          polarizable atoms in MM part
        point_charges       :  psi4.core.Molecule
          MM atoms which carry point charges
          The values of the point charges can be set via 
          `point_charges.set_nuclearo_charge(atom_id, charge)`

        polarizabilities    :  dict
          dictionary with atomic polarizabilities for each atom type (in Bohr^3)
        cutoff_alpha        :  float (in Bohr)
          exponent alpha in cutoff function C(r)=(1-exp(-alpha r^2))^q
        same_site_integrals :  str
          'exact' - use analytically exact polarization integrals whenever possible
          'R.I.'  - treat all integrals on the same footing by using the resolution of identity
          This only affects the 1e part of the Hamiltonian.
        dipole_damping      :  str
          The classical dipole polarizability may diverge for certain geometries, unless the dipole-dipole
          interaction is damped at close distances. The following damping functions are available:
            * 'Thole'  - the dipole field tensor is modified according to eqns. A.1 and A.2 in [Thole].
            * 'CPP'    - the same cutoff function as for the CPP integrals is used (see eqn. 4 in [CPP]) (not recommended)
            *  None    - no damping at all  (not recommended)
        monompole_damping   :  str
          The field of a point charge at the position of a polarizable atom becomes infinite as
          the two approach. This can be avoided if the point charge (QM nuclei and MM partial charges)
          are replaced by smeared out charge distributions so that the field approaches a finite value 
          as the point charge and the polarizable atom fuse.
            * 'Thole'  - replace the field of a point charge by eqn. A4 in [Thole]
            * None     - use field of a point charge, E(i) = Q(i) * r(i)/r^3   (not recommended)
        verbose             :  int
          controls amount of output written, 0 - silent
        """
        self.verbose = verbose
        self.molecule = molecule
        self.basis = basis
        # auxiliary basis set for resolution of identity
        self.ribasis = ribasis

        # number of polarizable sites
        self.npol = polarizable_atoms.natom()
        # number of basis functions
        self.nbf = self.basis.nbf()
        
        self.polarizable_atoms = polarizable_atoms
        assert self.polarizable_atoms.natom() > 0, "No polarizable atoms specified"
        self.point_charges = point_charges
        
        self.polarizabilities = polarizabilities
        if (self.verbose > 0):
            print(f"same-site polarization integrals are treated by method : '{same_site_integrals}'")
        self.same_site_integrals = same_site_integrals
        self.dipole_damping = dipole_damping
        if (self.verbose > 0):
            print(f"damping of dipole-dipole interaction : {dipole_damping}")
        self.monopole_damping = monopole_damping
        if (self.verbose > 0):
            print(f"damping of monopole field            : {monopole_damping}")

        # cutoff function C(r) = (1- exp(-alpha r^2))^q
        self.cutoff_alpha = cutoff_alpha
        self.cutoff_power = 2

        # integrals
        self.mints = core.MintsHelper(self.basis)
        
        # compute quantities needed for constructing two-electron, one-electron and zero-electron
        # contributions to the Hamiltonian due to the presence of polarizable atoms
        self.alpha = self._atomic_polarizabilities()
        self.A = self._effective_polarizability()
        self.F_elec = self._polarization_integrals_F()
        if (self.same_site_integrals == 'exact'):
            self.I_1e = self._polarization_integrals_I()
        self.F_nucl = self._nuclear_fields()

        # If the polarizable atoms were replaced by a single polarizable site, what
        # would be its polarizability tensor?
        self._molecular_polarizability()
        
        # for resolution of identity we need the inverse of the overlap matrix
        mints_ri = core.MintsHelper(self.ribasis)
        S = mints_ri.ao_overlap()
        self.Sinv = la.inv(S)