def test_eight_point_iter(self): constant = 100.0 one_body = numpy.zeros((self.n_qubits, self.n_qubits), float) two_body = numpy.zeros( (self.n_qubits, self.n_qubits, self.n_qubits, self.n_qubits), float) one_body[1, 1] = 10.0 one_body[2, 3] = 11.0 one_body[3, 2] = 11.0 two_body[1, 2, 3, 4] = 12.0 two_body[2, 1, 4, 3] = 12.0 two_body[3, 4, 1, 2] = 12.0 two_body[4, 3, 2, 1] = 12.0 two_body[1, 4, 3, 2] = 12.0 two_body[2, 3, 4, 1] = 12.0 two_body[3, 2, 1, 4] = 12.0 two_body[4, 1, 2, 3] = 12.0 interaction_operator = InteractionOperator(constant, one_body, two_body) want_str = '100.0\n10.0\n11.0\n12.0\n' got_str = '' for key in interaction_operator.unique_iter(): got_str += '{}\n'.format(interaction_operator[key]) self.assertEqual(want_str, got_str)
def test_neg(self): interaction_op = InteractionOperator(0, numpy.ones((3, 3)), numpy.ones((3, 3, 3, 3))) neg_interaction_op = -interaction_op assert isinstance(neg_interaction_op, InteractionOperator) assert neg_interaction_op == InteractionOperator( 0, -numpy.ones((3, 3)), -numpy.ones((3, 3, 3, 3)))
def test_get_interaction_operator_one_body(self): interaction_operator = get_interaction_operator( FermionOperator('2^ 2'), self.n_qubits) one_body = numpy.zeros((self.n_qubits, self.n_qubits), float) one_body[2, 2] = 1. self.assertEqual(interaction_operator, InteractionOperator(0.0, one_body, self.two_body))
def test_get_interaction_operator_two_body_distinct(self): interaction_operator = get_interaction_operator( FermionOperator('0^ 1^ 2 3'), self.n_qubits) two_body = numpy.zeros( (self.n_qubits, self.n_qubits, self.n_qubits, self.n_qubits), float) two_body[1, 0, 3, 2] = 1. self.assertEqual(interaction_operator, InteractionOperator(0.0, self.one_body, two_body))
def test_get_interaction_operator_two_body(self): interaction_operator = get_interaction_operator( FermionOperator('2^ 2 3^ 4'), self.n_qubits) two_body = numpy.zeros( (self.n_qubits, self.n_qubits, self.n_qubits, self.n_qubits), float) two_body[3, 2, 4, 2] = -1. self.assertEqual(interaction_operator, InteractionOperator(0.0, self.one_body, two_body))
def setUp(self): self.n_qubits = 5 self.constant = 0. self.one_body = numpy.zeros((self.n_qubits, self.n_qubits), float) self.two_body = numpy.zeros( (self.n_qubits, self.n_qubits, self.n_qubits, self.n_qubits), float) self.interaction_operator = InteractionOperator( self.constant, self.one_body, self.two_body)
def test_get_interaction_operator_one_body_twoterm(self): interaction_operator = get_interaction_operator( FermionOperator('2^ 3', -2j) + FermionOperator('3^ 2', 3j), self.n_qubits) one_body = numpy.zeros((self.n_qubits, self.n_qubits), complex) one_body[2, 3] = -2j one_body[3, 2] = 3j self.assertEqual(interaction_operator, InteractionOperator(0.0, one_body, self.two_body))
def test_get_interaction_operator_identity(self): interaction_operator = InteractionOperator(-2j, self.one_body, self.two_body) qubit_operator = jordan_wigner(interaction_operator) self.assertTrue(qubit_operator == -2j * QubitOperator(())) self.assertEqual( interaction_operator, get_interaction_operator(reverse_jordan_wigner(qubit_operator), self.n_qubits))
def test_save_interaction_operator_not_implemented(self): constant = 100.0 one_body = numpy.zeros((self.n_qubits, self.n_qubits), float) two_body = numpy.zeros( (self.n_qubits, self.n_qubits, self.n_qubits, self.n_qubits), float) one_body[1, 1] = 10.0 two_body[1, 2, 3, 4] = 12.0 interaction_operator = InteractionOperator(constant, one_body, two_body) with self.assertRaises(NotImplementedError): save_operator(interaction_operator, self.file_name)
def test_addition(self): interaction_op = InteractionOperator(0, numpy.ones((3, 3)), numpy.ones((3, 3, 3, 3))) summed_op = interaction_op + interaction_op self.assertTrue( numpy.array_equal(summed_op.one_body_tensor, summed_op.n_body_tensors[(1, 0)])) self.assertTrue( numpy.array_equal(summed_op.two_body_tensor, summed_op.n_body_tensors[(1, 1, 0, 0)]))
def test_bravyi_kitaev_fast_number_excitation_operator(self): # using hydrogen Hamiltonian and introducing some number operator terms constant = 0 one_body = numpy.zeros((4, 4)) one_body[(0, 0)] = .4 one_body[(1, 1)] = .5 one_body[(2, 2)] = .6 one_body[(3, 3)] = .7 two_body = self.molecular_hamiltonian.two_body_tensor # initiating number operator terms for all the possible cases two_body[(1, 2, 3, 1)] = 0.1 two_body[(1, 3, 2, 1)] = 0.1 two_body[(1, 2, 1, 3)] = 0.15 two_body[(3, 1, 2, 1)] = 0.15 two_body[(0, 2, 2, 1)] = 0.09 two_body[(1, 2, 2, 0)] = 0.09 two_body[(1, 2, 3, 2)] = 0.11 two_body[(2, 3, 2, 1)] = 0.11 two_body[(2, 2, 2, 2)] = 0.1 molecular_hamiltonian = InteractionOperator(constant, one_body, two_body) # comparing the eigenspectrum of Hamiltonian n_qubits = count_qubits(molecular_hamiltonian) bravyi_kitaev_fast_H = bksf.bravyi_kitaev_fast(molecular_hamiltonian) jw_H = jordan_wigner(molecular_hamiltonian) bravyi_kitaev_fast_H_eig = eigenspectrum(bravyi_kitaev_fast_H) jw_H_eig = eigenspectrum(jw_H) bravyi_kitaev_fast_H_eig = bravyi_kitaev_fast_H_eig.round(5) jw_H_eig = jw_H_eig.round(5) evensector_H = 0 for i in range(numpy.size(jw_H_eig)): if bool( numpy.size( numpy.where(jw_H_eig[i] == bravyi_kitaev_fast_H_eig))): evensector_H += 1 # comparing eigenspectrum of number operator bravyi_kitaev_fast_n = bksf.number_operator(molecular_hamiltonian) jw_n = QubitOperator() n_qubits = count_qubits(molecular_hamiltonian) for i in range(n_qubits): jw_n += jordan_wigner_one_body(i, i) jw_eig_spec = eigenspectrum(jw_n) bravyi_kitaev_fast_eig_spec = eigenspectrum(bravyi_kitaev_fast_n) evensector_n = 0 for i in range(numpy.size(jw_eig_spec)): if bool( numpy.size( numpy.where( jw_eig_spec[i] == bravyi_kitaev_fast_eig_spec))): evensector_n += 1 self.assertEqual(evensector_H, 2**(n_qubits - 1)) self.assertEqual(evensector_n, 2**(n_qubits - 1))
def generate_hamiltonian( one_body_integrals: np.ndarray, two_body_integrals: np.ndarray, constant: float, EQ_TOLERANCE: Optional[float] = 1.0E-12) -> InteractionOperator: n_qubits = 2 * one_body_integrals.shape[0] # Initialize Hamiltonian coefficients. one_body_coefficients = np.zeros((n_qubits, n_qubits)) two_body_coefficients = np.zeros((n_qubits, n_qubits, n_qubits, n_qubits)) # Loop through integrals. for p in range(n_qubits // 2): for q in range(n_qubits // 2): # Populate 1-body coefficients. Require p and q have same spin. one_body_coefficients[2 * p, 2 * q] = one_body_integrals[p, q] one_body_coefficients[2 * p + 1, 2 * q + 1] = one_body_integrals[p, q] # Continue looping to prepare 2-body coefficients. for r in range(n_qubits // 2): for s in range(n_qubits // 2): # Mixed spin two_body_coefficients[2 * p, 2 * q + 1, 2 * r + 1, 2 * s] = ( two_body_integrals[p, q, r, s] / 2.) two_body_coefficients[2 * p + 1, 2 * q, 2 * r, 2 * s + 1] = ( two_body_integrals[p, q, r, s] / 2.) # Same spin two_body_coefficients[2 * p, 2 * q, 2 * r, 2 * s] = ( two_body_integrals[p, q, r, s] / 2.) two_body_coefficients[2 * p + 1, 2 * q + 1, 2 * r + 1, 2 * s + 1] = ( two_body_integrals[p, q, r, s] / 2.) # Truncate. one_body_coefficients[ np.absolute(one_body_coefficients) < EQ_TOLERANCE] = 0. two_body_coefficients[ np.absolute(two_body_coefficients) < EQ_TOLERANCE] = 0. # Cast to InteractionOperator class and return. molecular_hamiltonian = InteractionOperator(constant, one_body_coefficients, two_body_coefficients) return molecular_hamiltonian
def make_reduced_hamiltonian(molecular_hamiltonian: InteractionOperator, n_electrons: int) -> InteractionOperator: r""" Construct the reduced Hamiltonian. This Hamiltonian is equivalent to the electronic structure Hamiltonian but contains only two-body terms. To do this, the operator now depends on the number of particles being simulated. We use the RDM sum rule to lift the 1-body terms to the two-body space. Derivation: use the fact that i^l = (1/(n -1)) sum_{jk}\delta_{jk}i^ j^ k l i^l = (-1/(n -1)) sum_{jk}\delta_{jk}j^ i^ k l i^l = (-1/(n -1)) sum_{jk}\delta_{jk}i^ j^ l k i^l = (1/(n -1)) sum_{jk}\delta_{jk}j^ i^ l k Rewrite each one-body term as an even weighting of all four 2-RDM elements with delta functions. Then rearrange terms so that each ijkl term gets a sum of permuted one-body terms multiplied by delta function. One should notice that this results in the same formula if one was to apply the wedge product! Args: molecular_hamiltonian: operator to write reduced hamiltonian for n_electrons: number of electrons in the system Returns: InteractionOperator with a zero one-body component. """ constant = molecular_hamiltonian.constant h1 = molecular_hamiltonian.one_body_tensor h2 = molecular_hamiltonian.two_body_tensor delta = numpy.eye(h1.shape[0]) k2 = numpy.zeros_like(h2) normalization = 1 / (4 * (n_electrons - 1)) for i, j, k, l in product(range(h1.shape[0]), repeat=4): k2[i, j, k, l] = normalization * ( h1[i, l] * delta[j, k] + h1[j, k] * delta[i, l] - h1[i, k] * delta[j, l] - h1[j, l] * delta[i, k]) + h2[i, j, k, l] return InteractionOperator(constant, numpy.zeros_like(h1), k2)
def test_bravyi_kitaev_fast_excitation_terms(self): # Testing on-site and excitation terms in Hamiltonian constant = 0 one_body = numpy.array([[1, 2, 0, 3], [2, 1, 2, 0], [0, 2, 1, 2.5], [3, 0, 2.5, 1]]) # No Coloumb interaction two_body = numpy.zeros((4, 4, 4, 4)) molecular_hamiltonian = InteractionOperator(constant, one_body, two_body) n_qubits = count_qubits(molecular_hamiltonian) # comparing the eigenspectrum of Hamiltonian bravyi_kitaev_fast_H = bksf.bravyi_kitaev_fast(molecular_hamiltonian) jw_H = jordan_wigner(molecular_hamiltonian) bravyi_kitaev_fast_H_eig = eigenspectrum(bravyi_kitaev_fast_H) jw_H_eig = eigenspectrum(jw_H) bravyi_kitaev_fast_H_eig = bravyi_kitaev_fast_H_eig.round(5) jw_H_eig = jw_H_eig.round(5) evensector_H = 0 for i in range(numpy.size(jw_H_eig)): if bool( numpy.size( numpy.where(jw_H_eig[i] == bravyi_kitaev_fast_H_eig))): evensector_H += 1 # comparing eigenspectrum of number operator bravyi_kitaev_fast_n = bksf.number_operator(molecular_hamiltonian) jw_n = QubitOperator() n_qubits = count_qubits(molecular_hamiltonian) for i in range(n_qubits): jw_n += jordan_wigner_one_body(i, i) jw_eig_spec = eigenspectrum(jw_n) bravyi_kitaev_fast_eig_spec = eigenspectrum(bravyi_kitaev_fast_n) evensector_n = 0 for i in range(numpy.size(jw_eig_spec)): if bool( numpy.size( numpy.where( jw_eig_spec[i] == bravyi_kitaev_fast_eig_spec))): evensector_n += 1 self.assertEqual(evensector_H, 2**(n_qubits - 1)) self.assertEqual(evensector_n, 2**(n_qubits - 1))
def random_interaction_operator(n_orbitals, expand_spin=False, real=True, seed=None): """Generate a random instance of InteractionOperator. Args: n_orbitals: The number of orbitals. expand_spin: Whether to expand each orbital symmetrically into two spin orbitals. Note that if this option is set to True, then the total number of orbitals will be doubled. real: Whether to use only real numbers. seed: A random number generator seed. """ if seed is not None: numpy.random.seed(seed) if real: dtype = float else: dtype = complex # The constant has to be real. constant = numpy.random.randn() # The one-body tensor is a random Hermitian matrix. one_body_coefficients = random_hermitian_matrix(n_orbitals, real) # Generate random two-body coefficients. two_body_coefficients = numpy.zeros( (n_orbitals, n_orbitals, n_orbitals, n_orbitals), dtype) for p, q, r, s in itertools.product(range(n_orbitals), repeat=4): coeff = numpy.random.randn() if not real and len(set([p, q, r, s])) >= 3: coeff += 1.j * numpy.random.randn() # Four point symmetry. two_body_coefficients[p, q, r, s] = coeff two_body_coefficients[q, p, s, r] = coeff two_body_coefficients[s, r, q, p] = coeff.conjugate() two_body_coefficients[r, s, p, q] = coeff.conjugate() # Eight point symmetry. if real: two_body_coefficients[r, q, p, s] = coeff two_body_coefficients[p, s, r, q] = coeff two_body_coefficients[s, p, q, r] = coeff two_body_coefficients[q, r, s, p] = coeff # If requested, expand to spin orbitals. if expand_spin: n_spin_orbitals = 2 * n_orbitals # Expand one-body tensor. one_body_coefficients = numpy.kron(one_body_coefficients, numpy.eye(2)) # Expand two-body tensor. new_two_body_coefficients = numpy.zeros( (n_spin_orbitals, n_spin_orbitals, n_spin_orbitals, n_spin_orbitals), dtype=complex) for p, q, r, s in itertools.product(range(n_orbitals), repeat=4): coefficient = two_body_coefficients[p, q, r, s] # Mixed spin. new_two_body_coefficients[2 * p, 2 * q + 1, 2 * r + 1, 2 * s] = (coefficient) new_two_body_coefficients[2 * p + 1, 2 * q, 2 * r, 2 * s + 1] = (coefficient) # Same spin. new_two_body_coefficients[2 * p, 2 * q, 2 * r, 2 * s] = coefficient new_two_body_coefficients[2 * p + 1, 2 * q + 1, 2 * r + 1, 2 * s + 1] = coefficient two_body_coefficients = new_two_body_coefficients # Create the InteractionOperator. interaction_operator = InteractionOperator(constant, one_body_coefficients, two_body_coefficients) return interaction_operator
def normal_ordered(operator, hbar=1.): r"""Compute and return the normal ordered form of a FermionOperator, BosonOperator, QuadOperator, or InteractionOperator. Due to the canonical commutation/anticommutation relations satisfied by these operators, there are multiple forms that the same operator can take. Here, we define the normal ordered form of each operator, providing a distinct representation for distinct operators. In our convention, normal ordering implies terms are ordered from highest tensor factor (on left) to lowest (on right). In addition: * FermionOperators: a^\dagger comes before a * BosonOperators: b^\dagger comes before b * QuadOperators: q operators come before p operators, Args: operator: an instance of the FermionOperator, BosonOperator, QuadOperator, or InteractionOperator classes. hbar (float): the value of hbar used in the definition of the commutator [q_i, p_j] = i hbar delta_ij. By default hbar=1. This argument only applies when normal ordering QuadOperators. """ kwargs = {} if isinstance(operator, FermionOperator): ordered_operator = FermionOperator() order_fn = normal_ordered_ladder_term kwargs['parity'] = -1 elif isinstance(operator, BosonOperator): ordered_operator = BosonOperator() order_fn = normal_ordered_ladder_term kwargs['parity'] = 1 elif isinstance(operator, QuadOperator): ordered_operator = QuadOperator() order_fn = normal_ordered_quad_term kwargs['hbar'] = hbar elif isinstance(operator, InteractionOperator): constant = operator.constant n_modes = operator.n_qubits one_body_tensor = operator.one_body_tensor.copy() two_body_tensor = numpy.zeros_like(operator.two_body_tensor) quadratic_index_pairs = ( (pq, pq) for pq in itertools.combinations(range(n_modes)[::-1], 2)) cubic_index_pairs = ( index_pair for p, q, r in itertools.combinations(range(n_modes)[::-1], 3) for index_pair in [((p, q), (p, r)), ((p, r), ( p, q)), ((p, q), (q, r)), ((q, r), (p, q)), ((p, r), (q, r)), ((q, r), (p, r))]) quartic_index_pairs = ( index_pair for p, q, r, s in itertools.combinations(range(n_modes)[::-1], 4) for index_pair in [((p, q), (r, s)), ((r, s), ( p, q)), ((p, r), (q, s)), ((q, s), (p, r)), ((p, s), (q, r)), ((q, r), (p, s))]) index_pairs = itertools.chain(quadratic_index_pairs, cubic_index_pairs, quartic_index_pairs) for pq, rs in index_pairs: two_body_tensor[pq + rs] = sum( s * ss * operator.two_body_tensor[pq[::s] + rs[::ss]] for s, ss in itertools.product([-1, 1], repeat=2)) return InteractionOperator(constant, one_body_tensor, two_body_tensor) else: raise TypeError('Can only normal order FermionOperator, ' 'BosonOperator, QuadOperator, or InteractionOperator.') for term, coefficient in operator.terms.items(): ordered_operator += order_fn(term, coefficient, **kwargs) return ordered_operator
def test_projected(self): interaction_op = InteractionOperator(1, numpy.ones((3, 3)), numpy.ones((3, 3, 3, 3))) projected_two_body_tensor = numpy.zeros_like( interaction_op.two_body_tensor) for i in range(3): projected_two_body_tensor[(i, ) * 4] = 1 projected_op = InteractionOperator(0, numpy.eye(3), projected_two_body_tensor) assert projected_op == interaction_op.projected(1, exact=True) projected_op.constant = 1 assert projected_op == interaction_op.projected(1, exact=False) projected_op.one_body_tensor = numpy.zeros_like( interaction_op.one_body_tensor) for pq in itertools.product(range(2), repeat=2): projected_op.one_body_tensor[pq] = 1 projected_op.two_body_tensor = numpy.zeros_like( interaction_op.two_body_tensor) for pqrs in itertools.product(range(2), repeat=4): projected_op.two_body_tensor[pqrs] = 1 assert projected_op == interaction_op.projected((0, 1), exact=False) projected_op.constant = 0 projected_op.one_body_tensor = numpy.zeros_like( interaction_op.one_body_tensor) projected_op.one_body_tensor[0, 1] = 1 projected_op.one_body_tensor[1, 0] = 1 projected_op.two_body_tensor = numpy.zeros_like( interaction_op.two_body_tensor) for pqrs in itertools.product(range(2), repeat=4): if len(set(pqrs)) > 1: projected_op.two_body_tensor[pqrs] = 1 assert projected_op == interaction_op.projected((0, 1), exact=True)
def test_zero(self): interaction_op = InteractionOperator(0, numpy.ones((3, 3)), numpy.ones((3, 3, 3, 3))) assert InteractionOperator.zero( interaction_op.n_qubits) == interaction_op * 0
def get_interaction_operator(fermion_operator, n_qubits=None): r"""Convert a 2-body fermionic operator to InteractionOperator. This function should only be called on fermionic operators which consist of only a_p^\dagger a_q and a_p^\dagger a_q^\dagger a_r a_s terms. The one-body terms are stored in a matrix, one_body[p, q], and the two-body terms are stored in a tensor, two_body[p, q, r, s]. Returns: interaction_operator: An instance of the InteractionOperator class. Raises: TypeError: Input must be a FermionOperator. TypeError: FermionOperator does not map to InteractionOperator. Warning: Even assuming that each creation or annihilation operator appears at most a constant number of times in the original operator, the runtime of this method is exponential in the number of qubits. """ if not isinstance(fermion_operator, FermionOperator): raise TypeError('Input must be a FermionOperator.') check_no_sympy(fermion_operator) if n_qubits is None: n_qubits = op_utils.count_qubits(fermion_operator) if n_qubits < op_utils.count_qubits(fermion_operator): raise ValueError('Invalid number of qubits specified.') # Normal order the terms and initialize. fermion_operator = normal_ordered(fermion_operator) constant = 0. one_body = numpy.zeros((n_qubits, n_qubits), complex) two_body = numpy.zeros((n_qubits, n_qubits, n_qubits, n_qubits), complex) # Loop through terms and assign to matrix. for term in fermion_operator.terms: coefficient = fermion_operator.terms[term] # Ignore this term if the coefficient is zero if abs(coefficient) < EQ_TOLERANCE: # not testable because normal_ordered kills # fermion terms lower than EQ_TOLERANCE continue # pragma: no cover # Handle constant shift. if len(term) == 0: constant = coefficient elif len(term) == 2: # Handle one-body terms. if [operator[1] for operator in term] == [1, 0]: p, q = [operator[0] for operator in term] one_body[p, q] = coefficient else: raise InteractionOperatorError('FermionOperator does not map ' 'to InteractionOperator.') elif len(term) == 4: # Handle two-body terms. if [operator[1] for operator in term] == [1, 1, 0, 0]: p, q, r, s = [operator[0] for operator in term] two_body[p, q, r, s] = coefficient else: raise InteractionOperatorError('FermionOperator does not map ' 'to InteractionOperator.') else: # Handle non-molecular Hamiltonian. raise InteractionOperatorError('FermionOperator does not map ' 'to InteractionOperator.') # Form InteractionOperator and return. interaction_operator = InteractionOperator(constant, one_body, two_body) return interaction_operator