def write_orbitals(self, filename='None'):
        # Assign filename depending if it is given as an input
        # or if it has to be generated from the elements.
        if filename == 'None':
            filename = '../data/molden-fchk_files/' + self.scheme[
                0] + '/' + self.generate_molname()
        else:
            filename = '../data/molden-fchk_files/' + filename

        # Write out the molden file using psi4
        psi4.molden(self.model.wavefunction, filename + '.molden')

        # Using IOData, we load in the molden file. We alter the
        # values of the coefficients using the localized coefficients.
        # Finally we write them out to a FCHK file and remove the molden file
        molecule = load_one(filename + '.molden')
        molecule_mo = molecule.mo
        mo_coeffs = molecule_mo.coeffs
        # replace the occupied orbital coefficients with the localized ones.
        self.L_occ = np.dot(self.C_occ, self.W)
        mo_coeffs[:, :self.N_occ] = self.L_occ
        molecule_mo.coeffs = mo_coeffs
        dump_one(molecule, filename + '.fchk')
        os.remove(filename + '.molden')
Exemple #2
0
    dRMS = 0.5 * (mean(diis_r_a**2)**0.5 + mean(diis_r_b**2) * 0.5)
    print(
        'SCF Iteration {:3d}: Energy = {: 4.16f} dE = {: 1.5e} dRMS = {:1.5e}'.
        format(scf_iter + 1, SCF_E, dE, dRMS))

    if (abs(dE) < E_conv) and (dRMS < D_conv):
        break

    E_old = SCF_E

    if scf_iter >= 2:
        Fa = diis_xtrap(F_list_a, R_list_a)
        Fb = diis_xtrap(F_list_b, R_list_b)

    Ca, Pa, epa = diag_F(Fa, nalpha)
    Cb, Pb, epb = diag_F(Fb, nbeta)

    if scf_iter == maxiter:
        psi4.core.clean()
        raise Exception("Maximum number of SCF iterations exceeded.")

print('\nSCF Converged.')
print('Final UHF Energy: {: .8f} [Eh]'.format(SCF_E))

SCF_E_psi, wfn = psi4.energy('SCF', return_wfn=True)
psi4.compare_values(SCF_E_psi, SCF_E, 6, 'SCF Energy')
# create molden file
os.system('rm -f {}.uhf.molden'.format(cmpd))
psi4.molden(wfn, '{}.uhf.molden'.format(cmpd))
psi4.fchk(wfn, 'heh+.fchk')
Exemple #3
0
def psi4_calculation(conf,
                     dih,
                     conf_name,
                     angle_deg,
                     spe_method,
                     spe_basis,
                     geom_opt_technique,
                     opt_method,
                     opt_basis,
                     geom_maxiter=100,
                     **psi4opts):

    dih_atoms = [x for x in dih.GetAtoms()]
    dih_string = " ".join(["{}".format(x.GetIdx() + 1) for x in dih_atoms])

    psi4_log_filename = "psi4_" + conf_name + ".dat"
    psi4.core.set_output_file(psi4_log_filename, False)

    psi_mol = get_psi4_mol_from_conf(conf)

    wf_filename = None
    if geom_opt_technique == "QM":
        logging.info("Start geometry optimization on conformer %s angle %d",
                     conf_name, angle_deg)
        geom_opt_start = time.time()
        logging.debug("Frozen Dihedral: '%s'", dih_string)
        psi4.set_options({
            "frozen_dihedral": dih_string,
            "geom_maxiter": geom_maxiter,
            "dynamic_level": 1,
        })
        psi_mol.update_geometry()
        calc_value, wavefcn = run_psi4("optimize", psi_mol, opt_method,
                                       opt_basis, **psi4opts)
        if calc_value is None:
            logging.warning(
                "Failed to optimize geometry for conformer %s angle %d.",
                conf_name,
                angle_deg,
            )
            # get logfile to help debug failures
            try:
                psi4.core.flush_outfile()
                with open(psi4_log_filename, "r") as fptr:
                    psi4_log_data = fptr.read()
                    conf.SetData("PSI4_LOG", psi4_log_data)
            except IOError as e:
                print(
                    "Unable to retrieve psi4 log data ",
                    psi4_log_filename,
                    " because of error: ",
                    e,
                )

            raise ValueError(
                "Failed to optimize geometry for conformer %s angle %.1f.",
                conf_name,
                angle_deg,
            )
        logging.info(
            "Optimized geometry for conformer %s angle %d in %.1f s.",
            conf_name,
            angle_deg,
            time.time() - geom_opt_start,
        )
        calc_value *= psi4.constants.hartree2kcalmol

        # write wavefuction
        wf_filename = "{}_{}.molden".format("wavefcn_opt", conf_name)
        psi4.molden(wavefcn, wf_filename)

    elif geom_opt_technique == "MM":
        # TODO: Implement molecular mechanics-based optimization
        pass
    else:
        pass

    if ((geom_opt_technique == "None") or (opt_method != spe_method)
            or (opt_basis != spe_basis)):
        logging.info(
            "Calculate single point energy of conformer %s angle %d",
            conf_name,
            angle_deg,
        )
        spe_start = time.time()
        calc_value, wavefcn = run_psi4("energy", psi_mol, spe_method,
                                       spe_basis, **psi4opts)
        if calc_value is None:
            logging.error(
                "Failed to calculate single point energy for conformer %s angle %d.",
                conf_name,
                angle_deg,
            )
            raise ValueError(
                "Failed to calculate single point energy for conformer %s angle %d.",
                conf_name,
                angle_deg,
            )
        logging.info(
            "Calculated single point energy for conformer %s angle %d in %.1f s.",
            conf_name,
            angle_deg,
            time.time() - spe_start,
        )
        calc_value *= psi4.constants.hartree2kcalmol

    # attached psi4 log to each file
    try:
        # extract minimal geometry optimization data for success
        psi4.core.flush_outfile()
        with open(psi4_log_filename, "r") as fptr:
            psi4_log_str = str()
            for line in fptr.readlines():
                if (line.find("~") > 0 or line.find("Exception") > 0
                        or line.find("Optimization") > 0):
                    psi4_log_str += line
            conf.SetData("PSI4_LOG", psi4_log_str)
    except IOError as e:
        print("Unable to save psi4 log data ", psi4_log_filename,
              " because of error: ", e)

    return calc_value, psi_mol, wavefcn
    if SORTandRENAME:
        listOfFiles = os.listdir(baseFileName)
        listOfFiles.sort(key=sortFunction)
        for i in range(len(listOfFiles)):
            os.rename(baseFileName + '/' + listOfFiles[i],
                      baseFileName + '/irc-' + str(i) + '.com')

    if PSI4LOAD and xyzOUTPUT and CALCWF:
        # set psi4 stuff
        psi4.set_memory('500 MB')
        psi4.set_options({'PARALLEL': True, 'reference': 'rhf'})
        geometries = {}
        energies = {}
        wavefunctions = {}

        for file in outFileList:
            if file.split('.')[-1] != 'xyz':
                print('remove ' + file +
                      ' from list, because it is no xyz file')
                outFileList.remove(file)
            else:
                with open(file) as f:
                    geometries[file] = psi4.geometry(f.read())
                print('calc wfn for ' + file)
                energies[file], wavefunctions[file] = psi4.energy(
                    'mp2/cc-pvdz', molecule=geometries[file], return_wfn=True)
                outFileName = file.split('.')[0] + '.molden'
                print('save molden file: ' + outFileName)
                psi4.molden(wavefunctions[file], outFileName)
Exemple #5
0
def do_bloch(wfn,
             n_sites,
             site_list=None,
             site_list_orbs=None,
             molden_file='orbs.molden',
             skip_localization=False,
             neutral=False):
    """
    Bloch effective Hamiltonian solver.

    Solves the Bloch effective Hamiltonian and returns a matrix containing 
    J coupling information. Sites are designated using ``site_list`` or 
    ``site_list_orbs``; if neither of these keywords are specified, 
    each orbital in the CAS/RAS2 space is assumed to be its own site. 
    A Molden file containing the orbitals is written to ``orbs.molden`` (or 
    whichever file is specified by the user). The J coupling values are 
    also written to standard output.

    Parameters
    ----------
    sf_wfn : sf_wfn
        SF-IP-EA wfn object containing info about the calculation.
    n_sites : int
        The number of sites.
    site_list : list
        List of which atoms are "sites". If this is not given,
        the program assumes one orbital per site. Atomic center ordering
        starts at zero. Optional.
    site_list_orbs : list
        A list of sites, using lists of orbitals rather 
        than atomic centers. (It's a list of lists. For example, for a 
        two-site case where MOs 55, 56, and 59 are on one site and the 
        remaining orbitals are on the other, use ``[[55,56,59],[57,58,60]]``.) 
        Note that ordering starts at 1, not zero, so it follows the same 
        indexing as the MO printing in the Psi4 output files. Optional.
    molden_file : string
        Molden filename to which orbitals are written. Optional. 
        Defaults to ``orbs.molden``.
    skip_localization : bool
        Whether to skip orbital localization. If true, the user should 
        localize the orbitals in wfn.wfn beforehand! Optional. 
        Defaults to False.
    neutral : bool
        Indicates that the RAS eigenvectors passed in were calculated 
        in the neutral determinant space only. Optional. Defaults to False.

    Returns
    -------
    numpy.ndarray
        NumPy matrix of J coupling values
    """

    np.set_printoptions(suppress=True)
    print("Doing Bloch Hamiltonian analysis...")

    # Put input vector into block form (only need CAS-1SF block!!)
    n_SF = wfn.n_SF
    ras1 = wfn.ras1
    ras2 = wfn.ras2
    e = wfn.e.copy()
    vecs = wfn.vecs
    # special case for unpacking neutral determinant space
    if (neutral):
        newvecs = np.zeros((ras2, ras2, n_sites))
        for i in range(ras2):
            for n in range(n_sites):
                newvecs[i, i, n] = wfn.vecs[i, n]
        newvecs = np.reshape(newvecs, (ras2 * ras2, n_sites))
        vecs = newvecs
    v_b1 = vecs[:(ras2 * ras2), :n_sites].copy()
    n_roots = v_b1.shape[1]
    v_b1 = np.reshape(v_b1, (ras2, ras2, n_roots))  # v[i,a]

    # Obtain info for orbital localization and localize v
    psi4_wfn = wfn.wfn
    C = psi4.core.Matrix.to_array(psi4_wfn.Ca(), copy=True)
    # Allow user to skip our localization and use their own if needed
    if (not skip_localization):
        ras1_C = C[:, :ras1]
        ras2_C = C[:, ras1:ras1 + ras2]
        ras3_C = C[:, ras1 + ras2:]
        loc = psi4.core.Localizer.build('BOYS', psi4_wfn.basisset(),
                                        psi4.core.Matrix.from_array(ras2_C))
        loc.localize()
        U = psi4.core.Matrix.to_array(loc.U, copy=True)
        # localize vects
        v_b1 = np.einsum("ji,jbn->ibn", U, v_b1)
        v_b1 = np.einsum("ba,ibn->ian", U, v_b1)
        wfn.local_vecs = np.reshape(v_b1, (ras2 * ras2, n_roots))
        # write localized orbitals to wfn and molden
        C_full_loc = psi4.core.Matrix.from_array(
            np.column_stack(
                (ras1_C, psi4.core.Matrix.to_array(loc.L), ras3_C)))
        psi4_wfn.Ca().copy(C_full_loc)
        psi4_wfn.Cb().copy(C_full_loc)

    # write Molden file
    psi4.molden(psi4_wfn, molden_file)

    # Extract i=a part (neutral determinants only!!)
    v_n = None
    for i in range(v_b1.shape[2]):
        v_new = np.diagonal(v_b1[:, :, i])
        if (type(v_n) == type(None)):
            v_n = v_new
        else:
            v_n = np.vstack((v_n, v_new))
    v_n = v_n.T  # make sure columns are states rather than rows

    # Obtain S
    # S should be I if states are orthonormal

    orbs_per_site = []
    # Handle grouping orbitals if needed
    if (type(site_list) != type(None)):
        # Construct density N for sites
        N = np.zeros((len(site_list), ras2))
        bas = psi4_wfn.basisset()
        S = psi4.core.Matrix.to_array(psi4_wfn.S())
        C = psi4.core.Matrix.to_array(psi4_wfn.Ca())
        CS = np.einsum("vi,vu->ui", C, S)
        for atom, A in enumerate(site_list):
            for i in range(ras2):
                for mu in range(C.shape[1]):
                    if (bas.function_to_center(mu) == A):
                        N[atom, i] += C[mu, ras1 + i] * CS[mu, ras1 + i]
        # Reorder v_n rows so they're grouped by site
        perm = []
        for i in range(ras2):
            diff = abs(N[:, i] - 1)
            perm.append(np.argmin(diff))
        print("Reordering RAS2 determinants as follows:")
        print(perm)
        v_n = v_n[np.argsort(perm), :]  # permute!
        # construct coeff matrix
        R = np.zeros((ras2, len(site_list)))
        tmp, orbs_per_site = np.unique(perm, return_counts=True)
        for i, site in enumerate(np.sort(perm)):
            R[i, site] = 1.0 / math.sqrt(orbs_per_site[site])
        # orthonormalize (SVD)
        v_n = np.dot(R.T, v_n)

    elif (type(site_list_orbs) != type(None)):
        # Reorder v_n rows so they're grouped by site
        perm = []
        for site in site_list_orbs:
            for orb in site:
                perm.append(orb - ras1 - 1)
        print("Reordering RAS2 determinants as follows:")
        print(perm)
        v_n = v_n[perm, :]  # permute!
        # construct coeff matrix
        # construct coeff matrix
        R = np.zeros((ras2, len(site_list_orbs)))
        ind = 0
        for s, site in enumerate(site_list_orbs):
            orbs_per_site.append(len(site))
            for i in range(len(site)):
                R[ind, s] = 1.0 / math.sqrt(len(site))
                ind = ind + 1
        # orthonormalize (SVD)
        v_n = np.dot(R.T, v_n)

    # Else, assume 1 orbital per site
    else:
        orbs_per_site = n_sites * [1]

    #v_orth = v_n
    v_orth = lowdin_orth(v_n)

    S = np.dot(v_orth.T, v_orth)
    print("Orthogonalized Orbital Overlap:")
    print(S)

    # Build Bloch Hamiltonian
    #H = np.dot(S, v_orth)
    H = v_orth
    H = np.dot(H, np.diag(e))
    H = np.dot(H, v_orth.T)  # invert v_orth
    J = np.zeros(H.shape)
    print("Effective Hamiltonian")
    print(H)
    print("Orbs Per Site")
    print(orbs_per_site)
    print("J Couplings:")
    for i in range(n_sites):
        for j in range(i):
            Sa = orbs_per_site[i] / 2.0
            Sb = orbs_per_site[j] / 2.0
            J[i, j] = J[j, i] = -1.0 * H[i, j] / (2.0 * math.sqrt(Sa * Sb))
            print("\tJ%i%i = %6.6f" % (i, j, J[i, j]))

    return J
Exemple #6
0
        0.0053000000 1.00000000
        ****
        """
    return basstrings


nstates = 15
psi4.qcdb.libmintsbasisset.basishorde[
    'ANONYMOUS03952CBD'] = basisspec_psi4_yo__anonymous03952cbd
psi4.core.set_global_option("BASIS", "anonymous03952cbd")
E, wfn = psi4.energy('scf', return_wfn=True)
mints = psi4.core.MintsHelper(wfn.basisset())
S_mat = np.asarray(mints.ao_overlap())
n_bas = S_mat.shape[0]
so2ao = mints.petite_list().sotoao()
psi4.molden(wfn, 'h2.molden')
# add 7F to molden file, psi4 doesn't write it for some reason
with open("h2.molden", "a") as myfile:
    myfile.write("\n [7F] \n")

molden_dict = {"basis_file": "h2.molden", "molecule": "molden"}
s = pyopencap.System(molden_dict)
s.check_overlap_mat(S_mat, "psi4")
cap_dict = {
    "cap_type": "box",
    "cap_x": "6.00",
    "cap_y": "6.00",
    "cap_z": "6.7",
}
pc = pyopencap.CAP(s, cap_dict, nstates)
Exemple #7
0
    def run_psi4(self):
        core.set_output_file(self.output + '.dat')
        import psi4
        psi4.set_memory('1 GB')
        psi_molecule = psi4.geometry(self.psi_geom)
        psi4.set_options({
            'basis': self.basis,
            'molden_write': False,
            'WRITER_FILE_LABEL': str(self.output) + '.dat',
            'maxiter': 500,
            'fail_on_maxiter': False
        })
        if self.multiplicity != 1:
            psi4.set_options({'reference': 'ROHF'})

        e, wfn = psi4.energy('scf', return_wfn=True)
        self.molecule.hf_energy = e
        if os.path.exists('./scr.molden'):
            os.system('rm scr.molden')
        if self.active != None:
            cb = wfn.Cb().to_array()
            ca = wfn.Ca().to_array()
            self.molecule.n_orbitals = len(ca)
            ca[:, self.active + self.reorder] = ca[:,
                                                   self.reorder + self.active]
            cb[:, self.active + self.reorder] = cb[:,
                                                   self.reorder + self.active]
            if self.loc == 'True':
                acs = psi4.core.Matrix('null')
                acs = acs.from_array(ca[:, self.active])
                Local = psi4.core.Localizer.build("BOYS", wfn.basisset(), acs)
                Local.localize()
                acs = Local.L
                ca[:, self.active] = acs
                acs2 = psi4.core.Matrix('null2')
                acs2 = acs2.from_array(cb[:, self.active])
                Local = psi4.core.Localizer.build("BOYS", wfn.basisset(), acs2)
                Local.localize()
                acs2 = Local.L
                cb[:, self.active] = acs2
            ca = psi4.core.Matrix.from_array(ca)
            cb = psi4.core.Matrix.from_array(cb)
            wfn.Cb().copy(cb)
            wfn.Ca().copy(ca)
            psi4.molden(wfn, 'scr.molden')
            psi4.set_options({'frozen_docc': [self.n_fdoccs]})
            self.n_fnoccs = self.molecule.n_orbitals - self.n_fdoccs - len(
                self.active)
            psi4.set_options({'frozen_uocc': [self.n_fnoccs]})
            self.CASCI = psi4.energy('fci', ref_wfn=wfn)
            self.molecule.CASCI = self.CASCI
            self.fci_energy = self.CASCI
            self.molecule.fci_energy = self.CASCI
        else:
            self.CASCI = psi4.energy('fci', ref_wfn=wfn)
            self.molecule.CASCI = self.CASCI
            self.fci_energy = self.CASCI
            self.molecule.fci_energy = self.CASCI
            self.molecule.n_orbitals = len(wfn.Ca().to_array())
            psi4.molden(wfn, 'scr.molden')
        self.molecule.nuclear_repulsion = psi_molecule.nuclear_repulsion_energy(
        )
        self.molecule.canonical_orbitals = np.asarray(wfn.Ca())
        if self.active == None:
            self.active = [i for i in range(0, self.molecule.n_orbitals)]
        self.molecule.overlap_integrals = np.asarray(wfn.S())
        self.molecule.n_qubits = 2 * self.molecule.n_orbitals
        self.molecule.orbital_energies = np.asarray(wfn.epsilon_a())
        self.molecule.fock_matrix = np.asarray(wfn.Fa())
        mints = psi4.core.MintsHelper(wfn.basisset())
        self.molecule.one_body_integrals = general_basis_change(
            np.asarray(mints.ao_kinetic()), self.molecule.canonical_orbitals,
            (1, 0))
        self.molecule.one_body_integrals += general_basis_change(
            np.asarray(mints.ao_potential()), self.molecule.canonical_orbitals,
            (1, 0))
        two_body_integrals = np.asarray(mints.ao_eri())
        two_body_integrals.reshape(
            (self.molecule.n_orbitals, self.molecule.n_orbitals,
             self.molecule.n_orbitals, self.molecule.n_orbitals))
        two_body_integrals = np.einsum('psqr', two_body_integrals)
        two_body_integrals = general_basis_change(
            two_body_integrals, self.molecule.canonical_orbitals, (1, 1, 0, 0))
        self.molecule.two_body_integrals = two_body_integrals
        doccs = [i for i in range(0, self.n_fdoccs)]

        if self.active != None:
            self.molecule.n_orbitals = len(self.active)
            self.molecule.n_qubits = 2 * self.molecule.n_orbitals
        else:
            self.molecule.n_orbitals = len(self.molecule.canonical_orbitals)
            self.molecule.n_qubits = 2 * self.molecule.n_orbitals
        self.molecule.n_fdoccs = self.n_fdoccs
        self.molecule.hamiltonian = self.molecule.get_molecular_hamiltonian(
            occupied_indices=doccs, active_indices=self.active)
        self.molecule.save()
        return self.molecule
Exemple #8
0
        D   1   1.00
        0.0755000              1.0000000
        D   1   1.00
        0.0377500              1.0000000
        D   1   1.00
        0.0188750              1.0000000
        ****
        """
    return basstrings


psi4.qcdb.libmintsbasisset.basishorde[
    'ANONYMOUS03952CBD'] = basisspec_psi4_yo__anonymous03952cbd
psi4.core.set_global_option("BASIS", "anonymous03952cbd")
E, wfn = psi4.energy('scf', return_wfn=True)
psi4.molden(wfn, 'n2.molden')
# add 7F to molden file, psi4 doesn't write it for some reason
with open("n2.molden", "a") as myfile:
    myfile.write("\n [7F] \n")

# checking overlap matrix
mints = psi4.core.MintsHelper(wfn.basisset())
S_mat = np.asarray(mints.ao_overlap())
molden_dict = {"basis_file": "n2.molden", "molecule": "molden"}
s = pyopencap.System(molden_dict)
s.check_overlap_mat(S_mat, "psi4")
print('', end='', flush=True)
cap_dict = {
    "cap_type": "box",
    "cap_x": "2.76",
    "cap_y": "2.76",
Ca, Cb, mapping = tcmolden2psi4wfn_ao_mapping(d_molden, restricted=restricted)
wfn_minimal_np = np.load("wfn-1step.180.npy", allow_pickle=True)
wfn_minimal_np[()]['matrix']["Ca"] = Ca
if not restricted:
    wfn_minimal_np[()]['matrix']["Cb"] = Cb
else:
    wfn_minimal_np[()]['matrix']["Cb"] = Ca
np.save("wfn-1step-tc.180.npy", wfn_minimal_np)

# ----copy wfn file to the right place with a right name---
print("wfn copying...")
pid = str(os.getpid())
targetfile = psi4_scr + filename + '.default.' + pid + '.180.npy'
shutil.copyfile("wfn-1step-tc.180.npy", targetfile)

# ---final scf---
print("final scf...")
psi4.set_options({
    'SOSCF': False,
    "SOSCF_MAX_ITER": 40,
})
psi4.set_options({
    "maxiter": 250,
    "D_CONVERGENCE": 1e-6,
    "E_CONVERGENCE": 1e-6,
    "fail_on_maxiter": True
})
e, wfn = psi4.energy('b3lyp', molecule=mol, return_wfn=True)
wfn.to_file("wfn.180")
psi4.molden(wfn, "geo.molden")
Exemple #10
0
def write_molden(wfn):
    psi4.molden(wfn, 'h2.molden')
    # add 7F to molden file, psi4 doesn't write it for some reason
    with open('h2.molden', "a") as myfile:
        myfile.write("\n [7F] \n")
Exemple #11
0
def calculate_energy(mol,
                     dih,
                     spe_method="SCF",
                     spe_basis="6-31G",
                     geom_opt_technique="None",
                     opt_method="SCF",
                     opt_basis="6-31G",
                     geom_maxiter=100,
                     only_selected_conf=False,
                     molden_output=False,
                     **psi4opts):
    """Calculates the energy for a single conformer at a single dihedral angle.
    """

    # Argument validation
    if geom_opt_technique not in ["None", "QM", "MM"]:
        geom_opt_technique = "None"

    parent_torsion_tag = "TORSION_ATOMS_ParentMol"
    torsion_atoms_in_parent = get_sd_data(mol, parent_torsion_tag).split()
    dih_name = mol.GetTitle() + "_" + "_".join(torsion_atoms_in_parent)

    if only_selected_conf:
        conf_selection_tag = "SELECTED_CONFORMER"
        if not mol.HasData(conf_selection_tag):
            raise ValueError("Could not find 'SELECTED_CONFORMER' Tag in %s.",
                             dih_name)
        key_conf_id = mol.GetIntData(conf_selection_tag)

    for conf in mol.GetConfs():
        if only_selected_conf:
            if conf.GetIdx() != key_conf_id:
                continue

        conf_name = get_sd_data(conf, "CONFORMER_LABEL")
        if only_selected_conf:
            logging.debug("Only running psi4 calculation for %s" % conf_name)
        else:
            logging.debug("Running psi4 calculation for %s" % conf_name)
        angle_deg = conf.GetDoubleData("TORSION_ANGLE")

        normalize_coordinates(conf, dih)
        try:
            energy, psi_mol, wavefcn = psi4_calculation(
                conf, dih, conf_name, angle_deg, spe_method, spe_basis,
                geom_opt_technique, opt_method, opt_basis, geom_maxiter,
                **psi4opts)
        except ValueError as e:
            logging.error(e)
            raise ValueError(
                "Failed to run calculation for conformer %s angle %d.",
                conf_name,
                angle_deg,
            )

        PSI4_OPT_METHOD_KEY = "PSI4_OPT_METHOD"
        PSI4_OPT_BASIS_KEY = "PSI4_OPT_BASIS"

        prev_opt_method = oechem.OEGetSDData(conf, PSI4_OPT_METHOD_KEY)
        if len(prev_opt_method) > 0:
            prev_opt_method += "_"
        prev_opt_basis = oechem.OEGetSDData(conf, PSI4_OPT_BASIS_KEY)
        if len(prev_opt_basis) > 0:
            prev_opt_basis += "_"

        complete_opt_method = prev_opt_method + str(opt_method)
        complete_opt_basis = prev_opt_basis + str(opt_basis)

        get_conf_from_psi4_mol(mol, psi_mol, conf)
        logging.debug("Completed psi4 calculation for %s with energy %f" %
                      (conf_name, energy))
        conf.SetEnergy(energy)
        oechem.OESetSDData(conf, "PSI4_ENERGY", str(energy))
        conf.SetDoubleData("PSI4_ENERGY", energy)
        if geom_opt_technique == "QM":
            oechem.OESetSDData(conf, PSI4_OPT_METHOD_KEY, complete_opt_method)
            oechem.OESetSDData(conf, PSI4_OPT_BASIS_KEY, complete_opt_basis)
        oechem.OESetSDData(conf, "PSI4_SPE_METHOD", str(spe_method))
        oechem.OESetSDData(conf, "PSI4_SPE_BASIS", str(spe_basis))

        # write wavefuction
        if molden_output:
            wf_filename = "{}_{}.molden".format("wavefcn", conf_name)
            psi4.molden(wavefcn, wf_filename)
            try:
                with open(wf_filename, "r") as fptr:
                    molden_data = fptr.read()
                    conf.SetData("MOLDEN_DATA", molden_data)
            except IOError as e:
                print(
                    "Unable to save wave function data ",
                    wf_filename,
                    " because of error: ",
                    e,
                )

    if psi4opts:
        save_sddata(mol, psi4opts)