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 test_operator_consistency(self): # Initialize a random InteractionOperator and FermionOperator. n_qubits = 4 random_interaction = random_interaction_operator(n_qubits, real=False, seed=34281) random_fermion = get_fermion_operator(random_interaction) # Convert to chemist ordered tensor. io_constant, io_one_body_coefficients, io_chemist_tensor = \ get_chemist_two_body_coefficients(random_interaction) fo_constant, fo_one_body_coefficients, fo_chemist_tensor = \ get_chemist_two_body_coefficients(random_fermion) # Ensure consistency between FermionOperator and InteractionOperator. self.assertAlmostEqual(io_constant, fo_constant) one_body_difference = numpy.sum( numpy.absolute(io_one_body_coefficients - fo_one_body_coefficients)) self.assertAlmostEqual(0., one_body_difference) two_body_difference = numpy.sum( numpy.absolute(io_chemist_tensor - fo_chemist_tensor)) self.assertAlmostEqual(0., two_body_difference) # Convert output to FermionOperator. output_operator = FermionOperator() output_operator += FermionOperator((), fo_constant) # Convert one-body. for p, q in itertools.product(range(n_qubits), repeat=2): term = ((p, 1), (q, 0)) coefficient = fo_one_body_coefficients[p, q] output_operator += FermionOperator(term, coefficient) # Convert two-body. for p, q, r, s in itertools.product(range(n_qubits), repeat=4): term = ((p, 1), (q, 0), (r, 1), (s, 0)) coefficient = fo_chemist_tensor[p, q, r, s] output_operator += FermionOperator(term, coefficient) # Check that difference is small. difference = normal_ordered(random_fermion - output_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_exception(self): # Initialize a bad FermionOperator. n_qubits = 4 random_interaction = random_interaction_operator(n_qubits, seed=36229) random_fermion = get_fermion_operator(random_interaction) bad_term = ((1, 1), (2, 1)) random_fermion += FermionOperator(bad_term) # Check for exception. with self.assertRaises(TypeError): fo_constant, fo_one_body_coefficients, fo_chemist_tensor = ( get_chemist_two_body_coefficients(random_fermion))
def test_operator_consistency_w_spin(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=False, seed=8) random_fermion = get_fermion_operator(random_interaction) # Convert to chemist ordered tensor. one_body_correction, chemist_tensor = \ get_chemist_two_body_coefficients( random_interaction.two_body_tensor, spin_basis=True) # Convert output to FermionOperator. output_operator = FermionOperator((), random_interaction.constant) # Convert one-body. 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] output_operator += FermionOperator(term, coefficient) # Convert two-body. for p, q, r, s in itertools.product(range(n_orbitals), repeat=4): coefficient = chemist_tensor[p, q, r, s] # Mixed spin. term = ((2 * p, 1), (2 * q, 0), (2 * r + 1, 1), (2 * s + 1, 0)) output_operator += FermionOperator(term, coefficient) term = ((2 * p + 1, 1), (2 * q + 1, 0), (2 * r, 1), (2 * s, 0)) output_operator += FermionOperator(term, coefficient) # Same spin. term = ((2 * p, 1), (2 * q, 0), (2 * r, 1), (2 * s, 0)) output_operator += FermionOperator(term, coefficient) term = ((2 * p + 1, 1), (2 * q + 1, 0), (2 * r + 1, 1), (2 * s + 1, 0)) output_operator += FermionOperator(term, coefficient) # Check that difference is small. difference = normal_ordered(random_fermion - output_operator) self.assertAlmostEqual(0., difference.induced_norm())
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_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])
fermionic_hamiltonian = get_fermion_operator( molecule.get_molecular_hamiltonian()) # matrix for hamiltonian h = np.array([0, [], [[]], [[[]]], [[[[]]]]]) h[0] = fermionic_hamiltonian.terms[()] h[2] = np.zeros((nqubit, nqubit)) h[4] = np.zeros((nqubit, nqubit, nqubit, nqubit)) # input hamiltonian into h for i, j, k, l in itertools.product(range(nqubit), repeat=4): h[4][i][j][k][l] = fermionic_hamiltonian.terms.get( ((i, 1), (j, 1), (k, 0), (l, 0)), 0) # get chemist-ordered matrix (h2_correction unused) h2_correction, V = get_chemist_two_body_coefficients(h[4], spin_basis=True) # reshape into matrix Vmat = V.reshape(half_nqubit**2, half_nqubit**2) # SVD decomposition u1, lam, u2 = np.linalg.svd(Vmat) # Record the errors of SVD expansion in each k V_svd = np.tensordot(u1[:, 0], u2[0, :], 0) * lam[0] error_svd.append(np.linalg.norm(Vmat - V_svd)) for i in range(1, k_svd): V_svd += np.tensordot(u1[:, i], u2[i, :], 0) * lam[i] error_svd.append(np.linalg.norm(Vmat - V_svd)) # 第一添え字をxにするため