def test_be_no_mf(self): """ Test the CCSDSolver against result from reference implementation with mean field not calculated.""" mol = gto.Mole() mol.atom = Be mol.basis = "3-21g" mol.charge = 0 mol.spin = 0 mol.build() solver = CCSDSolver() energy = solver.simulate(mol) self.assertAlmostEqual(energy, -14.531416589890926, places=8)
def test_h2_with_mf(self): """ Test the CCSDSolver against result from reference implementation with mean field calculated and passed in.""" mol = gto.Mole() mol.atom = H2 mol.basis = "3-21g" mol.charge = 0 mol.spin = 0 mol.build() mf = scf.RHF(mol) mf.verbose = 0 mf.scf() solver = CCSDSolver() energy = solver.simulate(mol, mf) self.assertAlmostEqual(energy, -1.1478300596229851, places=8)
def test_tetra_ml_fci_no_mf(self): mol = gto.Mole() mol.atom = TETRA mol.basis = "3-21g" mol.charge = 0 mol.spin = 0 mol.build() solver = DMETProblemDecomposition() solver.electron_localization_method = meta_lowdin_localization solver.electronic_structure_solver = CCSDSolver() energy = solver.simulate(mol, [1,1,1,1,1,1,1,1]) self.assertAlmostEqual(energy, -153.1879732845, places=6)
def test_h4ring_ml_ccsd_no_mf_minao(self): """ Tests the result from DMET against a value from a reference implementation with meta-lowdin localization and CCSD solution to fragments.""" mol = gto.Mole() mol.atom = H4_RING mol.basis = "minao" mol.charge = 0 mol.spin = 0 mol.build() solver = DMETProblemDecomposition() solver.electron_localization_method = meta_lowdin_localization solver.electronic_structure_solver = CCSDSolver() energy = solver.simulate(mol, [1,1,1,1]) self.assertAlmostEqual(energy, -1.9916120594, places=6)
def test_h4ring_iao_ccsd_no_mf_321g(self): """ Tests the result from DMET against a value from a reference implementation with IAO localization, 3-21g basis, and CCSD solution to fragments.""" mol = gto.Mole() mol.atom = H4_RING mol.basis = "3-21g" mol.charge = 0 mol.spin = 0 mol.build() solver = DMETProblemDecomposition() solver.electron_localization_method = iao_localization solver.electronic_structure_solver = CCSDSolver() energy = solver.simulate(mol, [2,2]) self.assertAlmostEqual(energy, -2.0290205366, places=6)
def test_solver_mix(self): """Tests that solving with multiple solvers works. With this simple system, we can assume that both CCSD and FCI can reach chemical accuracy.""" mol = gto.Mole() mol.atom = H4_RING mol.basis = "3-21g" mol.charge = 0 mol.spin = 0 mol.build() solver = DMETProblemDecomposition() solver.electron_localization_method = iao_localization fci = FCISolver() ccsd = CCSDSolver() energy = solver.simulate(mol, [1, 1, 1, 1], fragment_solvers=[fci, fci, ccsd, ccsd]) self.assertAlmostEqual(energy, -2.0284, places=4)
def test_get_rdm_without_simulate(self): """Test that the runtime error is raised when user calls get RDM without first running a simulation.""" solver = CCSDSolver() self.assertRaises(RuntimeError, solver.get_rdm)
def __init__(self): self.verbose = False self.electronic_structure_solver = CCSDSolver() self.electron_localization_method = iao_localization
class DMETProblemDecomposition(ProblemDecomposition): """Employ DMET as a problem decomposition technique. DMET single-shot algorithm is used for problem decomposition technique. By default, CCSD is used as the electronic structure solver, and IAO is used for the localization scheme. Users can define other electronic structure solver such as FCI or VQE as an impurity solver. Meta-Lowdin localization scheme can be used instead of the IAO scheme, which cannot be used for minimal basis set. Attributes: electronic_structure_solver (subclass of ElectronicStructureSolver): A type of electronic structure solver. Default is CCSD. electron_localization_method (string): A type of localization scheme. Default is IAO. """ def __init__(self): self.verbose = False self.electronic_structure_solver = CCSDSolver() self.electron_localization_method = iao_localization def simulate(self, molecule, fragment_atoms, mean_field=None, fragment_solvers=None): """Perform DMET single-shot calculation. If the mean field is not provided it is automatically calculated. Args: molecule (pyscf.gto.Mole): The molecule to simulate. fragment_atoms (list): List of number of atoms for each fragment (int). mean_field (pyscf.scf.RHF): The mean field of the molecule. fragment_solvers (list): List of ElectronicStructureSolvers with which to solve each fragment. If None is passed here, the solver that is provided for the `electronic_structure_solver` attribute is used. Return: float64: The DMET energy (dmet_energy). Raise: RuntimeError: If the sum of the atoms in the fragments is different from the number of atoms in the molecule. RuntimeError: If the number fragments is different from the number of solvers. """ # Check if the number of fragment sites is equal to the number of atoms in the molecule if molecule.natm != sum(fragment_atoms): raise RuntimeError( "The number of fragment sites is not equal to the number of atoms in the molecule" ) # Check that the number of solvers matches the number of fragments. if fragment_solvers: if len(fragment_solvers) != len(fragment_atoms): raise RuntimeError( "The number of solvers does not match the number of fragments." ) # Calculate the mean field if the user has not already done it. if not mean_field: mean_field = scf.RHF(molecule) mean_field.verbose = 0 mean_field.scf() # Check the convergence of the mean field if not mean_field.converged: warnings.warn("DMET simulating with mean field not converged.", RuntimeWarning) # Construct orbital object orbitals = helpers._orbitals(molecule, mean_field, range(molecule.nao_nr()), self.electron_localization_method) # TODO: remove last argument, combining fragments not supported orb_list, orb_list2, atom_list2 = helpers._fragment_constructor( molecule, fragment_atoms, 0) # Initialize the energy list and SCF procedure employing newton-raphson algorithm energy = [] chemical_potential = 0.0 chemical_potential = scipy.optimize.newton(self._oneshot_loop, chemical_potential, args=(orbitals, orb_list, orb_list2, energy, fragment_solvers), tol=1e-5) # Get the final energy value niter = len(energy) dmet_energy = energy[niter - 1] if self.verbose: print(' \t*** DMET Cycle Done *** ') print(' \tDMET Energy ( a.u. ) = ' + '{:17.10f}'.format(dmet_energy)) print(' \tChemical Potential = ' + '{:17.10f}'.format(chemical_potential)) return dmet_energy def _oneshot_loop(self, chemical_potential, orbitals, orb_list, orb_list2, energy_list, solvers=None): """Perform the DMET loop. This is the function which runs in the minimizer. DMET calculation converges when the chemical potential is below the threshold value of the Newton-Rhapson optimizer. Args: chemical_potential (float64): The Chemical potential. orbitals (numpy.array): The localized orbitals (float64). orb_list (list): The number of orbitals for each fragment (int). orb_list2 (list): List of lists of the minimum and maximum orbital label for each fragment (int). energy_list (list): List of DMET energy for each iteration (float64). solvers (list): List of ElectronicStructureSolvers used to solve each fragment. Returns: float64: The new chemical potential. """ # Calculate the 1-RDM for the entire molecule onerdm_low = helpers._low_rdm(orbitals.active_fock, orbitals.number_active_electrons) niter = len(energy_list) + 1 if self.verbose: print(" \tIteration = ", niter) print(' \t----------------') print(' ') number_of_electron = 0.0 energy_temp = 0.0 for i, norb in enumerate(orb_list): if self.verbose: print("\t\tFragment Number : # ", i + 1) print('\t\t------------------------') t_list = [] t_list.append(norb) temp_list = orb_list2[i] # Construct bath orbitals bath_orb, e_occupied = helpers._fragment_bath( orbitals.mol_full, t_list, temp_list, onerdm_low) # Obtain one particle rdm for a fragment norb_high, nelec_high, onerdm_high = helpers._fragment_rdm( t_list, bath_orb, e_occupied, orbitals.number_active_electrons) # Obtain one particle rdm for a fragment one_ele, fock, two_ele = orbitals.dmet_fragment_hamiltonian( bath_orb, norb_high, onerdm_high) # Construct guess orbitals for fragment SCF calculations guess_orbitals = helpers._fragment_guess(t_list, bath_orb, chemical_potential, norb_high, nelec_high, orbitals.active_fock) # Carry out SCF calculation for a fragment mf_fragment, fock_frag_copy, mol_frag = helpers._fragment_scf( t_list, two_ele, fock, nelec_high, norb_high, guess_orbitals, chemical_potential) # Solve the electronic structure and calculate the RDMs energy = None cc_onerdm = None cc_twordm = None if solvers: energy = (solvers[i]).simulate(mol_frag, mf_fragment) cc_onerdm, cc_twordm = (solvers[i]).get_rdm() else: energy = self.electronic_structure_solver.simulate( mol_frag, mf_fragment) cc_onerdm, cc_twordm = self.electronic_structure_solver.get_rdm( ) # Compute the fragment energy fragment_energy, total_energy_rdm, one_rdm = self._compute_energy( mf_fragment, cc_onerdm, cc_twordm, fock_frag_copy, t_list, one_ele, two_ele, fock) # Sum up the energy energy_temp += fragment_energy # Sum up the number of electrons number_of_electron += np.trace(one_rdm[:t_list[0], :t_list[0]]) if self.verbose: print("\t\tFragment Energy = " + '{:17.10f}'.format(fragment_energy)) print("\t\tNumber of Electrons in Fragment = " + '{:17.10f}'.format(np.trace(one_rdm))) print('') energy_temp += orbitals.core_constant_energy energy_list.append(energy_temp) return number_of_electron - orbitals.number_active_electrons def _compute_energy(self, mf_frag, onerdm, twordm, fock_frag_copy, t_list, oneint, twoint, fock): """Calculate the fragment energy. Args: mean_field (pyscf.scf.RHF): The mean field of the fragment. cc_onerdm (numpy.array): one-particle reduced density matrix (float64). cc_twordm (numpy.array): two-particle reduced density matrix (float64). fock_frag_copy (numpy.array): Fock matrix with the chemical potential subtracted (float64). t_list (list): List of number of fragment and bath orbitals (int). oneint (numpy.array): One-electron integrals of fragment (float64). twoint (numpy.array): Two-electron integrals of fragment (float64). fock (numpy.array): Fock matrix of fragment (float64). Returns: float64: Fragment energy (fragment_energy). float64: Total energy for fragment using RDMs (total_energy_rdm). numpy.array: One-particle RDM for a fragment (one_rdm, float64). """ # Execute CCSD calculation norb = t_list[0] # Calculate the one- and two- RDM for DMET energy calculation (Transform to AO basis) one_rdm = reduce(np.dot, (mf_frag.mo_coeff, onerdm, mf_frag.mo_coeff.T)) twordm = np.einsum('pi,ijkl->pjkl', mf_frag.mo_coeff, twordm) twordm = np.einsum('qj,pjkl->pqkl', mf_frag.mo_coeff, twordm) twordm = np.einsum('rk,pqkl->pqrl', mf_frag.mo_coeff, twordm) twordm = np.einsum('sl,pqrl->pqrs', mf_frag.mo_coeff, twordm) # Calculate the total energy based on RDMs total_energy_rdm = np.einsum( 'ij,ij->', fock_frag_copy, one_rdm) + 0.5 * np.einsum('ijkl,ijkl->', twoint, twordm) # Calculate fragment expectation value fragment_energy_one_rdm = 0.25 * np.einsum('ij,ij->', one_rdm[ : norb, : ], fock[: norb, : ] + oneint[ : norb, : ]) \ + 0.25 * np.einsum('ij,ij->', one_rdm[ : , : norb ], fock[: , : norb] + oneint[ : , : norb]) fragment_energy_twordm = 0.125 * np.einsum('ijkl,ijkl->', twordm[ : norb, : , : , : ], twoint[ : norb, : , : , : ]) \ + 0.125 * np.einsum('ijkl,ijkl->', twordm[ : , : norb, : , : ], twoint[ : , : norb, : , : ]) \ + 0.125 * np.einsum('ijkl,ijkl->', twordm[ : , : , : norb, : ], twoint[ : , : , : norb, : ]) \ + 0.125 * np.einsum('ijkl,ijkl->', twordm[ : , : , : , : norb ], twoint[ : , : , : , : norb]) fragment_energy = fragment_energy_one_rdm + fragment_energy_twordm return fragment_energy, total_energy_rdm, one_rdm