def test_molecular_operator_consistency(self): # Initialize H2 InteractionOperator. n_qubits = 4 filename = os.path.join(THIS_DIRECTORY, 'data', 'H2_sto-3g_singlet_0.7414') molecule = MolecularData(filename=filename) molecule_interaction = molecule.get_molecular_hamiltonian() molecule_operator = get_fermion_operator(molecule_interaction) constant = molecule_interaction.constant one_body_coefficients = molecule_interaction.one_body_tensor two_body_coefficients = molecule_interaction.two_body_tensor # Perform decomposition. eigenvalues, one_body_squares, one_body_corrections, trunc_error = ( low_rank_two_body_decomposition(two_body_coefficients)) self.assertAlmostEqual(trunc_error, 0.) # Build back operator constant and one-body components. decomposed_operator = FermionOperator((), constant) for p, q in itertools.product(range(n_qubits), repeat=2): term = ((p, 1), (q, 0)) coefficient = (one_body_coefficients[p, q] + one_body_corrections[p, q]) decomposed_operator += FermionOperator(term, coefficient) # Build back two-body component. for l in range(one_body_squares.shape[0]): one_body_operator = FermionOperator() for p, q in itertools.product(range(n_qubits), repeat=2): term = ((p, 1), (q, 0)) coefficient = one_body_squares[l, p, q] if abs(eigenvalues[l]) > 1e-6: self.assertTrue(is_hermitian(one_body_squares[l])) one_body_operator += FermionOperator(term, coefficient) decomposed_operator += eigenvalues[l] * (one_body_operator ** 2) # Test for consistency. difference = normal_ordered(decomposed_operator - molecule_operator) self.assertAlmostEqual(0., difference.induced_norm()) # Decompose with slightly negative operator that must use eigen molecule = MolecularData(filename=filename) molecule.two_body_integrals[0, 0, 0, 0] -= 1 eigenvalues, one_body_squares, one_body_corrections, trunc_error = ( low_rank_two_body_decomposition(two_body_coefficients)) self.assertAlmostEqual(trunc_error, 0.) # Check for property errors with self.assertRaises(TypeError): eigenvalues, one_body_squares, _, trunc_error = ( low_rank_two_body_decomposition( two_body_coefficients + 0.01j, truncation_threshold=1., final_rank=1))
def __init__(self, hamiltonian: InteractionOperator, truncation_threshold: Optional[float]=None, final_rank: Optional[int]=None) -> None: self.truncation_threshold = truncation_threshold self.final_rank = final_rank # Get the chemist matrix. (self.constant, self.one_body_coefficients, chemist_two_body_coefficients) = ( get_chemist_two_body_coefficients(hamiltonian)) # Perform the low rank decomposition of two-body operator. self.eigenvalues, self.one_body_squares, _ = ( low_rank_two_body_decomposition( chemist_two_body_coefficients, truncation_threshold=self.truncation_threshold, final_rank=self.final_rank)) # Get scaled density-density terms and basis transformation matrices. self.scaled_density_density_matrices = [] # type: List[numpy.ndarray] self.basis_change_matrices = [] # type: List[numpy.ndarray] for j in range(len(self.eigenvalues)): density_density_matrix, basis_change_matrix = ( prepare_one_body_squared_evolution(self.one_body_squares[j])) self.scaled_density_density_matrices.append( numpy.real(self.eigenvalues[j] * density_density_matrix)) self.basis_change_matrices.append(basis_change_matrix) super().__init__(hamiltonian)
def __init__(self, hamiltonian: InteractionOperator, truncation_threshold: Optional[float] = 1e-8, final_rank: Optional[int] = None, spin_basis=True) -> None: self.truncation_threshold = truncation_threshold self.final_rank = final_rank # Perform the low rank decomposition of two-body operator. self.eigenvalues, self.one_body_squares, one_body_correction, _ = ( low_rank_two_body_decomposition( hamiltonian.two_body_tensor, truncation_threshold=self.truncation_threshold, final_rank=self.final_rank, spin_basis=spin_basis)) self.one_body_coefficients = (hamiltonian.one_body_tensor + one_body_correction) self.constant = hamiltonian.constant # Get scaled density-density terms and basis transformation matrices. self.scaled_density_density_matrices = [] # type: List[numpy.ndarray] self.basis_change_matrices = [] # type: List[numpy.ndarray] for j in range(len(self.eigenvalues)): density_density_matrix, basis_change_matrix = ( prepare_one_body_squared_evolution(self.one_body_squares[j])) self.scaled_density_density_matrices.append( numpy.real(self.eigenvalues[j] * density_density_matrix)) self.basis_change_matrices.append(basis_change_matrix) super().__init__(hamiltonian)
def test_one_body_square_decomposition(self): # Initialize H2 InteractionOperator. n_qubits = 4 n_orbitals = 2 filename = os.path.join(THIS_DIRECTORY, 'data', 'H2_sto-3g_singlet_0.7414') molecule = MolecularData(filename=filename) molecule_interaction = molecule.get_molecular_hamiltonian() fermion_operator = get_fermion_operator(molecule_interaction) two_body_coefficients = molecule_interaction.two_body_tensor # Decompose. eigenvalues, one_body_squares, one_body_correction, error = ( low_rank_two_body_decomposition(two_body_coefficients)) rank = eigenvalues.size for l in range(rank): one_body_operator = FermionOperator() for p, q in itertools.product(range(n_qubits), repeat=2): term = ((p, 1), (q, 0)) coefficient = one_body_squares[l, p, q] one_body_operator += FermionOperator(term, coefficient) one_body_squared = one_body_operator**2 # Get the squared one-body operator via one-body decomposition. if abs(eigenvalues[l]) < 1e-6: with self.assertRaises(ValueError): prepare_one_body_squared_evolution(one_body_squares[l]) continue else: density_density_matrix, basis_transformation_matrix = ( prepare_one_body_squared_evolution(one_body_squares[l])) two_body_operator = FermionOperator() for p, q in itertools.product(range(n_qubits), repeat=2): term = ((p, 1), (p, 0), (q, 1), (q, 0)) coefficient = density_density_matrix[p, q] two_body_operator += FermionOperator(term, coefficient) # Confirm that the rotations diagonalize the one-body squares. hopefully_diagonal = basis_transformation_matrix.dot( numpy.dot( one_body_squares[l], numpy.transpose( numpy.conjugate(basis_transformation_matrix)))) diagonal = numpy.diag(hopefully_diagonal) difference = hopefully_diagonal - numpy.diag(diagonal) self.assertAlmostEqual(0., numpy.amax(numpy.absolute(difference))) density_density_alternative = numpy.outer(diagonal, diagonal) difference = density_density_alternative - density_density_matrix self.assertAlmostEqual(0., numpy.amax(numpy.absolute(difference))) # Test spectra. one_body_squared_spectrum = eigenspectrum(one_body_squared) two_body_spectrum = eigenspectrum(two_body_operator) difference = two_body_spectrum - one_body_squared_spectrum self.assertAlmostEqual(0., numpy.amax(numpy.absolute(difference)))
def test_operator_consistency(self): # Initialize a random two-body FermionOperator. n_qubits = 4 random_operator = get_fermion_operator( random_interaction_operator(n_qubits, seed=28644)) # Convert to chemist tensor. constant, one_body_coefficients, chemist_tensor = ( get_chemist_two_body_coefficients(random_operator)) # Build back operator constant and one-body components. decomposed_operator = FermionOperator((), constant) for p, q in itertools.product(range(n_qubits), repeat=2): term = ((p, 1), (q, 0)) coefficient = one_body_coefficients[p, q] decomposed_operator += FermionOperator(term, coefficient) # Perform decomposition. eigenvalues, one_body_squares, trunc_error = ( low_rank_two_body_decomposition(chemist_tensor)) self.assertFalse(trunc_error) # Check for exception. with self.assertRaises(ValueError): eigenvalues, one_body_squares, trunc_error = ( low_rank_two_body_decomposition(chemist_tensor, truncation_threshold=1., final_rank=1)) # Build back two-body component. for l in range(n_qubits**2): one_body_operator = FermionOperator() for p, q in itertools.product(range(n_qubits), repeat=2): term = ((p, 1), (q, 0)) coefficient = one_body_squares[l, p, q] one_body_operator += FermionOperator(term, coefficient) decomposed_operator += eigenvalues[l] * (one_body_operator**2) # Test for consistency. difference = normal_ordered(decomposed_operator - random_operator) self.assertAlmostEqual(0., difference.induced_norm())
def test_one_body_square_decomposition(self): # Initialize a random two-body FermionOperator. n_qubits = 4 random_operator = get_fermion_operator( random_interaction_operator(n_qubits, seed=17004)) # Convert to chemist tensor. constant, one_body_coefficients, chemist_tensor = ( get_chemist_two_body_coefficients(random_operator)) # Perform decomposition. eigenvalues, one_body_squares, trunc_error = ( low_rank_two_body_decomposition(chemist_tensor)) # Build back two-body component. for l in range(n_qubits**2): # Get the squared one-body operator. one_body_operator = FermionOperator() for p, q in itertools.product(range(n_qubits), repeat=2): term = ((p, 1), (q, 0)) coefficient = one_body_squares[l, p, q] one_body_operator += FermionOperator(term, coefficient) one_body_squared = one_body_operator**2 # Get the squared one-body operator via one-body decomposition. density_density_matrix, basis_transformation_matrix = ( prepare_one_body_squared_evolution(one_body_squares[l])) two_body_operator = FermionOperator() for p, q in itertools.product(range(n_qubits), repeat=2): term = ((p, 1), (p, 0), (q, 1), (q, 0)) coefficient = density_density_matrix[p, q] two_body_operator += FermionOperator(term, coefficient) # Confirm that the rotations diagonalize the one-body squares. hopefully_diagonal = basis_transformation_matrix.dot( numpy.dot( one_body_squares[l], numpy.transpose( numpy.conjugate(basis_transformation_matrix)))) diagonal = numpy.diag(hopefully_diagonal) difference = hopefully_diagonal - numpy.diag(diagonal) self.assertAlmostEqual(0., numpy.amax(numpy.absolute(difference))) density_density_alternative = numpy.outer(diagonal, diagonal) difference = density_density_alternative - density_density_matrix self.assertAlmostEqual(0., numpy.amax(numpy.absolute(difference))) # Test spectra. one_body_squared_spectrum = eigenspectrum(one_body_squared) two_body_spectrum = eigenspectrum(two_body_operator) difference = two_body_spectrum - one_body_squared_spectrum self.assertAlmostEqual(0., numpy.amax(numpy.absolute(difference)))
def test_random_operator_consistency(self): # Initialize a random InteractionOperator and FermionOperator. n_orbitals = 3 n_qubits = 2 * n_orbitals random_interaction = random_interaction_operator(n_orbitals, expand_spin=True, real=True, seed=8) random_fermion = get_fermion_operator(random_interaction) # Decompose. eigenvalues, one_body_squares, one_body_correction, error = ( low_rank_two_body_decomposition( random_interaction.two_body_tensor)) self.assertFalse(error) # Build back operator constant and one-body components. decomposed_operator = FermionOperator((), random_interaction.constant) one_body_coefficients = (random_interaction.one_body_tensor + one_body_correction) for p, q in itertools.product(range(n_qubits), repeat=2): term = ((p, 1), (q, 0)) coefficient = one_body_coefficients[p, q] decomposed_operator += FermionOperator(term, coefficient) # Build back two-body component. for l in range(n_orbitals**2): one_body_operator = FermionOperator() for p, q in itertools.product(range(n_qubits), repeat=2): term = ((p, 1), (q, 0)) coefficient = one_body_squares[l, p, q] one_body_operator += FermionOperator(term, coefficient) decomposed_operator += eigenvalues[l] * (one_body_operator**2) # Test for consistency. difference = normal_ordered(decomposed_operator - random_fermion) self.assertAlmostEqual(0., difference.induced_norm())
def test_rank_reduction(self): # Initialize H2. geometry = [('H', (0., 0., 0.)), ('H', (0., 0., 0.7414))] basis = 'sto-3g' multiplicity = 1 filename = os.path.join(THIS_DIRECTORY, 'data', 'H2_sto-3g_singlet_0.7414') molecule = MolecularData(geometry, basis, multiplicity, filename=filename) molecule.load() # Get molecular Hamiltonian. molecular_hamiltonian = molecule.get_molecular_hamiltonian() # Get fermion Hamiltonian. fermion_hamiltonian = normal_ordered( get_fermion_operator(molecular_hamiltonian)) # Get chemist tensor. constant, one_body_coefficients, chemist_tensor = ( get_chemist_two_body_coefficients(fermion_hamiltonian)) n_qubits = one_body_coefficients.shape[0] # Rank reduce with threshold. errors = [] for truncation_threshold in [1., 0.1, 0.01, 0.001]: # Add back one-body terms and constant. decomposed_operator = FermionOperator((), constant) for p, q in itertools.product(range(n_qubits), repeat=2): term = ((p, 1), (q, 0)) coefficient = one_body_coefficients[p, q] decomposed_operator += FermionOperator(term, coefficient) # Rank reduce. eigenvalues, one_body_squares, trunc_error = ( low_rank_two_body_decomposition( chemist_tensor, truncation_threshold=truncation_threshold)) # Make sure error is below truncation specification. self.assertTrue(trunc_error < truncation_threshold) # Reassemble FermionOperator. l_max = eigenvalues.size for l in range(l_max): one_body_operator = FermionOperator() for p, q in itertools.product(range(n_qubits), repeat=2): term = ((p, 1), (q, 0)) coefficient = one_body_squares[l, p, q] one_body_operator += FermionOperator(term, coefficient) decomposed_operator += eigenvalues[l] * (one_body_operator**2) # Test for consistency. difference = normal_ordered(decomposed_operator - fermion_hamiltonian) errors += [difference.induced_norm()] self.assertTrue(errors[-1] <= trunc_error or abs(errors[-1] - trunc_error) < 1e-6) self.assertTrue(errors[3] <= errors[2] <= errors[1] <= errors[0]) # Rank reduce by setting final rank. errors = [] for final_rank in [4, 6, 8, 10, 12]: # Add back one-body terms and constant. decomposed_operator = FermionOperator((), constant) for p, q in itertools.product(range(n_qubits), repeat=2): term = ((p, 1), (q, 0)) coefficient = one_body_coefficients[p, q] decomposed_operator += FermionOperator(term, coefficient) # Rank reduce. eigenvalues, one_body_squares, trunc_error = ( low_rank_two_body_decomposition(chemist_tensor, final_rank=final_rank)) # Reassemble FermionOperator. l_max = eigenvalues.size for l in range(l_max): one_body_operator = FermionOperator() for p, q in itertools.product(range(n_qubits), repeat=2): term = ((p, 1), (q, 0)) coefficient = one_body_squares[l, p, q] one_body_operator += FermionOperator(term, coefficient) decomposed_operator += eigenvalues[l] * (one_body_operator**2) # Test for consistency. difference = normal_ordered(decomposed_operator - fermion_hamiltonian) errors += [difference.induced_norm()] self.assertTrue(errors[3] <= errors[2] <= errors[1] <= errors[0])
def test_rank_reduction(self): # Initialize H2 InteractionOperator. n_qubits = 4 n_orbitals = 2 filename = os.path.join(THIS_DIRECTORY, 'data', 'H2_sto-3g_singlet_0.7414') molecule = MolecularData(filename=filename) molecule_interaction = molecule.get_molecular_hamiltonian() fermion_operator = get_fermion_operator(molecule_interaction) constant = molecule_interaction.constant two_body_coefficients = molecule_interaction.two_body_tensor # Rank reduce with threshold. errors = [] for truncation_threshold in [1., 0.1, 0.01, 0.001]: # Decompose with threshold. (test_eigenvalues, one_body_squares, one_body_correction, trunc_error) = low_rank_two_body_decomposition( two_body_coefficients, truncation_threshold=truncation_threshold) # Make sure error is below truncation specification. self.assertTrue(trunc_error > 0.) self.assertTrue(trunc_error < truncation_threshold) self.assertTrue(len(test_eigenvalues) < n_orbitals**2) self.assertTrue(len(one_body_squares) == len(test_eigenvalues)) # Build back operator constant and one-body components. decomposed_operator = FermionOperator((), constant) one_body_coefficients = (molecule_interaction.one_body_tensor + one_body_correction) for p, q in itertools.product(range(n_qubits), repeat=2): term = ((p, 1), (q, 0)) coefficient = one_body_coefficients[p, q] decomposed_operator += FermionOperator(term, coefficient) # Build back two-body component. for l in range(len(test_eigenvalues)): one_body_operator = FermionOperator() for p, q in itertools.product(range(n_qubits), repeat=2): term = ((p, 1), (q, 0)) coefficient = one_body_squares[l, p, q] one_body_operator += FermionOperator(term, coefficient) decomposed_operator += (test_eigenvalues[l] * one_body_operator**2) # Test for consistency. difference = normal_ordered(decomposed_operator - fermion_operator) errors += [difference.induced_norm()] self.assertTrue(errors[-1] <= trunc_error) self.assertTrue(errors[3] <= errors[2] <= errors[1] <= errors[0]) # Rank reduce by imposing final rank. errors = [] for final_rank in [1, 2, 3, 4]: # Decompose with threshold. (test_eigenvalues, one_body_squares, one_body_correction, trunc_error) = low_rank_two_body_decomposition( two_body_coefficients, final_rank=final_rank) # Make sure error is below truncation specification. self.assertTrue(len(test_eigenvalues) == final_rank) # Build back operator constant and one-body components. decomposed_operator = FermionOperator((), constant) one_body_coefficients = (molecule_interaction.one_body_tensor + one_body_correction) for p, q in itertools.product(range(n_qubits), repeat=2): term = ((p, 1), (q, 0)) coefficient = one_body_coefficients[p, q] decomposed_operator += FermionOperator(term, coefficient) # Build back two-body component. for l in range(final_rank): one_body_operator = FermionOperator() for p, q in itertools.product(range(n_qubits), repeat=2): term = ((p, 1), (q, 0)) coefficient = one_body_squares[l, p, q] one_body_operator += FermionOperator(term, coefficient) decomposed_operator += (test_eigenvalues[l] * one_body_operator**2) # Test for consistency. difference = normal_ordered(decomposed_operator - fermion_operator) errors += [difference.induced_norm()] self.assertTrue(errors[3] <= errors[2] <= errors[1] <= errors[0])