def test_integration_jellium_hamiltonian_with_negation(self): hamiltonian = normal_ordered( jellium_model(Grid(2, 3, 1.), plane_wave=False)) part_a = FermionOperator.zero() part_b = FermionOperator.zero() add_to_a_or_b = 0 # add to a if 0; add to b if 1 for term, coeff in hamiltonian.terms.items(): # Partition terms in the Hamiltonian into part_a or part_b if add_to_a_or_b: part_a += FermionOperator(term, coeff) else: part_b += FermionOperator(term, coeff) add_to_a_or_b ^= 1 reference = normal_ordered(commutator(part_a, part_b)) result = commutator_ordered_diagonal_coulomb_with_two_body_operator( part_a, part_b) self.assertTrue(result.isclose(reference)) negative = commutator_ordered_diagonal_coulomb_with_two_body_operator( part_b, part_a) result += negative self.assertTrue(result.isclose(FermionOperator.zero()))
def two_body_sz_adapted(self): """ Doubles generators each with distinct Sz expectation value. G^{isigma, jtau, ktau, lsigma) for sigma, tau in 0, 1 """ for i, j, k, l in product(range(self.norbs), repeat=4): if i < j and k < l: op_aa = ((2 * i, 1), (2 * j, 1), (2 * k, 0), (2 * l, 0)) op_bb = ((2 * i + 1, 1), (2 * j + 1, 1), (2 * k + 1, 0), (2 * l + 1, 0)) fop_aa = of.FermionOperator(op_aa) fop_aa = fop_aa - of.hermitian_conjugated(fop_aa) fop_bb = of.FermionOperator(op_bb) fop_bb = fop_bb - of.hermitian_conjugated(fop_bb) fop_aa = of.normal_ordered(fop_aa) fop_bb = of.normal_ordered(fop_bb) self.op_pool.append(fop_aa) self.op_pool.append(fop_bb) op_ab = ((2 * i, 1), (2 * j + 1, 1), (2 * k + 1, 0), (2 * l, 0)) fop_ab = of.FermionOperator(op_ab) fop_ab = fop_ab - of.hermitian_conjugated(fop_ab) fop_ab = of.normal_ordered(fop_ab) if not np.isclose(fop_ab.induced_norm(), 0): self.op_pool.append(fop_ab)
def test_op_pool(): op = OperatorPool(2, [0], [1]) op.singlet_t2() true_generator = of.FermionOperator(((1, 1), (0, 1), (3, 0), (2, 0))) - \ of.FermionOperator(((3, 1), (2, 1), (1, 0), (0, 0))) assert np.isclose( of.normal_ordered(op.op_pool[0] - true_generator).induced_norm(), 0) op = OperatorPool(2, [0], [1]) op.two_body_sz_adapted() assert len(op.op_pool) == 20 true_generator0 = of.FermionOperator('1^ 0^ 2 1') - \ of.FermionOperator('2^ 1^ 1 0') assert np.isclose( of.normal_ordered(op.op_pool[0] - true_generator0).induced_norm(), 0) true_generator_end = of.FermionOperator('3^ 0^ 3 2') - \ of.FermionOperator('3^ 2^ 3 0') assert np.isclose( of.normal_ordered(op.op_pool[-1] - true_generator_end).induced_norm(), 0) op = OperatorPool(2, [0], [1]) op.one_body_sz_adapted() for gen in op.op_pool: for ladder_idx, _ in gen.terms.items(): # check if one body terms are generated assert len(ladder_idx) == 2
def benchmark_fermion_math_and_normal_order(n_qubits, term_length, power): """Benchmark both arithmetic operators and normal ordering on fermions. The idea is we generate two random FermionTerms, A and B, each acting on n_qubits with term_length operators. We then compute (A + B) ** power. This is costly that is the first benchmark. The second benchmark is in normal ordering whatever comes out. Args: n_qubits: The number of qubits on which these terms act. term_length: The number of operators in each term. power: Int, the exponent to which to raise sum of the two terms. Returns: runtime_math: The time it takes to perform (A + B) ** power runtime_normal_order: The time it takes to perform FermionOperator.normal_order() """ # Generate random operator strings. operators_a = [(numpy.random.randint(n_qubits), numpy.random.randint(2))] operators_b = [(numpy.random.randint(n_qubits), numpy.random.randint(2))] for _ in range(term_length): # Make sure the operator is not trivially zero. operator_a = (numpy.random.randint(n_qubits), numpy.random.randint(2)) while operator_a == operators_a[-1]: operator_a = (numpy.random.randint(n_qubits), numpy.random.randint(2)) operators_a += [operator_a] # Do the same for the other operator. operator_b = (numpy.random.randint(n_qubits), numpy.random.randint(2)) while operator_b == operators_b[-1]: operator_b = (numpy.random.randint(n_qubits), numpy.random.randint(2)) operators_b += [operator_b] # Initialize FermionTerms and then sum them together. fermion_term_a = FermionOperator(tuple(operators_a), float(numpy.random.randn())) fermion_term_b = FermionOperator(tuple(operators_b), float(numpy.random.randn())) fermion_operator = fermion_term_a + fermion_term_b # Exponentiate. start_time = time.time() fermion_operator **= power runtime_math = time.time() - start_time # Normal order. start_time = time.time() normal_ordered(fermion_operator) runtime_normal_order = time.time() - start_time # Return. return runtime_math, runtime_normal_order
def test_interaction_operator_interconversion(n_modes, seed): operator = openfermion.random_interaction_operator(n_modes, real=False, seed=seed) gates = ofc.fermionic_simulation_gates_from_interaction_operator(operator) other_operator = sum_of_interaction_operator_gate_generators( n_modes, gates) operator = openfermion.normal_ordered(operator) other_operator = openfermion.normal_ordered(other_operator) assert operator == other_operator
def test_integration_random_diagonal_coulomb_hamiltonian(self): hamiltonian1 = normal_ordered( get_fermion_operator( random_diagonal_coulomb_hamiltonian(n_qubits=7))) hamiltonian2 = normal_ordered( get_fermion_operator( random_diagonal_coulomb_hamiltonian(n_qubits=7))) reference = normal_ordered(commutator(hamiltonian1, hamiltonian2)) result = commutator_ordered_diagonal_coulomb_with_two_body_operator( hamiltonian1, hamiltonian2) self.assertTrue(result.isclose(reference))
def get_one_qubit_hydrogen_hamiltonian( interaction_operator: Union[InteractionOperator, str] ): """Generate a one qubit H2 hamiltonian from a corresponding interaction operator. Original H2 hamiltonian will be reduced to a 2 x 2 matrix defined on a subspace spanned by |0011> and |1100> and expanded in terms of I, X, Y, and Z matrices Args: interaction_operator: The input interaction operator """ if isinstance(interaction_operator, str): interaction_operator = load_interaction_operator(interaction_operator) fermion_h = get_fermion_operator(interaction_operator) # H00 H00 = normal_ordered(FermionOperator("0 1") * fermion_h * FermionOperator("1^ 0^")) H00 = H00.terms[()] # H11 H11 = normal_ordered(FermionOperator("2 3") * fermion_h * FermionOperator("3^ 2^")) H11 = H11.terms[()] # H10 H10 = normal_ordered(FermionOperator("2 3") * fermion_h * FermionOperator("1^ 0^")) H10 = H10.terms[()] # H01 H01 = np.conj(H10) one_qubit_h_matrix = np.array([[H00, H01], [H10, H11]]) pauli_x = np.array([[0.0, 1.0], [1.0, 0.0]]) pauli_y = np.array([[0.0, -1.0j], [1.0j, 0.0]]) pauli_z = np.array([[1.0, 0.0], [0.0, -1.0]]) r_id = 0.5 * np.trace(one_qubit_h_matrix) r_x = 0.5 * np.trace(one_qubit_h_matrix @ pauli_x) r_y = 0.5 * np.trace(one_qubit_h_matrix @ pauli_y) r_z = 0.5 * np.trace(one_qubit_h_matrix @ pauli_z) one_qubit_h = ( r_id * QubitOperator("") + r_x * QubitOperator("X0") + r_y * QubitOperator("Y0") + r_z * QubitOperator("Z0") ) save_qubit_operator(one_qubit_h, "qubit-operator.json")
def one_body_sz_adapted(self): # alpha-alpha rotation # beta-beta rotation for i, j in product(range(self.norbs), repeat=2): if i > j: op_aa = ((2 * i, 1), (2 * j, 0)) op_bb = ((2 * i + 1, 1), (2 * j + 1, 0)) fop_aa = of.FermionOperator(op_aa) fop_aa = fop_aa - of.hermitian_conjugated(fop_aa) fop_bb = of.FermionOperator(op_bb) fop_bb = fop_bb - of.hermitian_conjugated(fop_bb) fop_aa = of.normal_ordered(fop_aa) fop_bb = of.normal_ordered(fop_bb) self.op_pool.append(fop_aa) self.op_pool.append(fop_bb)
def test_erpa_eom_ham_h2(): filename = os.path.join(DATA_DIRECTORY, "H2_sto-3g_singlet_0.7414.hdf5") molecule = MolecularData(filename=filename) reduced_ham = make_reduced_hamiltonian( molecule.get_molecular_hamiltonian(), molecule.n_electrons) rha_fermion = of.get_fermion_operator(reduced_ham) permuted_hijkl = np.einsum('ijlk', reduced_ham.two_body_tensor) opdm = np.diag([1] * molecule.n_electrons + [0] * (molecule.n_qubits - molecule.n_electrons)) tpdm = 2 * of.wedge(opdm, opdm, (1, 1), (1, 1)) rdms = of.InteractionRDM(opdm, tpdm) dim = reduced_ham.one_body_tensor.shape[0] // 2 full_basis = {} # erpa basis. A, B basis in RPA language cnt = 0 for p, q in product(range(dim), repeat=2): if p < q: full_basis[(p, q)] = cnt full_basis[(q, p)] = cnt + dim * (dim - 1) // 2 cnt += 1 for rkey in full_basis.keys(): p, q = rkey for ckey in full_basis.keys(): r, s = ckey for sigma, tau in product([0, 1], repeat=2): test = erpa_eom_hamiltonian(permuted_hijkl, tpdm, 2 * q + sigma, 2 * p + sigma, 2 * r + tau, 2 * s + tau).real qp_op = of.FermionOperator( ((2 * q + sigma, 1), (2 * p + sigma, 0))) rs_op = of.FermionOperator( ((2 * r + tau, 1), (2 * s + tau, 0))) erpa_op = of.normal_ordered( of.commutator(qp_op, of.commutator(rha_fermion, rs_op))) true = rdms.expectation(of.get_interaction_operator(erpa_op)) assert np.isclose(true, test)
def test_commutator(self): operator_a = ( FermionOperator('0^ 0', 0.3) + FermionOperator('1^ 1', 0.1j) + FermionOperator('1^ 0^ 1 0', -0.2) + FermionOperator('1^ 3') + FermionOperator('3^ 0') + FermionOperator('3^ 2', 0.017) - FermionOperator('2^ 3', 1.99) + FermionOperator('3^ 1^ 3 1', .09) + FermionOperator('2^ 0^ 2 0', .126j) + FermionOperator('4^ 2^ 4 2') + FermionOperator('3^ 0^ 3 0')) operator_b = ( FermionOperator('3^ 1', 0.7) + FermionOperator('1^ 3', -9.) + FermionOperator('1^ 0^ 3 0', 0.1) - FermionOperator('3^ 0^ 1 0', 0.11) + FermionOperator('3^ 2^ 3 2') + FermionOperator('3^ 1^ 3 1', -1.37) + FermionOperator('4^ 2^ 4 2') + FermionOperator('4^ 1^ 4 1') + FermionOperator('1^ 0^ 4 0', 16.7) + FermionOperator('1^ 0^ 4 3', 1.67) + FermionOperator('4^ 3^ 5 2', 1.789j) + FermionOperator('6^ 5^ 4 1', -11.789j)) reference = normal_ordered(commutator(operator_a, operator_b)) result = commutator_ordered_diagonal_coulomb_with_two_body_operator( operator_a, operator_b) diff = result - reference self.assertTrue(diff.isclose(FermionOperator.zero()))
def singlet_t2(self): """ Generate singlet rotations T_{ij}^{ab} = T^(v1a, v2b)_{o1a, o2b} + T^(v1b, v2a)_{o1b, o2a} + T^(v1a, v2a)_{o1a, o2a} + T^(v1b, v2b)_{o1b, o2b} where v1,v2 are indices of the virtual obritals and o1, o2 are indices of the occupied orbitals with respect to the Hartree-Fock reference. """ for oo_i in self.occ: for oo_j in self.occ: for vv_a in self.virt: for vv_b in self.virt: term = of.FermionOperator() for sigma, tau in product(range(2), repeat=2): op = ((2 * vv_a + sigma, 1), (2 * vv_b + tau, 1), (2 * oo_j + tau, 0), (2 * oo_i + sigma, 0)) if (2 * vv_a + sigma == 2 * vv_b + tau or 2 * oo_j + tau == 2 * oo_i + sigma): continue fop = of.FermionOperator(op, coefficient=0.5) fop = fop - of.hermitian_conjugated(fop) fop = of.normal_ordered(fop) term += fop self.op_pool.append(term)
def get_one_qubit_hydrogen_hamiltonian( interaction_operator: Union[InteractionOperator, str]): """Generate a one qubit H2 hamiltonian from a corresponding interaction operator. Original H2 hamiltonian will be reduced to a 2 x 2 matrix defined on a subspace spanned by |0011> and |1100> and expanded in terms of I, X, Y, and Z matrices ARGS: interaction_operator (Union[InteractionOperator, str]): The input interaction operator """ if isinstance(interaction_operator, str): interaction_operator = load_interaction_operator(interaction_operator) fermion_h = get_fermion_operator(interaction_operator) # H00 H00 = normal_ordered( FermionOperator('0 1') * fermion_h * FermionOperator('1^ 0^')) H00 = H00.terms[()] # H11 H11 = normal_ordered( FermionOperator('2 3') * fermion_h * FermionOperator('3^ 2^')) H11 = H11.terms[()] # H10 H10 = normal_ordered( FermionOperator('2 3') * fermion_h * FermionOperator('1^ 0^')) H10 = H10.terms[()] # H01 H01 = np.conj(H10) one_qubit_h_matrix = np.array([[H00, H01], [H10, H11]]) pauli_x = np.array([[0., 1.], [1., 0.]]) pauli_y = np.array([[0., -1.j], [1.j, 0.]]) pauli_z = np.array([[1., 0.], [0., -1.]]) r_id = 0.5 * np.trace(one_qubit_h_matrix) r_x = 0.5 * np.trace(one_qubit_h_matrix @ pauli_x) r_y = 0.5 * np.trace(one_qubit_h_matrix @ pauli_y) r_z = 0.5 * np.trace(one_qubit_h_matrix @ pauli_z) one_qubit_h = r_id * QubitOperator('') + r_x * QubitOperator( 'X0') + r_y * QubitOperator('Y0') + r_z * QubitOperator('Z0') save_qubit_operator(one_qubit_h, 'qubit-operator.json')
def to_cirq_ncr(wfn: 'wavefunction.Wavefunction') -> numpy.ndarray: nqubit = wfn.norb() * 2 ops = normal_ordered(fqe_to_fermion_operator(wfn)) wf = numpy.zeros(2**nqubit, dtype=numpy.complex128) for term, coeff in ops.terms.items(): occ_idx = sum([2**(nqubit - oo[0] - 1) for oo in term]) wf[occ_idx] = coeff return wf
def assert_interaction_operator_consistent(gate): interaction_op = gate.interaction_operator_generator() other_gate = gate.from_interaction_operator(operator=interaction_op) if other_gate is None: assert np.allclose(gate.weights, 0) else: assert cirq.approx_eq(gate, other_gate) interaction_op = openfermion.normal_ordered(interaction_op) other_interaction_op = openfermion.InteractionOperator.zero( interaction_op.n_qubits) super(type(gate), gate).interaction_operator_generator(operator=other_interaction_op) other_interaction_op = openfermion.normal_ordered(interaction_op) assert interaction_op == other_interaction_op other_interaction_op = super(type(gate), gate).interaction_operator_generator() other_interaction_op = openfermion.normal_ordered(interaction_op) assert interaction_op == other_interaction_op
def benchmark_commutator_diagonal_coulomb_operators_2D_spinless_jellium( side_length): """Test speed of computing commutators using specialized functions. Args: side_length: The side length of the 2D jellium grid. There are side_length ** 2 qubits, and O(side_length ** 4) terms in the Hamiltonian. Returns: runtime_commutator: The time it takes to compute a commutator, after partitioning the terms and normal ordering, using the regular commutator function. runtime_diagonal_commutator: The time it takes to compute the same commutator using methods restricted to diagonal Coulomb operators. """ hamiltonian = normal_ordered( jellium_model(Grid(2, side_length, 1.), plane_wave=False)) part_a = FermionOperator.zero() part_b = FermionOperator.zero() add_to_a_or_b = 0 # add to a if 0; add to b if 1 for term, coeff in hamiltonian.terms.items(): # Partition terms in the Hamiltonian into part_a or part_b if add_to_a_or_b: part_a += FermionOperator(term, coeff) else: part_b += FermionOperator(term, coeff) add_to_a_or_b ^= 1 start = time.time() _ = normal_ordered(commutator(part_a, part_b)) end = time.time() runtime_commutator = end - start start = time.time() _ = commutator_ordered_diagonal_coulomb_with_two_body_operator( part_a, part_b) end = time.time() runtime_diagonal_commutator = end - start return runtime_commutator, runtime_diagonal_commutator
def test_split_operator_error_operator_VT_order_against_definition(self): hamiltonian = (normal_ordered(fermi_hubbard(3, 3, 1., 4.0)) - 2.3 * FermionOperator.identity()) potential_terms, kinetic_terms = ( diagonal_coulomb_potential_and_kinetic_terms_as_arrays( hamiltonian)) potential = sum(potential_terms, FermionOperator.zero()) kinetic = sum(kinetic_terms, FermionOperator.zero()) error_operator = ( split_operator_trotter_error_operator_diagonal_two_body( hamiltonian, order='V+T')) # V-then-T ordered double commutators: [V, [T, V]] + [T, [T, V]] / 2 inner_commutator = normal_ordered(commutator(kinetic, potential)) error_operator_definition = normal_ordered( commutator(potential, inner_commutator)) error_operator_definition += normal_ordered( commutator(kinetic, inner_commutator)) / 2.0 error_operator_definition /= 12.0 self.assertEqual(error_operator, error_operator_definition)
def _get_fermionic_operators(self) -> [of.FermionOperator]: """Returns second-quantization Hamiltonian in fermionic operator form""" # UCC-Single amplitudes _single_amp = self.molecule.ccsd_single_amps # UCC-Double amplitudes _double_amp = self.molecule.ccsd_double_amps # Creation/Annihilation operators _ucc_operator = of.normal_ordered( of.uccsd_generator(_single_amp, _double_amp)) # check that _ucc_operator is either a FermionOperator or a list of FermionOperators if isinstance(_ucc_operator, of.FermionOperator): _ucc_operator = list(_ucc_operator) return _ucc_operator
def test_fermionic_hamiltonian_from_integrals(g, n_qubits): rg = RichardsonGaudin(g, n_qubits) #hc, hr1, hr2 = rg.hc, rg.hr1, rg.hr2 doci = rg constant = doci.constant reference_constant = 0 doci_qubit_op = doci.qubit_operator doci_mat = get_sparse_operator(doci_qubit_op).toarray() doci_eigvals = np.linalg.eigh(doci_mat)[0] tensors = doci.n_body_tensors one_body_tensors, two_body_tensors = tensors[(1, 0)], tensors[(1, 1, 0, 0)] fermion_op = get_fermion_operator( InteractionOperator(constant, one_body_tensors, 0.5 * two_body_tensors)) fermion_op = normal_ordered(fermion_op) fermion_mat = get_sparse_operator(fermion_op).toarray() fermion_eigvals = np.linalg.eigh(fermion_mat)[0] one_body_tensors2, two_body_tensors2 = rg.get_antisymmetrized_tensors() fermion_op2 = get_fermion_operator( InteractionOperator(reference_constant, one_body_tensors2, 0.5 * two_body_tensors2)) fermion_op2 = normal_ordered(fermion_op2) fermion_mat2 = get_sparse_operator(fermion_op2).toarray() fermion_eigvals2 = np.linalg.eigh(fermion_mat2)[0] for eigval in doci_eigvals: assert any(abs(fermion_eigvals - eigval) < 1e-6), "The DOCI spectrum should have \ been contained in the spectrum of the fermionic operator constructed via the \ DOCIHamiltonian class" for eigval in doci_eigvals: assert any(abs(fermion_eigvals2 - eigval) < 1e-6), "The DOCI spectrum should have \
def test_strong_interaction_hubbard_VT_order_gives_larger_error(self): hamiltonian = normal_ordered(fermi_hubbard(4, 4, 1., 10.0)) TV_error_operator = ( split_operator_trotter_error_operator_diagonal_two_body( hamiltonian, order='T+V')) TV_error_bound = numpy.sum(numpy.absolute( list(TV_error_operator.terms.values()))) VT_error_operator = ( split_operator_trotter_error_operator_diagonal_two_body( hamiltonian, order='V+T')) VT_error_bound = numpy.sum(numpy.absolute( list(VT_error_operator.terms.values()))) self.assertGreater(VT_error_bound, TV_error_bound)
def test_intermediate_interaction_hubbard_TV_order_has_larger_error(self): hamiltonian = normal_ordered(fermi_hubbard(4, 4, 1., 4.0)) TV_error_operator = ( split_operator_trotter_error_operator_diagonal_two_body( hamiltonian, order='T+V')) TV_error_bound = numpy.sum(numpy.absolute( list(TV_error_operator.terms.values()))) VT_error_operator = ( split_operator_trotter_error_operator_diagonal_two_body( hamiltonian, order='V+T')) VT_error_bound = numpy.sum(numpy.absolute( list(VT_error_operator.terms.values()))) self.assertAlmostEqual(TV_error_bound, 1706.66666666666) self.assertAlmostEqual(VT_error_bound, 1365.33333333333)
def test_warning_on_bad_input_first_arg(self): with warnings.catch_warnings(record=True) as w: operator_a = FermionOperator('4^ 3^ 2 1') operator_b = FermionOperator('3^ 2^ 3 2') reference = normal_ordered(commutator(operator_a, operator_b)) result = ( commutator_ordered_diagonal_coulomb_with_two_body_operator( operator_a, operator_b)) self.assertTrue(len(w) == 1) self.assertIn('Defaulted to standard commutator evaluation', str(w[-1].message)) # Result should still be correct in this case. diff = result - reference self.assertTrue(diff.isclose(FermionOperator.zero()))
def test_hubbard_trotter_error_matches_low_depth_trotter_error(self): hamiltonian = normal_ordered(fermi_hubbard(3, 3, 1., 2.3)) error_operator = ( fermionic_swap_trotter_error_operator_diagonal_two_body( hamiltonian)) error_operator.compress() # Unpack result into terms, indices they act on, and whether # they're hopping operators. result = simulation_ordered_grouped_low_depth_terms_with_info( hamiltonian) terms, indices, is_hopping = result old_error_operator = low_depth_second_order_trotter_error_operator( terms, indices, is_hopping, jellium_only=True) old_error_operator -= error_operator self.assertEqual(old_error_operator, FermionOperator.zero())
def test_1d_jellium_wigner_seitz_10_VT_order_gives_larger_error(self): hamiltonian = normal_ordered(jellium_model( hypercube_grid_with_given_wigner_seitz_radius_and_filling( 1, 5, wigner_seitz_radius=10., spinless=True), spinless=True, plane_wave=False)) TV_error_operator = ( split_operator_trotter_error_operator_diagonal_two_body( hamiltonian, order='T+V')) TV_error_bound = numpy.sum(numpy.absolute( list(TV_error_operator.terms.values()))) VT_error_operator = ( split_operator_trotter_error_operator_diagonal_two_body( hamiltonian, order='V+T')) VT_error_bound = numpy.sum(numpy.absolute( list(VT_error_operator.terms.values()))) self.assertGreater(VT_error_bound, TV_error_bound)
def get_polynomial_tensor(fermion_operator, n_qubits=None): r"""Convert a fermionic operator to a Polynomial Tensor. Args: fermion_operator (openferion.ops.FermionOperator): The operator. n_qubits (int): The number of qubits to be included in the PolynomialTensor. Must be at least equal to the number of qubits that are acted on by fermion_operator. If None, then the number of qubits is inferred from fermion_operator. Returns: openfermion.ops.PolynomialTensor: The tensor representation of the operator. """ if not isinstance(fermion_operator, FermionOperator): raise TypeError("Input must be a FermionOperator.") if n_qubits is None: n_qubits = count_qubits(fermion_operator) if n_qubits < count_qubits(fermion_operator): raise ValueError("Invalid number of qubits specified.") # Normal order the terms and initialize. fermion_operator = normal_ordered(fermion_operator) tensor_dict = {} # Loop through terms and assign to matrix. for term in fermion_operator.terms: coefficient = fermion_operator.terms[term] # Handle constant shift. if len(term) == 0: tensor_dict[()] = coefficient else: key = tuple([operator[1] for operator in term]) if tensor_dict.get(key) is None: tensor_dict[key] = np.zeros((n_qubits,) * len(key), complex) indices = tuple([operator[0] for operator in term]) tensor_dict[key][indices] = coefficient return PolynomialTensor(tensor_dict)
def diagonal_coulomb_potential_and_kinetic_terms_as_arrays(hamiltonian): """Give the potential and kinetic terms of a diagonal Coulomb Hamiltonian as arrays. Args: hamiltonian (FermionOperator): The diagonal Coulomb Hamiltonian to separate the potential and kinetic terms for. Identity is arbitrarily chosen to be part of the potential. Returns: Tuple of (potential_terms, kinetic_terms). Both elements of the tuple are numpy arrays of FermionOperators. """ if not isinstance(hamiltonian, FermionOperator): try: hamiltonian = normal_ordered(get_fermion_operator(hamiltonian)) except TypeError: raise TypeError('hamiltonian must be either a FermionOperator ' 'or DiagonalCoulombHamiltonian.') potential = FermionOperator.zero() kinetic = FermionOperator.zero() for term, coeff in iteritems(hamiltonian.terms): acted = set(term[i][0] for i in range(len(term))) if len(acted) == len(term) / 2: potential += FermionOperator(term, coeff) else: kinetic += FermionOperator(term, coeff) potential_terms = numpy.array([ FermionOperator(term, coeff) for term, coeff in iteritems(potential.terms) ]) kinetic_terms = numpy.array([ FermionOperator(term, coeff) for term, coeff in iteritems(kinetic.terms) ]) return (potential_terms, kinetic_terms)
def test_1D_jellium_trotter_error_matches_low_depth_trotter_error(self): hamiltonian = normal_ordered(jellium_model( hypercube_grid_with_given_wigner_seitz_radius_and_filling( 1, 5, wigner_seitz_radius=10., spinless=True), spinless=True, plane_wave=False)) error_operator = ( fermionic_swap_trotter_error_operator_diagonal_two_body( hamiltonian)) error_operator.compress() # Unpack result into terms, indices they act on, and whether # they're hopping operators. result = simulation_ordered_grouped_low_depth_terms_with_info( hamiltonian) terms, indices, is_hopping = result old_error_operator = low_depth_second_order_trotter_error_operator( terms, indices, is_hopping, jellium_only=True) old_error_operator -= error_operator self.assertEqual(old_error_operator, FermionOperator.zero())
def commutator_ordered_diagonal_coulomb_with_two_body_operator( operator_a, operator_b, prior_terms=None): """Compute the commutator of two-body operators provided that both are normal-ordered and that the first only has diagonal Coulomb interactions. Args: operator_a: The first FermionOperator argument of the commutator. All terms must be normal-ordered, and furthermore either hopping operators (i^ j) or diagonal Coulomb operators (i^ i or i^ j^ i j). operator_b: The second FermionOperator argument of the commutator. operator_b can be any arbitrary two-body operator. prior_terms (optional): The initial FermionOperator to add to. Returns: The commutator, or the commutator added to prior_terms if provided. Notes: The function could be readily extended to the case of arbitrary two-body operator_a given that operator_b has the desired form; however, the extra check slows it down without desirable added utility. """ if prior_terms is None: prior_terms = FermionOperator.zero() for term_a in operator_a.terms: coeff_a = operator_a.terms[term_a] for term_b in operator_b.terms: coeff_b = operator_b.terms[term_b] coefficient = coeff_a * coeff_b # If term_a == term_b the terms commute, nothing to add. if term_a == term_b or not term_a or not term_b: continue # Case 1: both operators are two-body, operator_a is i^ j^ i j. if (len(term_a) == len(term_b) == 4 and term_a[0][0] == term_a[2][0] and term_a[1][0] == term_a[3][0]): _commutator_two_body_diagonal_with_two_body( term_a, term_b, coefficient, prior_terms) # Case 2: commutator of a 1-body and a 2-body operator elif (len(term_b) == 4 and len(term_a) == 2) or (len(term_a) == 4 and len(term_b) == 2): _commutator_one_body_with_two_body(term_a, term_b, coefficient, prior_terms) # Case 3: both terms are one-body operators (both length 2) elif len(term_a) == 2 and len(term_b) == 2: _commutator_one_body_with_one_body(term_a, term_b, coefficient, prior_terms) # Final case (case 4): violation of the input promise. Still # compute the commutator, but warn the user. else: warnings.warn('Defaulted to standard commutator evaluation ' 'due to an out-of-spec operator.') additional = FermionOperator.zero() additional.terms[term_a + term_b] = coefficient additional.terms[term_b + term_a] = -coefficient additional = normal_ordered(additional) prior_terms += additional return prior_terms
def build_hamiltonian(ops: Union[FermionOperator, hamiltonian.Hamiltonian], norb: int = 0, conserve_number: bool = True, e_0: complex = 0. + 0.j) -> 'hamiltonian.Hamiltonian': """Build a Hamiltonian object for the fqe Args: ops (FermionOperator, hamiltonian.Hamiltonian) - input operator as \ FermionOperator. If a Hamiltonian is passed as an argument, \ this function returns as is. norb (int) - the number of orbitals in the system conserve_number (bool) - whether the operator conserves the number e_0 (complex) - the scalar part of the operator Returns: (hamiltonian.Hamiltonian) - General Hamiltonian that is created from ops """ if isinstance(ops, hamiltonian.Hamiltonian): return ops if isinstance(ops, tuple): validate_tuple(ops) return general_hamiltonian.General(ops, e_0=e_0) if not isinstance(ops, FermionOperator): raise TypeError('Expected FermionOperator' \ ' but received {}.'.format(type(ops))) assert is_hermitian(ops) out: Any if len(ops.terms) <= 2: out = sparse_hamiltonian.SparseHamiltonian(ops, e_0=e_0) else: if not conserve_number: ops = transform_to_spin_broken(ops) ops = normal_ordered(ops) ops_rank, e_0 = split_openfermion_tensor(ops) # type: ignore if norb == 0: for term in ops_rank.values(): ablk, bblk = largest_operator_index(term) norb = max(norb, ablk // 2 + 1, bblk // 2 + 1) else: norb = norb ops_mat = {} maxrank = 0 for rank, term in ops_rank.items(): index = rank // 2 - 1 ops_mat[index] = fermionops_tomatrix(term, norb) maxrank = max(index, maxrank) if len(ops_mat) == 1 and (0 in ops_mat): out = process_rank2_matrix(ops_mat[0], norb=norb, e_0=e_0) elif len(ops_mat) == 1 and \ (1 in ops_mat) and \ check_diagonal_coulomb(ops_mat[1]): out = diagonal_coulomb.DiagonalCoulomb(ops_mat[1], e_0=e_0) else: dtypes = [xx.dtype for xx in ops_mat.values()] dtypes = numpy.unique(dtypes) if len(dtypes) != 1: raise TypeError( "Non-unique coefficient types for input operator") for i in range(maxrank + 1): if i not in ops_mat: mat_dim = tuple([2 * norb for _ in range((i + 1) * 2)]) ops_mat[i] = numpy.zeros(mat_dim, dtype=dtypes[0]) ops_mat2 = [] for i in range(maxrank + 1): ops_mat2.append(ops_mat[i]) out = general_hamiltonian.General(tuple(ops_mat2), e_0=e_0) out._conserve_number = conserve_number return out
def test_first_factorization(): molecule = build_lih_moleculardata() oei, tei = molecule.get_integrals() lrt_obj = LowRankTrotter(oei=oei, tei=tei) ( eigenvalues, one_body_squares, one_body_correction, ) = lrt_obj.first_factorization() # get true op n_qubits = molecule.n_qubits fermion_tei = of.FermionOperator() test_tei_tensor = np.zeros((n_qubits, n_qubits, n_qubits, n_qubits)) for p, q, r, s in product(range(oei.shape[0]), repeat=4): if np.abs(tei[p, q, r, s]) < EQ_TOLERANCE: coefficient = 0.0 else: coefficient = tei[p, q, r, s] / 2.0 for sigma, tau in product(range(2), repeat=2): if 2 * p + sigma == 2 * q + tau or 2 * r + tau == 2 * s + sigma: continue term = ( (2 * p + sigma, 1), (2 * q + tau, 1), (2 * r + tau, 0), (2 * s + sigma, 0), ) fermion_tei += of.FermionOperator(term, coefficient=coefficient) test_tei_tensor[2 * p + sigma, 2 * q + tau, 2 * r + tau, 2 * s + sigma] = coefficient mol_ham = of.InteractionOperator( one_body_tensor=np.zeros((n_qubits, n_qubits)), two_body_tensor=test_tei_tensor, constant=0, ) # check induced norm on operator checked_op = of.FermionOperator() for ( p, q, ) in product(range(n_qubits), repeat=2): term = ((p, 1), (q, 0)) coefficient = one_body_correction[p, q] checked_op += of.FermionOperator(term, coefficient) # Build back two-body component. for l in range(one_body_squares.shape[0]): one_body_operator = of.FermionOperator() for p, q in product(range(n_qubits), repeat=2): term = ((p, 1), (q, 0)) coefficient = one_body_squares[l, p, q] one_body_operator += of.FermionOperator(term, coefficient) checked_op += eigenvalues[l] * (one_body_operator**2) true_fop = of.normal_ordered(of.get_fermion_operator(mol_ham)) difference = of.normal_ordered(checked_op - true_fop) assert np.isclose(0, difference.induced_norm())
def test_generalized_doubles(): generator = generate_antisymm_generator(2) nso = generator.shape[0] for p, q, r, s in product(range(nso), repeat=4): if p < q and s < r: assert np.isclose(generator[p, q, r, s], -generator[q, p, r, s]) ul, vl, one_body_residual, ul_ops, vl_ops, one_body_op = \ doubles_factorization_svd(generator) generator_mat = np.reshape(np.transpose(generator, [0, 3, 1, 2]), (nso**2, nso**2)).astype(np.float) one_body_residual_test = -np.einsum('pqrq->pr', generator) assert np.allclose(generator_mat, generator_mat.T) assert np.allclose(one_body_residual, one_body_residual_test) tgenerator_mat = np.zeros_like(generator_mat) for row_gem, col_gem in product(range(nso**2), repeat=2): p, s = row_gem // nso, row_gem % nso q, r = col_gem // nso, col_gem % nso tgenerator_mat[row_gem, col_gem] = generator[p, q, r, s] assert np.allclose(tgenerator_mat, generator_mat) u, sigma, vh = np.linalg.svd(generator_mat) fop = copy.deepcopy(one_body_op) fop2 = copy.deepcopy(one_body_op) fop3 = copy.deepcopy(one_body_op) fop4 = copy.deepcopy(one_body_op) for ll in range(len(sigma)): ul.append(np.sqrt(sigma[ll]) * u[:, ll].reshape((nso, nso))) ul_ops.append( get_fermion_op(np.sqrt(sigma[ll]) * u[:, ll].reshape((nso, nso)))) vl.append(np.sqrt(sigma[ll]) * vh[ll, :].reshape((nso, nso))) vl_ops.append( get_fermion_op(np.sqrt(sigma[ll]) * vh[ll, :].reshape((nso, nso)))) Smat = ul[ll] + vl[ll] Dmat = ul[ll] - vl[ll] S = ul_ops[ll] + vl_ops[ll] Sd = of.hermitian_conjugated(S) D = ul_ops[ll] - vl_ops[ll] Dd = of.hermitian_conjugated(D) op1 = S + 1j * of.hermitian_conjugated(S) op2 = S - 1j * of.hermitian_conjugated(S) op3 = D + 1j * of.hermitian_conjugated(D) op4 = D - 1j * of.hermitian_conjugated(D) assert np.isclose( of.normal_ordered(of.commutator( op1, of.hermitian_conjugated(op1))).induced_norm(), 0) assert np.isclose( of.normal_ordered(of.commutator( op2, of.hermitian_conjugated(op2))).induced_norm(), 0) assert np.isclose( of.normal_ordered(of.commutator( op3, of.hermitian_conjugated(op3))).induced_norm(), 0) assert np.isclose( of.normal_ordered(of.commutator( op4, of.hermitian_conjugated(op4))).induced_norm(), 0) fop3 += (1 / 8) * ((S**2 - Sd**2) - (D**2 - Dd**2)) fop4 += (1 / 16) * ((op1**2 + op2**2) - (op3**2 + op4**2)) op1mat = Smat + 1j * Smat.T op2mat = Smat - 1j * Smat.T op3mat = Dmat + 1j * Dmat.T op4mat = Dmat - 1j * Dmat.T assert np.allclose(of.commutator(op1mat, op1mat.conj().T), 0) assert np.allclose(of.commutator(op2mat, op2mat.conj().T), 0) assert np.allclose(of.commutator(op3mat, op3mat.conj().T), 0) assert np.allclose(of.commutator(op4mat, op4mat.conj().T), 0) # check that we have normal operators and that the outer product # of their eigenvalues is imaginary. Also check vv is unitary if not np.isclose(sigma[ll], 0): assert np.isclose( of.normal_ordered(get_fermion_op(op1mat) - op1).induced_norm(), 0) assert np.isclose( of.normal_ordered(get_fermion_op(op2mat) - op2).induced_norm(), 0) assert np.isclose( of.normal_ordered(get_fermion_op(op3mat) - op3).induced_norm(), 0) assert np.isclose( of.normal_ordered(get_fermion_op(op4mat) - op4).induced_norm(), 0) ww, vv = np.linalg.eig(op1mat) eye = np.eye(nso) assert np.allclose(np.outer(ww, ww).real, 0) assert np.allclose(vv.conj().T @ vv, eye) ww, vv = np.linalg.eig(op2mat) assert np.allclose(np.outer(ww, ww).real, 0) assert np.allclose(vv.conj().T @ vv, eye) ww, vv = np.linalg.eig(op3mat) assert np.allclose(np.outer(ww, ww).real, 0) assert np.allclose(vv.conj().T @ vv, eye) ww, vv = np.linalg.eig(op4mat) assert np.allclose(np.outer(ww, ww).real, 0) assert np.allclose(vv.conj().T @ vv, eye) fop2 += 0.25 * ul_ops[ll] * vl_ops[ll] fop2 += 0.25 * vl_ops[ll] * ul_ops[ll] fop2 += -0.25 * of.hermitian_conjugated( vl_ops[ll]) * of.hermitian_conjugated(ul_ops[ll]) fop2 += -0.25 * of.hermitian_conjugated( ul_ops[ll]) * of.hermitian_conjugated(vl_ops[ll]) fop += vl_ops[ll] * ul_ops[ll] true_fop = get_fermion_op(generator) assert np.isclose(of.normal_ordered(fop - true_fop).induced_norm(), 0) assert np.isclose(of.normal_ordered(fop2 - true_fop).induced_norm(), 0) assert np.isclose(of.normal_ordered(fop3 - true_fop).induced_norm(), 0) assert np.isclose(of.normal_ordered(fop4 - true_fop).induced_norm(), 0)