From 899a1ffb755fe1c8bc5be732cee944b1f15c4faa Mon Sep 17 00:00:00 2001 From: AlexandreF-1qbit <76115575+AlexandreF-1qbit@users.noreply.github.com> Date: Tue, 13 Jun 2023 13:10:07 -0400 Subject: [PATCH 1/8] Support for UMP2 initial parameters. --- tangelo/algorithms/classical/mp2_solver.py | 63 +++++++++++++++++-- .../classical/tests/test_mp2_solver.py | 27 +++++++- tangelo/toolboxes/ansatz_generator/uccsd.py | 35 +++-------- 3 files changed, 95 insertions(+), 30 deletions(-) diff --git a/tangelo/algorithms/classical/mp2_solver.py b/tangelo/algorithms/classical/mp2_solver.py index 253854bda..b5a73c716 100644 --- a/tangelo/algorithms/classical/mp2_solver.py +++ b/tangelo/algorithms/classical/mp2_solver.py @@ -15,8 +15,12 @@ """Class performing electronic structure calculation employing the Moller-Plesset perturbation theory (MP2) method. """ +from itertools import combinations, product +from math import ceil + from tangelo.algorithms.electronic_structure_solver import ElectronicStructureSolver from tangelo.helpers.utils import is_package_installed +from tangelo.toolboxes.ansatz_generator._unitary_cc_openshell import uccsd_openshell_get_packed_amplitudes class MP2Solver(ElectronicStructureSolver): @@ -37,9 +41,6 @@ def __init__(self, molecule): raise ModuleNotFoundError(f"Using {self.__class__.__name__} requires the installation of the pyscf package") from pyscf import mp - if molecule.uhf: - raise NotImplementedError(f"SecondQuantizedMolecule that use UHF are not currently supported in {self.__class__.__name__}. Use CCSDSolver") - self.mp = mp self.mp2_fragment = None @@ -49,6 +50,13 @@ def __init__(self, molecule): self.frozen = molecule.frozen_mos self.uhf = molecule.uhf + if self.spin != 0 or self.uhf: + self.n_alpha, self.n_beta = molecule.n_active_ab_electrons + self.n_active_moa, self.n_active_mob = molecule.n_active_mos if self.uhf else (molecule.n_active_mos, molecule.n_active_mos) + else: + self.n_occupied = ceil(molecule.n_active_electrons / 2) + self.n_virtual = molecule.n_active_mos - self.n_occupied + def simulate(self): """Perform the simulation (energy calculation) for the molecule. @@ -56,7 +64,11 @@ def simulate(self): float: MP2 energy. """ # Execute MP2 calculation - self.mp2_fragment = self.mp.MP2(self.mean_field, frozen=self.frozen) + if self.uhf: + self.mp2_fragment = self.mp.UMP2(self.mean_field, frozen=self.frozen) + else: + self.mp2_fragment = self.mp.RMP2(self.mean_field, frozen=self.frozen) + self.mp2_fragment.verbose = 0 _, self.mp2_t2 = self.mp2_fragment.kernel() @@ -85,3 +97,46 @@ def get_rdm(self): two_rdm = self.mp2_fragment.make_rdm2() return one_rdm, two_rdm + + def get_mp2_params(self): + """Computes the MP2 initial variational parameters. Compute the initial + variational parameters with PySCF MP2 calculation, and then reorders the + elements into the appropriate convention. MP2 only has doubles (T2) + amplitudes, thus the single (T1) amplitudes are set to a small non-zero + value and added. The ordering is single, double (diagonal), double + (non-diagonal). + + Returns: + list of float: The initial variational parameters. + """ + + # Check if MP2 is performed + if self.mp2_fragment is None: + raise RuntimeError("MP2Solver: Cannot retrieve MP2 parameters. Please run the 'simulate' method first") + + if self.spin != 0 or self.uhf: + # Reorder the T2 amplitudes in a dense list. + mp2_params = uccsd_openshell_get_packed_amplitudes( + self.mp2_t2[0], # aa + self.mp2_t2[2], # bb + self.mp2_t2[1], # ab + self.n_alpha, + self.n_beta, + self.n_active_moa, + self.n_active_mob + ) + else: + # Get singles amplitude. Just get "up" amplitude, since "down" should be the same + singles = [2.e-5] * (self.n_virtual * self.n_occupied) + + # Get singles and doubles amplitudes associated with one spatial occupied-virtual pair + doubles_1 = [-self.mp2_t2[q, q, p, p]/2. if (abs(-self.mp2_t2[q, q, p, p]/2.) > 1e-15) else 0. + for p, q in product(range(self.n_virtual), range(self.n_occupied))] + + # Get doubles amplitudes associated with two spatial occupied-virtual pairs + doubles_2 = [-self.mp2_t2[q, s, p, r] for (p, q), (r, s) + in combinations(product(range(self.n_virtual), range(self.n_occupied)), 2)] + + mp2_params = singles + doubles_1 + doubles_2 + + return mp2_params diff --git a/tangelo/algorithms/classical/tests/test_mp2_solver.py b/tangelo/algorithms/classical/tests/test_mp2_solver.py index e4dd551cd..7f3abad25 100644 --- a/tangelo/algorithms/classical/tests/test_mp2_solver.py +++ b/tangelo/algorithms/classical/tests/test_mp2_solver.py @@ -14,8 +14,10 @@ import unittest +import numpy as np + from tangelo.algorithms.classical import MP2Solver -from tangelo.molecule_library import mol_H2_321g, mol_Be_321g, mol_H4_cation_sto3g +from tangelo.molecule_library import mol_H2_321g, mol_Be_321g, mol_H2_sto3g, mol_H2_sto3g_uhf class MP2SolverTest(unittest.TestCase): @@ -59,6 +61,29 @@ def test_be_frozen_core(self): self.assertAlmostEqual(energy, -14.5092873, places=6) + def test_get_mp2_params_restricted(self): + """Test the packing of RMP2 amplitudes as intial parameter for UCC + methods. + """ + + solver = MP2Solver(mol_H2_sto3g) + solver.simulate() + + ref_params = [2.e-05, 3.632537e-02] + + np.testing.assert_array_almost_equal(ref_params, solver.get_mp2_params()) + + def test_get_mp2_params_unrestricted(self): + """Test the packing of UMP2 amplitudes as intial parameter for UCC-like + methods. + """ + + solver = MP2Solver(mol_H2_sto3g_uhf) + solver.simulate() + ref_params = [0., 0., 0.030736] + + np.testing.assert_array_almost_equal(ref_params, solver.get_mp2_params()) + if __name__ == "__main__": unittest.main() diff --git a/tangelo/toolboxes/ansatz_generator/uccsd.py b/tangelo/toolboxes/ansatz_generator/uccsd.py index 0e1ab33ec..1a6a7ed20 100644 --- a/tangelo/toolboxes/ansatz_generator/uccsd.py +++ b/tangelo/toolboxes/ansatz_generator/uccsd.py @@ -28,13 +28,10 @@ Physical Review A 95, 020501 (2017). """ -import itertools - import numpy as np from openfermion.circuits import uccsd_singlet_generator from tangelo import SecondQuantizedMolecule -from tangelo.algorithms.classical.mp2_solver import MP2Solver from tangelo.linq import Circuit from tangelo.helpers.utils import is_package_installed from tangelo.toolboxes.qubit_mappings.mapping_transform import fermion_to_qubit_mapping @@ -99,11 +96,11 @@ def __init__(self, molecule, mapping="JW", up_then_down=False, spin=None, refere # TODO: support for others self.supported_reference_state = {"HF", "zero"} # Supported var param initialization - self.supported_initial_var_params = {"ones", "random", "mp2"} if (self.spin == 0 and not self.molecule.uhf) else {"ones", "random"} + self.supported_initial_var_params = {"ones", "random", "mp2"} # Default initial parameters for initialization # TODO: support for openshell MP2 initialization - self.var_params_default = "mp2" if "mp2" in self.supported_initial_var_params else "ones" + self.var_params_default = "mp2" self.reference_state = reference_state self.var_params = None @@ -255,22 +252,21 @@ def _get_openshell_qubit_operator(self): return qubit_op def _compute_mp2_params(self): - """Computes the MP2 initial variational parameters. Compute the initial - variational parameters with PySCF MP2 calculation, and then reorders the - elements into the appropriate convention. MP2 only has doubles (T2) - amplitudes, thus the single (T1) amplitudes are set to a small non-zero - value and added. The ordering is single, double (diagonal), double - (non-diagonal). + """Computes the MP2 initial variational parameters. The request is made + via the MP2Solver class. Returns: list of float: The initial variational parameters. """ - if self.molecule.uhf: - raise NotImplementedError(f"MP2 initialization is not currently implemented for UHF reference in {self.__class__}") if not is_package_installed("pyscf"): raise ValueError(f"pyscf is required for MP2 initial parameters in {self.__class__.__name__}.") + # Import here to solve an AttributeError: partially initialized module + # tangelo.toolboxes.ansatz_generator' has no attribute 'UCCSD' + # (most likely due to a circular import). + from tangelo.algorithms.classical.mp2_solver import MP2Solver + if not isinstance(self.molecule.solver, IntegralSolverPySCF): pymol = SecondQuantizedMolecule(self.molecule.xyz, self.molecule.q, self.molecule.spin, basis=self.molecule.basis, ecp=self.molecule.ecp, symmetry=self.molecule.symmetry, uhf=self.molecule.uhf, @@ -281,15 +277,4 @@ def _compute_mp2_params(self): mp2 = MP2Solver(pymol) mp2.simulate() - # Get singles amplitude. Just get "up" amplitude, since "down" should be the same - singles = [2.e-5] * (self.n_virtual * self.n_occupied) - - # Get singles and doubles amplitudes associated with one spatial occupied-virtual pair - doubles_1 = [-mp2.mp2_t2[q, q, p, p]/2. if (abs(-mp2.mp2_t2[q, q, p, p]/2.) > 1e-15) else 0. - for p, q in itertools.product(range(self.n_virtual), range(self.n_occupied))] - - # Get doubles amplitudes associated with two spatial occupied-virtual pairs - doubles_2 = [-mp2.mp2_t2[q, s, p, r] for (p, q), (r, s) - in itertools.combinations(itertools.product(range(self.n_virtual), range(self.n_occupied)), 2)] - - return singles + doubles_1 + doubles_2 + return mp2.get_mp2_params() From 104f2c42986709c8aef5a8c26e7cc82031d2152c Mon Sep 17 00:00:00 2001 From: AlexandreF-1qbit <76115575+AlexandreF-1qbit@users.noreply.github.com> Date: Wed, 14 Jun 2023 15:33:35 -0400 Subject: [PATCH 2/8] Code improvement, fix doc. --- tangelo/algorithms/classical/mp2_solver.py | 2 +- tangelo/algorithms/classical/tests/test_mp2_solver.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tangelo/algorithms/classical/mp2_solver.py b/tangelo/algorithms/classical/mp2_solver.py index b5a73c716..818e51cb8 100644 --- a/tangelo/algorithms/classical/mp2_solver.py +++ b/tangelo/algorithms/classical/mp2_solver.py @@ -52,7 +52,7 @@ def __init__(self, molecule): if self.spin != 0 or self.uhf: self.n_alpha, self.n_beta = molecule.n_active_ab_electrons - self.n_active_moa, self.n_active_mob = molecule.n_active_mos if self.uhf else (molecule.n_active_mos, molecule.n_active_mos) + self.n_active_moa, self.n_active_mob = molecule.n_active_mos if self.uhf else (molecule.n_active_mos,)*2 else: self.n_occupied = ceil(molecule.n_active_electrons / 2) self.n_virtual = molecule.n_active_mos - self.n_occupied diff --git a/tangelo/algorithms/classical/tests/test_mp2_solver.py b/tangelo/algorithms/classical/tests/test_mp2_solver.py index 7f3abad25..9be05fc26 100644 --- a/tangelo/algorithms/classical/tests/test_mp2_solver.py +++ b/tangelo/algorithms/classical/tests/test_mp2_solver.py @@ -62,8 +62,8 @@ def test_be_frozen_core(self): self.assertAlmostEqual(energy, -14.5092873, places=6) def test_get_mp2_params_restricted(self): - """Test the packing of RMP2 amplitudes as intial parameter for UCC - methods. + """Test the packing of RMP2 amplitudes as initial parameters for coupled + cluster based methods. """ solver = MP2Solver(mol_H2_sto3g) @@ -74,8 +74,8 @@ def test_get_mp2_params_restricted(self): np.testing.assert_array_almost_equal(ref_params, solver.get_mp2_params()) def test_get_mp2_params_unrestricted(self): - """Test the packing of UMP2 amplitudes as intial parameter for UCC-like - methods. + """Test the packing of UMP2 amplitudes as initial parameters for coupled + cluster based methods. """ solver = MP2Solver(mol_H2_sto3g_uhf) From 8496ee05e59e1711fdd9421a01974d1a61c96d26 Mon Sep 17 00:00:00 2001 From: AlexandreF-1qbit <76115575+AlexandreF-1qbit@users.noreply.github.com> Date: Wed, 14 Jun 2023 15:34:33 -0400 Subject: [PATCH 3/8] Unecessary comment. --- tangelo/toolboxes/ansatz_generator/uccsd.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tangelo/toolboxes/ansatz_generator/uccsd.py b/tangelo/toolboxes/ansatz_generator/uccsd.py index 1a6a7ed20..0937e5f5f 100644 --- a/tangelo/toolboxes/ansatz_generator/uccsd.py +++ b/tangelo/toolboxes/ansatz_generator/uccsd.py @@ -99,7 +99,6 @@ def __init__(self, molecule, mapping="JW", up_then_down=False, spin=None, refere self.supported_initial_var_params = {"ones", "random", "mp2"} # Default initial parameters for initialization - # TODO: support for openshell MP2 initialization self.var_params_default = "mp2" self.reference_state = reference_state From c27e0fa3dd1cd45089950b500c2da0bdd3d376cf Mon Sep 17 00:00:00 2001 From: AlexandreF-1qbit <76115575+AlexandreF-1qbit@users.noreply.github.com> Date: Thu, 15 Jun 2023 11:27:10 -0400 Subject: [PATCH 4/8] Review fixes. --- tangelo/algorithms/classical/mp2_solver.py | 27 ++++++++++--------- .../classical/tests/test_mp2_solver.py | 4 +-- tangelo/toolboxes/ansatz_generator/uccsd.py | 8 ++++-- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/tangelo/algorithms/classical/mp2_solver.py b/tangelo/algorithms/classical/mp2_solver.py index 818e51cb8..66ce5c2d3 100644 --- a/tangelo/algorithms/classical/mp2_solver.py +++ b/tangelo/algorithms/classical/mp2_solver.py @@ -50,6 +50,8 @@ def __init__(self, molecule): self.frozen = molecule.frozen_mos self.uhf = molecule.uhf + # Define variables used to transform the MP2 parameters into an ordered + # list of parameters with single and double excitations. if self.spin != 0 or self.uhf: self.n_alpha, self.n_beta = molecule.n_active_ab_electrons self.n_active_moa, self.n_active_mob = molecule.n_active_mos if self.uhf else (molecule.n_active_mos,)*2 @@ -63,6 +65,7 @@ def simulate(self): Returns: float: MP2 energy. """ + # Execute MP2 calculation if self.uhf: self.mp2_fragment = self.mp.UMP2(self.mean_field, frozen=self.frozen) @@ -87,32 +90,30 @@ def get_rdm(self): RuntimeError: If no simulation has been run. """ - # Check if MP2 is performed + # Check if MP2 has been performed if self.mp2_fragment is None: - raise RuntimeError("MP2Solver: Cannot retrieve RDM. Please run the 'simulate' method first") + raise RuntimeError(f"{self.__class__.name}: Cannot retrieve RDM. Please run the 'simulate' method first") if self.frozen is not None: - raise RuntimeError("MP2Solver: RDM calculation is not implemented with frozen orbitals.") + raise RuntimeError(f"{self.__class__.name}: RDM calculation is not implemented with frozen orbitals.") one_rdm = self.mp2_fragment.make_rdm1() two_rdm = self.mp2_fragment.make_rdm2() return one_rdm, two_rdm - def get_mp2_params(self): - """Computes the MP2 initial variational parameters. Compute the initial - variational parameters with PySCF MP2 calculation, and then reorders the - elements into the appropriate convention. MP2 only has doubles (T2) - amplitudes, thus the single (T1) amplitudes are set to a small non-zero - value and added. The ordering is single, double (diagonal), double - (non-diagonal). + def get_mp2_amplitudes(self): + """Compute the double amplitudes from the perturbative method, and then + reorders the elements into the appropriate convention. The single (T1) + amplitudes are set to a small non-zero value. The ordering is single, + double (diagonal), double (non-diagonal). Returns: - list of float: The initial variational parameters. + list of float: The electronic excitation amplitudes. """ - # Check if MP2 is performed + # Check if MP2 has been performed. if self.mp2_fragment is None: - raise RuntimeError("MP2Solver: Cannot retrieve MP2 parameters. Please run the 'simulate' method first") + raise RuntimeError(f"{self.__class__.name}: Cannot retrieve MP2 parameters. Please run the 'simulate' method first") if self.spin != 0 or self.uhf: # Reorder the T2 amplitudes in a dense list. diff --git a/tangelo/algorithms/classical/tests/test_mp2_solver.py b/tangelo/algorithms/classical/tests/test_mp2_solver.py index 9be05fc26..4c2433755 100644 --- a/tangelo/algorithms/classical/tests/test_mp2_solver.py +++ b/tangelo/algorithms/classical/tests/test_mp2_solver.py @@ -71,7 +71,7 @@ def test_get_mp2_params_restricted(self): ref_params = [2.e-05, 3.632537e-02] - np.testing.assert_array_almost_equal(ref_params, solver.get_mp2_params()) + np.testing.assert_array_almost_equal(ref_params, solver.get_mp2_amplitudes()) def test_get_mp2_params_unrestricted(self): """Test the packing of UMP2 amplitudes as initial parameters for coupled @@ -82,7 +82,7 @@ def test_get_mp2_params_unrestricted(self): solver.simulate() ref_params = [0., 0., 0.030736] - np.testing.assert_array_almost_equal(ref_params, solver.get_mp2_params()) + np.testing.assert_array_almost_equal(ref_params, solver.get_mp2_amplitudes()) if __name__ == "__main__": diff --git a/tangelo/toolboxes/ansatz_generator/uccsd.py b/tangelo/toolboxes/ansatz_generator/uccsd.py index 0937e5f5f..94fcd3628 100644 --- a/tangelo/toolboxes/ansatz_generator/uccsd.py +++ b/tangelo/toolboxes/ansatz_generator/uccsd.py @@ -251,8 +251,12 @@ def _get_openshell_qubit_operator(self): return qubit_op def _compute_mp2_params(self): - """Computes the MP2 initial variational parameters. The request is made - via the MP2Solver class. + """Computes the MP2 initial variational parameters. Compute the initial + variational parameters with PySCF MP2 calculation, and then reorders the + elements into the appropriate convention. MP2 only has doubles (T2) + amplitudes, thus the single (T1) amplitudes are set to a small non-zero + value and added. The ordering is single, double (diagonal), double + (non-diagonal). Returns: list of float: The initial variational parameters. From d18022938be6cf539dc4faee52f85669c223f341 Mon Sep 17 00:00:00 2001 From: AlexandreF-1qbit <76115575+AlexandreF-1qbit@users.noreply.github.com> Date: Thu, 15 Jun 2023 11:30:36 -0400 Subject: [PATCH 5/8] Doc. --- tangelo/algorithms/classical/mp2_solver.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tangelo/algorithms/classical/mp2_solver.py b/tangelo/algorithms/classical/mp2_solver.py index 66ce5c2d3..eb2bb35ad 100644 --- a/tangelo/algorithms/classical/mp2_solver.py +++ b/tangelo/algorithms/classical/mp2_solver.py @@ -102,10 +102,10 @@ def get_rdm(self): return one_rdm, two_rdm def get_mp2_amplitudes(self): - """Compute the double amplitudes from the perturbative method, and then - reorders the elements into the appropriate convention. The single (T1) - amplitudes are set to a small non-zero value. The ordering is single, - double (diagonal), double (non-diagonal). + """Compute the double amplitudes from the MP2 perturbative method, and + then reorder the elements into a dense list. The single (T1) amplitudes + are set to a small non-zero value. The ordering is single, double + (diagonal), double (non-diagonal). Returns: list of float: The electronic excitation amplitudes. From 7e3df4a83ba70d848829e2cd69b1c3cc47b91c6d Mon Sep 17 00:00:00 2001 From: AlexandreF-1qbit <76115575+AlexandreF-1qbit@users.noreply.github.com> Date: Thu, 15 Jun 2023 11:33:58 -0400 Subject: [PATCH 6/8] Doc uccsd was not saved. --- tangelo/toolboxes/ansatz_generator/uccsd.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/tangelo/toolboxes/ansatz_generator/uccsd.py b/tangelo/toolboxes/ansatz_generator/uccsd.py index 94fcd3628..9ccb527ab 100644 --- a/tangelo/toolboxes/ansatz_generator/uccsd.py +++ b/tangelo/toolboxes/ansatz_generator/uccsd.py @@ -251,12 +251,9 @@ def _get_openshell_qubit_operator(self): return qubit_op def _compute_mp2_params(self): - """Computes the MP2 initial variational parameters. Compute the initial - variational parameters with PySCF MP2 calculation, and then reorders the - elements into the appropriate convention. MP2 only has doubles (T2) - amplitudes, thus the single (T1) amplitudes are set to a small non-zero - value and added. The ordering is single, double (diagonal), double - (non-diagonal). + """Compute the MP2 initial variational parameters. Only double + excitation amplitudes are retrieved from an MP2 calculation (singles + set to a small number close to 0). Returns: list of float: The initial variational parameters. From e488a05a70b90b1c6d848009cd62f4889b86dc0c Mon Sep 17 00:00:00 2001 From: AlexandreF-1qbit <76115575+AlexandreF-1qbit@users.noreply.github.com> Date: Thu, 15 Jun 2023 13:08:13 -0400 Subject: [PATCH 7/8] Wrong method name. --- tangelo/toolboxes/ansatz_generator/uccsd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tangelo/toolboxes/ansatz_generator/uccsd.py b/tangelo/toolboxes/ansatz_generator/uccsd.py index 9ccb527ab..5c7094822 100644 --- a/tangelo/toolboxes/ansatz_generator/uccsd.py +++ b/tangelo/toolboxes/ansatz_generator/uccsd.py @@ -277,4 +277,4 @@ def _compute_mp2_params(self): mp2 = MP2Solver(pymol) mp2.simulate() - return mp2.get_mp2_params() + return mp2.get_mp2_amplitudes() From 062ed577a9cad01280e2da74a565887ea06479b5 Mon Sep 17 00:00:00 2001 From: AlexandreF-1qbit <76115575+AlexandreF-1qbit@users.noreply.github.com> Date: Thu, 15 Jun 2023 14:44:00 -0400 Subject: [PATCH 8/8] name -> __name__. --- tangelo/algorithms/classical/mp2_solver.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tangelo/algorithms/classical/mp2_solver.py b/tangelo/algorithms/classical/mp2_solver.py index eb2bb35ad..0083203fc 100644 --- a/tangelo/algorithms/classical/mp2_solver.py +++ b/tangelo/algorithms/classical/mp2_solver.py @@ -92,9 +92,9 @@ def get_rdm(self): # Check if MP2 has been performed if self.mp2_fragment is None: - raise RuntimeError(f"{self.__class__.name}: Cannot retrieve RDM. Please run the 'simulate' method first") + raise RuntimeError(f"{self.__class__.__name__}: Cannot retrieve RDM. Please run the 'simulate' method first") if self.frozen is not None: - raise RuntimeError(f"{self.__class__.name}: RDM calculation is not implemented with frozen orbitals.") + raise RuntimeError(f"{self.__class__.__name__}: RDM calculation is not implemented with frozen orbitals.") one_rdm = self.mp2_fragment.make_rdm1() two_rdm = self.mp2_fragment.make_rdm2() @@ -113,7 +113,7 @@ def get_mp2_amplitudes(self): # Check if MP2 has been performed. if self.mp2_fragment is None: - raise RuntimeError(f"{self.__class__.name}: Cannot retrieve MP2 parameters. Please run the 'simulate' method first") + raise RuntimeError(f"{self.__class__.__name__}: Cannot retrieve MP2 parameters. Please run the 'simulate' method first") if self.spin != 0 or self.uhf: # Reorder the T2 amplitudes in a dense list.