Example #1
0
    def get_dot_product(self, i, j):
        key = frozenset([i, j])
        try:
            return self.cached_dot_products[key]
        except KeyError:
            if self.storage_policy == StoragePolicy.InCore:
                Ri = self.stored_vectors[i][0]
                Rj = self.stored_vectors[j][0]
                dot_product = sum(Rix.vector_dot(Rjx) for Rix, Rjx in zip(Ri, Rj))
            elif self.storage_policy == StoragePolicy.OnDisk:
                dot_product = 0
                psio = core.IO.shared_object()
                for x, entry_dims in enumerate(self.R_template):
                    if len(entry_dims) == 2:
                        Rix = core.Matrix(self.get_name("R", i, x), *entry_dims)
                        Rjx = core.Matrix(self.get_name("R", j, x), *entry_dims)
                        Rix.load(psio, psif.PSIF_LIBDIIS, core.SaveType.SubBlocks)
                        Rjx.load(psio, psif.PSIF_LIBDIIS, core.SaveType.SubBlocks)
                    elif len(entry_dims) == 1:
                        Rix = core.Vector(self.get_name("R", i, x), *entry_dims)
                        Rjx = core.Vector(self.get_name("R", j, x), *entry_dims)
                        Rix.load(psio, psif.PSIF_LIBDIIS)
                        Rjx.load(psio, psif.PSIF_LIBDIIS)
                    else:
                        raise Exception("R_template may only have 1 or 2 dimensions. This is a bug: contact developers.")
                    dot_product += Rix.vector_dot(Rjx)
            else:
                raise Exception(f"StoragePolicy {self.storage_policy} not recognized. This is a bug: contact developers.")

            self.cached_dot_products[key] = dot_product
            return dot_product
Example #2
0
def orthogonalize(C, S):
    nbf, nocc = C.shape

    eigenvectors = core.Matrix(nocc, nocc)
    eigvals = core.Vector(nocc)
    sqrt_eigvals = core.Vector(nocc)

    CTSC = core.Matrix.triplet(C, S, C, True, False, False)
    CTSC.diagonalize(eigenvectors, eigvals, core.DiagonalizeOrder.Ascending)

    orthonormal = core.Matrix.doublet(C, eigenvectors, False, False)

    sqrt_eigvals.np[:] = np.sqrt(eigvals.np)
    orthonormal.np[:, :] /= sqrt_eigvals.np[np.newaxis, :]

    return orthonormal
Example #3
0
def _np_read(self, filename, prefix=""):

    if isinstance(filename, np.lib.npyio.NpzFile):
        data = filename
    elif isinstance(filename, (str, unicode)):
        if not filename.endswith('.npz'):
            filename = filename + '.npz'

        data = np.load(filename)
    else:
        raise Exception("Filename not understood: %s" % filename)

    ret_data = []

    if ((prefix + "Irreps") not in data.keys()) or ((prefix + "Name")
                                                    not in data.keys()):
        raise KeyError("File %s does not appear to be a numpyz save" %
                       filename)

    for h in range(data[prefix + "Irreps"]):
        ret_data.append(data[prefix + "IrrepData" + str(h)])

    arr_type = self.__mro__[0]
    if arr_type == core.Matrix:
        dim1 = core.Dimension.from_list(data[prefix + "Dim1"])
        dim2 = core.Dimension.from_list(data[prefix + "Dim2"])
        ret = core.Matrix(str(data[prefix + "Name"]), dim1, dim2)
    elif arr_type == core.Vector:
        dim1 = core.Dimension.from_list(data[prefix + "Dim"])
        ret = core.Vector(str(data[prefix + "Name"]), dim1)

    for h in range(data[prefix + "Irreps"]):
        ret.nph[h][:] = ret_data[h]

    return ret
Example #4
0
    def load_quantity(self, name, entry_num, item_num, force_new=True):
        """ Load quantity from wherever it's stored, constructing a new object if needed. """
        template_object = self.template[name][item_num]
        if isinstance(template_object,
                      float) or self.storage_policy == StoragePolicy.InCore:
            quantity = self.stored_vectors[entry_num][name][item_num]
            try:
                quantity = quantity.clone()
            except AttributeError:
                # The quantity must have been a float. No need to clone.
                pass
        elif self.storage_policy == StoragePolicy.OnDisk:
            entry_dims = template_object
            full_name = self.get_name(name, entry_num, item_num)
            psio = core.IO.shared_object()
            if len(entry_dims) == 2:
                quantity = core.Matrix(full_name, *entry_dims)
                quantity.load(psio, psif.PSIF_LIBDIIS, core.SaveType.SubBlocks)
            elif len(entry_dims) == 1:
                quantity = core.Vector(full_name, *entry_dims)
                quantity.load(psio, psif.PSIF_LIBDIIS)
        else:
            raise Exception(
                f"StoragePolicy {self.storage_policy} not recognized. This is a bug: contact developers."
            )

        return quantity
Example #5
0
    def load_quantity(self, name, entry_num, item_num, force_new = True):
        """ Load quantity from wherever it's stored, constructing a new object if needed. """
        template_object = self.template[name][item_num]
        if isinstance(template_object, float) or self.storage_policy == StoragePolicy.InCore:
            quantity = self.stored_vectors[entry_num][name][item_num]
            try:
                quantity = quantity.clone()
            except AttributeError:
                # The quantity must have been a float. No need to clone.
                pass
        elif self.storage_policy == StoragePolicy.OnDisk:
            full_name = self.get_name(name, entry_num, item_num)
            psio = core.IO.shared_object()
            if hasattr(template_object, "__len__"):
                # Looks like we have dimensions.
                if len(template_object) == 2:
                    quantity = core.Matrix(full_name, *template_object)
                    quantity.load(psio, psif.PSIF_LIBDIIS, core.SaveType.SubBlocks)
                elif len(template_object) == 1:
                    quantity = core.Vector(full_name, *template_object)
                    quantity.load(psio, psif.PSIF_LIBDIIS)
            elif which_import("ambit", return_bool=True):
                import ambit
                if template_object == ambit.BlockedTensor:
                    quantity = ambit.BlockedTensor.load_and_build(f"libdiis.{full_name}")
        else:
            raise Exception(f"StoragePolicy {self.storage_policy} not recognized. This is a bug: contact developers.")

        return quantity
Example #6
0
def _compute_fxc(PQrho, half_Saux, halfp_Saux, rho_thresh=1.e-8):
    """
    Computes the gridless (P|fxc|Q) ALDA tensor.
    """

    naux = PQrho.shape[0]

    # Level it out
    PQrho_lvl = core.Matrix.triplet(half_Saux, PQrho, half_Saux, False, False,
                                    False)

    # Rotate into a diagonal basis
    rho = core.Vector("rho eigenvalues", naux)
    U = core.Matrix("rho eigenvectors", naux, naux)
    PQrho_lvl.diagonalize(U, rho, core.DiagonalizeOrder.Ascending)

    # "Gridless DFT"
    mask = rho.np < rho_thresh  # Values too small cause singularities
    rho.np[mask] = rho_thresh

    dft_size = rho.shape[0]

    inp = {"RHO_A": rho}
    out = {
        "V": core.Vector(dft_size),
        "V_RHO_A": core.Vector(dft_size),
        "V_RHO_A_RHO_A": core.Vector(dft_size)
    }

    func_x = core.LibXCFunctional('XC_LDA_X', True)
    func_x.compute_functional(inp, out, dft_size, 2)

    func_c = core.LibXCFunctional('XC_LDA_C_VWN', True)
    func_c.compute_functional(inp, out, dft_size, 2)

    out["V_RHO_A_RHO_A"].np[mask] = 0

    # Rotate back
    Ul = U.clone()
    Ul.np[:] *= out["V_RHO_A_RHO_A"].np
    tmp = core.Matrix.doublet(Ul, U, False, True)

    # Undo the leveling
    return core.Matrix.triplet(halfp_Saux, tmp, halfp_Saux, False, False,
                               False)
Example #7
0
    def extrapolate(self, *args):

        dim = len(self.stored_vectors) + 1
        B = np.zeros((dim, dim))
        for i in range(len(self.stored_vectors)):
            for j in range(len(self.stored_vectors)):
                B[i, j] = self.get_dot_product(i, j)
        B[-1, :-1] = B[:-1, -1] = -1

        rhs = np.zeros((dim))
        rhs[-1] = -1

        # Trick to improve numerical conditioning.
        # Instead of solving B c = r, we solve D B D^-1 D c = D r, using
        # D r = r. D is the diagonals ^ -1/2 matrix.
        # This improves the conditioning of the problem.
        diagonals = B.diagonal().copy()
        diagonals[-1] = 1
        if np.all(diagonals > 0):
            diagonals = diagonals ** (- 0.5)
            B = np.einsum("i,ij,j -> ij", diagonals, B, diagonals)
            coeffs = np.linalg.lstsq(B, rhs, rcond=None)[0][:-1] * diagonals[:-1]
        else:
            coeffs = np.linalg.lstsq(B, rhs, rcond=None)[0][:-1]

        for j, Tj in enumerate(args):
            Tj.zero()
            if self.storage_policy == StoragePolicy.InCore:
                for ci, (_, Ti) in zip(coeffs, self.stored_vectors):
                    axpy(Tj, ci, Ti[j])
            elif self.storage_policy == StoragePolicy.OnDisk:
                for i, ci in enumerate(coeffs):
                    psio = core.IO.shared_object()
                    if isinstance(Tj, core.Vector):
                        Tij = core.Vector(self.get_name("T", i, j), *self.T_template[j])
                        Tij.load(psio, psif.PSIF_LIBDIIS)
                    elif isinstance(Tj, (core.Matrix, core.dpdfile2, core.dpdbuf4)):
                        Tij = core.Matrix(self.get_name("T", i, j), *self.T_template[j])
                        Tij.load(psio, psif.PSIF_LIBDIIS, core.SaveType.SubBlocks)
                    else:
                        raise TypeError("Unrecognized object type for DIIS.")
                    axpy(Tj, ci, Tij)
            else:
                raise Exception(f"StoragePolicy {self.storage_policy} not recognized. This is a bug: contact developers.")

        return True
Example #8
0
def test_ccl_functional(functional, ccl_functional):

    check = True

    if (not os.path.exists('data_pt_%s.html' % (ccl_functional))):
        os.system('wget ftp://ftp.dl.ac.uk/qcg/dft_library/data_pt_%s.html' %
                  ccl_functional)
    fh = open('data_pt_%s.html' % (ccl_functional))
    lines = fh.readlines()
    fh.close()

    points = []
    point = {}

    rho_line = re.compile(
        r'^\s*rhoa=\s*(-?\d+\.\d+E[+-]\d+)\s*rhob=\s*(-?\d+\.\d+E[+-]\d+)\s*sigmaaa=\s*(-?\d+\.\d+E[+-]\d+)\s*sigmaab=\s*(-?\d+\.\d+E[+-]\d+)\s*sigmabb=\s*(-?\d+\.\d+E[+-]\d+)\s*'
    )
    val_line = re.compile(r'^\s*(\w*)\s*=\s*(-?\d+\.\d+E[+-]\d+)')

    aliases = {
        'zk': 'v',
        'vrhoa': 'v_rho_a',
        'vrhob': 'v_rho_b',
        'vsigmaaa': 'v_gamma_aa',
        'vsigmaab': 'v_gamma_ab',
        'vsigmabb': 'v_gamma_bb',
        'v2rhoa2': 'v_rho_a_rho_a',
        'v2rhoab': 'v_rho_a_rho_b',
        'v2rhob2': 'v_rho_b_rho_b',
        'v2rhoasigmaaa': 'v_rho_a_gamma_aa',
        'v2rhoasigmaab': 'v_rho_a_gamma_ab',
        'v2rhoasigmabb': 'v_rho_a_gamma_bb',
        'v2rhobsigmaaa': 'v_rho_b_gamma_aa',
        'v2rhobsigmaab': 'v_rho_b_gamma_ab',
        'v2rhobsigmabb': 'v_rho_b_gamma_bb',
        'v2sigmaaa2': 'v_gamma_aa_gamma_aa',
        'v2sigmaaaab': 'v_gamma_aa_gamma_ab',
        'v2sigmaaabb': 'v_gamma_aa_gamma_bb',
        'v2sigmaab2': 'v_gamma_ab_gamma_ab',
        'v2sigmaabbb': 'v_gamma_ab_gamma_bb',
        'v2sigmabb2': 'v_gamma_bb_gamma_bb',
    }

    for line in lines:

        mobj = re.match(rho_line, line)
        if (mobj):

            if len(point):
                points.append(point)
                point = {}

            point['rho_a'] = float(mobj.group(1))
            point['rho_b'] = float(mobj.group(2))
            point['gamma_aa'] = float(mobj.group(3))
            point['gamma_ab'] = float(mobj.group(4))
            point['gamma_bb'] = float(mobj.group(5))

            continue

        mobj = re.match(val_line, line)
        if (mobj):
            point[aliases[mobj.group(1)]] = float(mobj.group(2))

    points.append(point)

    N = len(points)
    rho_a = core.Vector(N)
    rho_b = core.Vector(N)
    gamma_aa = core.Vector(N)
    gamma_ab = core.Vector(N)
    gamma_bb = core.Vector(N)
    tau_a = core.Vector(N)
    tau_b = core.Vector(N)

    index = 0
    for point in points:
        rho_a[index] = point['rho_a']
        rho_b[index] = point['rho_b']
        gamma_aa[index] = point['gamma_aa']
        gamma_ab[index] = point['gamma_ab']
        gamma_bb[index] = point['gamma_bb']
        index = index + 1

    super = build_superfunctional(functional, N, 1)
    super.test_functional(rho_a, rho_b, gamma_aa, gamma_ab, gamma_bb, tau_a,
                          tau_b)

    v = super.value('V')
    v_rho_a = super.value('V_RHO_A')
    v_rho_b = super.value('V_RHO_B')
    v_gamma_aa = super.value('V_GAMMA_AA')
    v_gamma_ab = super.value('V_GAMMA_AB')
    v_gamma_bb = super.value('V_GAMMA_BB')

    if not v_gamma_aa:
        v_gamma_aa = tau_a
        v_gamma_ab = tau_a
        v_gamma_bb = tau_a

    tasks = [
        'v', 'v_rho_a', 'v_rho_b', 'v_gamma_aa', 'v_gamma_ab', 'v_gamma_bb'
    ]
    mapping = {
        'v': v,
        'v_rho_a': v_rho_a,
        'v_rho_b': v_rho_b,
        'v_gamma_aa': v_gamma_aa,
        'v_gamma_ab': v_gamma_ab,
        'v_gamma_bb': v_gamma_bb,
    }

    super.print_detail(3)
    index = 0
    for point in points:
        core.print_out(
            'rho_a= %11.3E, rho_b= %11.3E, gamma_aa= %11.3E, gamma_ab= %11.3E, gamma_bb= %11.3E\n'
            % (rho_a[index], rho_b[index], gamma_aa[index], gamma_ab[index],
               gamma_bb[index]))

        for task in tasks:
            v_ref = point[task]
            v_obs = mapping[task][index]
            delta = v_obs - v_ref
            if (v_ref == 0.0):
                epsilon = 0.0
            else:
                epsilon = abs(delta / v_ref)
            if (epsilon < 1.0E-11):
                passed = 'PASSED'
            else:
                passed = 'FAILED'
                check = False

            core.print_out('\t%-15s %24.16E %24.16E %24.16E %24.16E %6s\n' %
                           (task, v_ref, v_obs, delta, epsilon, passed))

        index = index + 1

    core.print_out('\n')
    return check
Example #9
0
def _write_molden(
    self: core.Wavefunction,
    filename: Optional[str] = None,
    do_virtual: Optional[bool] = None,
    use_natural: bool = False,
):
    """Writes wavefunction information in *wfn* to *filename* in
    molden format. Will write natural orbitals from *density* (MO basis) if supplied.
    Warning! most post-SCF wavefunctions do not build the density as this is often
    much more costly than the energy. In addition, the wavefunction density attributes
    (Da and Db) return the SO density and must be transformed to the MO basis
    to use with this function.

    .. versionadded:: 0.5
       *wfn* parameter passed explicitly

    :returns: None

    :type filename:
    :param filename:

        Destination file name for MOLDEN file. If unspecified (None), a file
        name will be generated from the molecule name.

    :type do_virtual:
    :param do_virtual:

        Do write all the MOs to the MOLDEN file (True) or discard the unoccupied
        MOs (False). Not valid for NO's. If unspecified (None), value taken from
        :term:`MOLDEN_WITH_VIRTUAL <MOLDEN_WITH_VIRTUAL (GLOBALS)>`.

    :type use_natural:
    :param use_natural:

        Write natural orbitals determined from density on wavefunction.

    :examples:

    1. Molden file with the Kohn-Sham orbitals of a DFT calculation.

       >>> E, wfn = energy('b3lyp', return_wfn=True)
       >>> wfn.molden('mycalc.molden')

    2. Molden file with the natural orbitals of a CCSD computation. For correlated methods, an energy call will not compute the density.
       "properties" or "gradient" must be called.

       >>> E, wfn = properties('ccsd', return_wfn=True)
       >>> wfn.molden('ccsd_no.molden', use_natural=True)

    3. To supply a custom density matrix, manually set the Da and Db of the wavefunction.
       This is used, for example, to write natural orbitals coming from a root computed
       by a ``CIWavefunction`` computation, e.g., ``detci``, ``fci``, ``casscf``.
       The first two arguments of :py:meth:`~psi4.core.CIWavefunction.get_opdm`
       can be set to ``n, n`` where n => 0 selects the root to
       write out, provided these roots were computed, see :term:`NUM_ROOTS <NUM_ROOTS (DETCI)>`. The
       third argument controls the spin (``"A"``, ``"B"`` or ``"SUM"``) and the final
       boolean option determines whether inactive orbitals are included.

       >>> E, wfn = energy('detci', return_wfn=True)
       >>> wfn.Da() = wfn.get_opdm(0, 0, "A", True)
       >>> wfn.Db() = wfn.get_opdm(0, 0, "B", True)
       >>> molden(wfn, 'no_root1.molden', use_natural=True)

    """

    if filename is None:
        filename = core.get_writer_file_prefix(
            self.molecule().name()) + ".molden"

    if do_virtual is None:
        do_virtual = bool(core.get_option("SCF", "MOLDEN_WITH_VIRTUAL"))

    basisset = self.basisset()
    mol = self.molecule()
    # Header and geometry (Atom, Atom #, Z, x, y, z)
    mol_string = '[Molden Format]\n[Atoms] (AU)\n'
    for atom in range(mol.natom()):
        mol_string += f"{mol.symbol(atom):2s}  {atom+1:2d}  {int(mol.Z(atom)):3d}   {mol.x(atom):20.10f} {mol.y(atom):20.10f} {mol.z(atom):20.10f}\n"

    # Dump basis set
    mol_string += '[GTO]\n'
    for atom in range(mol.natom()):
        mol_string += f"  {atom+1:d} 0\n"
        for rel_shell_idx in range(basisset.nshell_on_center(atom)):
            abs_shell_idx = basisset.shell_on_center(atom, rel_shell_idx)
            shell = basisset.shell(abs_shell_idx)
            mol_string += f" {shell.amchar:s}{shell.nprimitive:5d}  1.00\n"
            for prim in range(shell.nprimitive):
                mol_string += f"{shell.exp(prim):20.10f} {shell.original_coef(prim):20.10f}\n"
        mol_string += '\n'

    #
    if use_natural:
        # Alphas
        nmopi = self.nmopi()
        #MO_Da = core.Matrix("MO Alpha Density Matrix", nmopi, nmopi)
        #MO_Da.transform(self.Da(), self.Ca().transpose())
        MO_Da = self.Da_subset("MO")  #MO_Da.transform(self.Da(), self.Ca())
        NO_Ra = core.Matrix("NO Alpha Rotation Matrix", nmopi, nmopi)
        occupation_a = core.Vector(nmopi)
        MO_Da.diagonalize(NO_Ra, occupation_a,
                          core.DiagonalizeOrder.Descending)
        Ca = core.doublet(self.Ca(), NO_Ra, False, False)
        epsilon_a = occupation_a
        # Betas
        #MO_Db = core.Matrix("MO Beta Density Matrix", nmopi, nmopi)
        #MO_Db.transform(self.Db(), self.Cb().transpose())
        MO_Db = self.Db_subset("MO")
        NO_Rb = core.Matrix("NO Beta Rotation Matrix", nmopi, nmopi)
        occupation_b = core.Vector(nmopi)
        MO_Db.diagonalize(NO_Rb, occupation_b,
                          core.DiagonalizeOrder.Descending)
        Cb = core.doublet(self.Cb(), NO_Rb, False, False)
        epsilon_b = occupation_b

    else:
        Ca = self.Ca()
        Cb = self.Cb()
        occupation_a = self.occupation_a()
        occupation_b = self.occupation_b()
        epsilon_a = self.epsilon_a()
        epsilon_b = self.epsilon_b()

    # Convert C matrices to AO MO basis. Ca_subset costs information about which symmetry an orbital originally had, which is why we can't use it.
    aotoso = self.aotoso()
    Ca_ao_mo = core.doublet(aotoso, Ca, False, False).nph
    Cb_ao_mo = core.doublet(aotoso, Cb, False, False).nph
    ao_overlap = self.mintshelper().ao_overlap().np
    # Convert from Psi4 internal normalization to the unit normalization expected by Molden
    ao_normalizer = ao_overlap.diagonal()**(-1 / 2)
    Ca_ao_mo = core.Matrix.from_array([(i.T / ao_normalizer).T
                                       for i in Ca_ao_mo])
    Cb_ao_mo = core.Matrix.from_array([(i.T / ao_normalizer).T
                                       for i in Cb_ao_mo])

    # Reorder AO x MO matrix to fit Molden conventions
    '''
    Reordering expected by Molden
    P: x, y, z
    5D: D 0, D+1, D-1, D+2, D-2
    6D: xx, yy, zz, xy, xz, yz
    7F: F 0, F+1, F-1, F+2, F-2, F+3, F-3
    10F: xxx, yyy, zzz, xyy, xxy, xxz, xzz, yzz, yyz, xyz
    9G: G 0, G+1, G-1, G+2, G-2, G+3, G-3, G+4, G-4
    15G: xxxx, yyyy, zzzz, xxxy, xxxz, yyyz, zzzx, zzzy, xxyy, xxzz, yyzz, xxyz, yyxz, zzxy
    
    Molden does not handle angular momenta higher than G
    '''
    molden_cartesian_order = [
        [2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],  # p
        [0, 3, 4, 1, 5, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0],  # d
        [0, 4, 5, 3, 9, 6, 1, 8, 7, 2, 0, 0, 0, 0, 0],  # f
        [0, 3, 4, 9, 12, 10, 5, 13, 14, 7, 1, 6, 11, 8, 2]  # g
    ]
    nirrep = self.nirrep()
    count = 0  # Keeps track of count for reordering
    temp_a = Ca_ao_mo.clone()  # Placeholders for original AO x MO matrices
    temp_b = Cb_ao_mo.clone()

    for i in range(basisset.nshell()):
        am = basisset.shell(i).am
        if (am == 1 and basisset.has_puream()) or (
                am > 1 and am < 5 and basisset.shell(i).is_cartesian()):
            for j in range(basisset.shell(i).nfunction):
                for h in range(nirrep):
                    for k in range(Ca_ao_mo.coldim()[h]):
                        Ca_ao_mo.set(h,
                                     count + molden_cartesian_order[am - 1][j],
                                     k, temp_a.get(h, count + j, k))
                        Cb_ao_mo.set(h,
                                     count + molden_cartesian_order[am - 1][j],
                                     k, temp_b.get(h, count + j, k))
        count += basisset.shell(i).nfunction

    # Dump MO information
    if basisset.has_puream():
        # For historical reasons, D and F can go on the same line, but setting D without F implicitly sets F. G must be on its own.
        mol_string += '[5D7F]\n[9G]\n\n'
    ct = mol.point_group().char_table()
    mol_string += '[MO]\n'
    mo_dim = self.nmopi() if do_virtual else (self.doccpi() + self.soccpi())

    # Alphas. If Alphas and Betas are the same, then only Alphas with double occupation will be written (see line marked "***")
    mos = []
    for h in range(nirrep):
        for n in range(mo_dim[h]):
            mos.append((epsilon_a.get(h, n), (h, n)))

    # Sort mos based on energy
    def mosSort(element):
        return element[0]

    mos.sort(key=mosSort)

    for i in range(len(mos)):
        h, n = mos[i][1]
        mol_string += f" Sym= {ct.gamma(h).symbol():s}\n Ene= {epsilon_a.get(h, n):24.10e}\n Spin= Alpha\n"
        if self.same_a_b_orbs() and self.epsilon_a() == self.epsilon_b(
        ) and self.same_a_b_dens():
            mol_string += f" Occup= {occupation_a.get(h, n) + occupation_b.get(h, n):24.10e}\n"
        else:
            mol_string += f" Occup= {occupation_a.get(h, n):24.10e}\n"
        for so in range(self.nso()):
            mol_string += f"{so+1:3d} {Ca_ao_mo.get(h, so, n):24.10e}\n"

    # Betas
    mos = []
    if not self.same_a_b_orbs(
    ) or self.epsilon_a() != self.epsilon_b() or not self.same_a_b_dens():
        for h in range(nirrep):
            for n in range(mo_dim[h]):
                mos.append((self.epsilon_b().get(h, n), (h, n)))
        mos.sort(key=mosSort)
        for i in range(len(mos)):
            h, n = mos[i][1]
            mol_string += f" Sym= {ct.gamma(h).symbol():s}\n Ene= {epsilon_b.get(h, n):24.10e}\n Spin= Beta\n " \
                          f"Occup= {occupation_b.get(h, n):24.10e}\n"
            for so in range(self.nso()):
                mol_string += f"{so+1:3d} {Cb_ao_mo.get(h, so, n):24.10e}\n"

    # Write Molden string to file
    with open(filename, 'w') as fn:
        fn.write(mol_string)