def test_relations(self): n_spatial_orbitals = 2 s_plus = s_plus_operator(n_spatial_orbitals) s_minus = s_minus_operator(n_spatial_orbitals) sx = sx_operator(n_spatial_orbitals) sy = sy_operator(n_spatial_orbitals) sz = sz_operator(n_spatial_orbitals) s_squared = s_squared_operator(n_spatial_orbitals) identity = FermionOperator(()) self.assertEqual(normal_ordered(sx), normal_ordered(.5 * (s_plus + s_minus))) self.assertEqual(normal_ordered(sy), normal_ordered((.5 / 1.j) * (s_plus - s_minus))) self.assertEqual(normal_ordered(s_squared), normal_ordered(sx**2 + sy**2 + sz**2)) self.assertEqual( normal_ordered(s_squared), normal_ordered(s_plus * s_minus + sz * (sz - identity))) self.assertEqual(normal_ordered(commutator(s_plus, s_minus)), normal_ordered(2 * sz)) self.assertEqual(normal_ordered(commutator(sx, sy)), normal_ordered(1.j * sz))
def test_commutator(self): operator_a = FermionOperator('') self.assertTrue(FermionOperator().isclose( commutator(operator_a, self.fermion_operator))) operator_b = QubitOperator('X1 Y2') self.assertTrue( commutator(self.qubit_operator, operator_b).isclose(self.qubit_operator * operator_b - operator_b * self.qubit_operator))
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 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 generate_QCC_gradient_groupings(hamiltonian, n_qubits, hf_occ, cutoff=0.001): QMF_angles = np.concatenate([np.array([0] * n_qubits), np.pi * hf_occ]) hamiltonian_flip_indices = get_hamiltonian_flipindices( hamiltonian, n_qubits) gradient_groupings = [] for flip_indices in hamiltonian_flip_indices: representative_entangler = generate_qubitop( generate_representative(flip_indices, n_qubits)) pauli_commutator = commutator(hamiltonian, representative_entangler) gradient = abs( 1j / 2 * eval_meanfield_expectation(pauli_commutator, QMF_angles)) if gradient > cutoff: gradient_groupings.append((flip_indices, round(gradient, 4))) gradient_groupings = Sort(gradient_groupings) return gradient_groupings
def double_commutator(op1, op2, op3, indices2=None, indices3=None, is_hopping_operator2=None, is_hopping_operator3=None): """Return the double commutator [op1, [op2, op3]]. Assumes the operators are from the dual basis Hamiltonian. Args: op1, op2, op3 (FermionOperators): operators for the commutator. indices2, indices3 (set): The indices op2 and op3 act on. is_hopping_operator2 (bool): Whether op2 is a hopping operator. is_hopping_operator3 (bool): Whether op3 is a hopping operator. Returns: The double commutator of the given operators. """ if is_hopping_operator2 and is_hopping_operator3: indices2 = set(indices2) indices3 = set(indices3) # Determine which indices both op2 and op3 act on. try: intersection, = indices2.intersection(indices3) except ValueError: return FermionOperator.zero() # Remove the intersection from the set of indices, since it will get # cancelled out in the final result. indices2.remove(intersection) indices3.remove(intersection) # Find the indices of the final output hopping operator. index2, = indices2 index3, = indices3 coeff2 = op2.terms[list(op2.terms)[0]] coeff3 = op3.terms[list(op3.terms)[0]] commutator23 = (FermionOperator( ((index2, 1), (index3, 0)), coeff2 * coeff3) + FermionOperator( ((index3, 1), (index2, 0)), -coeff2 * coeff3)) else: commutator23 = normal_ordered(commutator(op2, op3)) return normal_ordered(commutator(op1, commutator23))
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 test_uccsd_singlet_symmetries(self): """Test that the singlet generator has the correct symmetries.""" test_orbitals = 8 test_electrons = 4 packed_amplitude_size = uccsd_singlet_paramsize( test_orbitals, test_electrons) packed_amplitudes = randn(int(packed_amplitude_size)) generator = uccsd_singlet_generator(packed_amplitudes, test_orbitals, test_electrons) # Construct symmetry operators sz = sz_operator(test_orbitals) s_squared = s_squared_operator(test_orbitals) # Check the symmetries comm_sz = normal_ordered(commutator(generator, sz)) comm_s_squared = normal_ordered(commutator(generator, s_squared)) zero = FermionOperator() self.assertEqual(comm_sz, zero) self.assertEqual(comm_s_squared, zero)
def bin_str_to_commutator(bin_str, x, y): """ Generate nested commutator in Dynkin's style with binary string representation e.g. '010...' -> [X,[Y,[X, ...]]] """ from openfermion.utils import commutator char_to_xy = lambda char: x if char == '0' else y next_term = char_to_xy(bin_str[0]) later_terms = bin_str[1:] if len(bin_str) == 1: return next_term else: return commutator(next_term, bin_str_to_commutator(later_terms, x, y))
def add_singles(occ_idx, vir_idx): for i in occ_idx: for a in vir_idx: single = FermionOperator(((a, 1), (i, 0))) # 1. build Fermion anti-Hermitian operator single -= hermitian_conjugated(single) # 2. JW transformation to qubit operator jw_single = jordan_wigner(single) h_single_commutator = commutator(self.h_qubit_OF, jw_single) # 3. qforte.build_from_openfermion(OF_qubitop) qf_jw_single = qforte.build_from_openfermion(jw_single) qf_commutator = qforte.build_from_openfermion(h_single_commutator) self.fermion_ops.append((i,a)) self.jw_ops.append(qf_jw_single) self.jw_commutators.append(qf_commutator)
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 add_doubles(occ_idx_pairs, vir_idx_pairs): for ji in occ_idx_pairs: for ba in vir_idx_pairs: j, i = ji b, a = ba double = FermionOperator(F'{a}^ {b}^ {i} {j}') double -= hermitian_conjugated(double) jw_double = jordan_wigner(double) h_double_commutator = commutator(self.h_qubit_OF, jw_double) qf_jw_double = qforte.build_from_openfermion(jw_double) qf_commutator = qforte.build_from_openfermion( h_double_commutator) self.fermion_ops.append((j,i,b,a)) self.jw_ops.append(qf_jw_double) self.jw_commutators.append(qf_commutator)
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 single_operator_gradient(p, q, jw_hamiltonian, state, n_qubits): """Function Compute gradient d<H>/dXpq Author(s): Masaki Taii, Takashi Tsuchimochi """ dummy = FermionOperator(f"{n_qubits-1}^ {n_qubits-1}", 1.) fermi = FermionOperator(f"{p}^ {q}", 1.) + FermionOperator( f"{q}^ {p}", -1.) jw_dummy = jordan_wigner(dummy) jw_fermi = jordan_wigner(fermi) jw_gradient = commutator(jw_fermi, jw_hamiltonian) + jw_dummy observable_dummy \ = create_observable_from_openfermion_text(str(jw_dummy)) observable_gradient \ = create_observable_from_openfermion_text(str(jw_gradient)) gradient = (observable_gradient.get_expectation_value(state) \ - observable_dummy.get_expectation_value(state)) return gradient
def test_commutator_operator_a_bad_type(self): with self.assertRaises(TypeError): commutator(1, self.fermion_operator)
def QCC(qubit_H, entanglers, angle_folds, amplitude_folds, sampler, num_cycles=5, num_samples=1000, strength=1e3, verbose=False): n, N_ent = count_qubits(qubit_H), len(entanglers) if N_ent == 0: QMF_energy, bloch_angles = QMF(qubit_H, angle_folds, sampler, num_cycles=num_cycles, num_samples=num_samples, strength=strength, verbose=verbose) return QMF_energy, bloch_angles expr = qubit_op_to_expr(qubit_H, angle_folds=angle_folds) #get coefficients if amplitude_folds == 0: alpha = [se.sin(se.Symbol('tau' + str(i))) for i in range(N_ent)] beta = [(1 - se.cos(se.Symbol('tau' + str(i)))) for i in range(N_ent)] elif amplitude_folds == 1: alpha = [ se.Symbol('F' + str(i)) * se.sin(se.Symbol('tau' + str(i))) for i in range(N_ent) ] beta = [(1 - se.cos(se.Symbol('tau' + str(i)))) for i in range(N_ent)] else: alpha = [ se.Symbol('F' + str(i)) * se.sin(se.Symbol('tau' + str(i))) for i in range(N_ent) ] beta = [ (1 - se.Symbol('G' + str(i)) * se.cos(se.Symbol('tau' + str(i)))) for i in range(N_ent) ] #calculate QCC transformation expr = 0 entanglers_str = list(entanglers.keys()) term_combs = list(itertools.product('abn', repeat=N_ent)) for term_comb in term_combs: coeff, term = 1, qubit_H for i in range(len(term_comb)): char, P = term_comb[i], entanglers[entanglers_str[i]] if char == 'a': coeff *= alpha[i] term = -1j / 2 * commutator(term, P) elif char == 'b': coeff *= beta[i] term = 1 / 2 * P * commutator(term, P) term = qubit_op_to_expr(term, angle_folds=angle_folds) expr += coeff * term #minimize QCC expression QCC_energy, cont_dict, disc_dict = minimize_expr(expr, angle_folds, amplitude_folds, sampler, max_cycles=num_cycles, num_samples=num_samples, strength=strength, verbose=verbose) #unfold continuous variables for key in cont_dict: num = str(key)[3:] if str(key)[:3] == 'phi': if angle_folds == 3: try: W = disc_dict[se.Symbol('W' + str(num))] if W == -1: cont_dict[key] = np.pi - cont_dict[key] except KeyError: pass if angle_folds >= 2: try: Q = disc_dict[se.Symbol('Q' + str(num))] if Q == -1: cont_dict[key] = 2 * np.pi - cont_dict[key] except KeyError: pass elif str(key)[:3] == 'the': if angle_folds >= 1: try: Z = disc_dict[se.Symbol('Z' + str(num))] if Z == -1: cont_dict[key] = np.pi - cont_dict[key] except KeyError: pass else: if amplitude_folds == 2: try: G = disc_dict[se.Symbol('G' + str(num))] if G == 1: cont_dict[key] = np.pi - cont_dict[key] except KeyError: pass if amplitude_folds >= 1: try: F = disc_dict[se.Symbol('F' + str(num))] if F == -1: cont_dict[key] = 2 * np.pi - cont_dict[key] except KeyError: pass return QCC_energy, cont_dict
def bch_expand_baseline(x, y, order): """Compute log[e^x e^y] using the Baker-Campbell-Hausdorff formula Args: x: An operator for which multiplication and addition are supported. For instance, a QubitOperator, FermionOperator or numpy array. y: The same type as x. order(int): The order to truncate the BCH expansions. Currently function goes up to only third order. Returns: z: The truncated BCH operator. Raises: ValueError: operator x is not same type as operator y. ValueError: invalid order parameter. ValueError: order exceeds maximum order supported. """ from openfermion.utils import commutator # First order. z = x + y # Second order. if order > 1: z += commutator(x, y) / 2. # Third order. if order > 2: z += commutator(x, commutator(x, y)) / 12. z += commutator(y, commutator(y, x)) / 12. # Fourth order. if order > 3: z -= commutator(y, commutator(x, commutator(x, y))) / 24. # Fifth order. if order > 4: z -= commutator( y, commutator(y, commutator(y, commutator(y, x)))) / 720. z -= commutator( x, commutator(x, commutator(x, commutator(x, y)))) / 720. z += commutator( x, commutator(y, commutator(y, commutator(y, x)))) / 360. z += commutator( y, commutator(x, commutator(x, commutator(x, y)))) / 360. z += commutator( y, commutator(x, commutator(y, commutator(x, y)))) / 120. z += commutator( x, commutator(y, commutator(x, commutator(y, x)))) / 120. return z
def test_commutes_identity(self): com = commutator(FermionOperator.identity(), FermionOperator('2^ 3', 2.3)) self.assertTrue(com.isclose(FermionOperator.zero()))
def test_commutes_no_intersection(self): com = commutator(FermionOperator('2^ 3'), FermionOperator('4^ 5^ 3')) com = normal_ordered(com) self.assertTrue(com.isclose(FermionOperator.zero()))
def test_commutes_number_operators(self): com = commutator(FermionOperator('4^ 3^ 4 3'), FermionOperator('2^ 2')) com = normal_ordered(com) self.assertTrue(com.isclose(FermionOperator.zero()))
def test_commutator_hopping_operators(self): com = commutator(3 * FermionOperator('1^ 2'), FermionOperator('2^ 3')) com = normal_ordered(com) self.assertTrue(com.isclose(FermionOperator('1^ 3', 3)))
def test_commutator_not_same_type(self): with self.assertRaises(TypeError): commutator(self.fermion_operator, self.qubit_operator)
def test_commutator_hopping_with_single_number(self): com = commutator(FermionOperator('1^ 2', 1j), FermionOperator('1^ 1')) com = normal_ordered(com) self.assertTrue(com.isclose(-FermionOperator('1^ 2') * 1j))
def test_commutator_hopping_with_double_number_two_intersections(self): com = commutator(FermionOperator('2^ 3'), FermionOperator('3^ 2^ 3 2')) com = normal_ordered(com) self.assertTrue(com.isclose(FermionOperator.zero()))
def jw_get_ground_states_by_particle_number(sparse_operator, particle_number, sparse=True, num_eigs=3): """For a Jordan-Wigner encoded Hermitian operator, compute the lowest eigenvalue and eigenstates at a particular particle number. The operator must conserve particle number. Args: sparse_operator(sparse): A Jordan-Wigner encoded sparse operator. particle_number(int): The particle number at which to compute ground states. sparse(boolean, optional): Whether to use sparse eigensolver. Default is True. num_eigs(int, optional): The number of eigenvalues to request from the sparse eigensolver. Needs to be at least as large as the degeneracy of the ground energy in order to obtain all ground states. Only used if `sparse=True`. Default is 3. Returns: ground_energy(float): The lowest eigenvalue of sparse_operator within the eigenspace of the number operator corresponding to particle_number. ground_states(list[ndarray]): A list of the corresponding eigenstates. Warning: The running time of this method is exponential in the number of qubits. """ # Check if operator is Hermitian if not is_hermitian(sparse_operator): raise ValueError('sparse_operator must be Hermitian.') n_qubits = int(numpy.log2(sparse_operator.shape[0])) # Check if operator conserves particle number sparse_num_op = jordan_wigner_sparse(number_operator(n_qubits)) com = commutator(sparse_num_op, sparse_operator) if com.nnz: maxval = max(map(abs, com.data)) if maxval > EQ_TOLERANCE: raise ValueError('sparse_operator must conserve particle number.') # Get the operator restricted to the subspace of the desired # particle number restricted_operator = jw_number_restrict_operator(sparse_operator, particle_number, n_qubits) if sparse and num_eigs >= restricted_operator.shape[0] - 1: # Restricted operator too small for sparse eigensolver sparse = False # Compute eigenvalues and eigenvectors if sparse: eigvals, eigvecs = scipy.sparse.linalg.eigsh(restricted_operator, k=num_eigs, which='SA') if abs(max(eigvals) - min(eigvals)) < EQ_TOLERANCE: warnings.warn( 'The lowest {} eigenvalues are degenerate. ' 'There may be more ground states; increase ' 'num_eigs or set sparse=False to get ' 'them.'.format(num_eigs), RuntimeWarning) else: dense_restricted_operator = restricted_operator.toarray() eigvals, eigvecs = numpy.linalg.eigh(dense_restricted_operator) # Get the ground energy if sparse: ground_energy = sorted(eigvals)[0] else: # No need to sort in the case of dense eigenvalue computation ground_energy = eigvals[0] # Get the indices of eigenvectors corresponding to the ground energy ground_state_indices = numpy.where( abs(eigvals - ground_energy) < EQ_TOLERANCE) ground_states = list() for i in ground_state_indices[0]: restricted_ground_state = eigvecs[:, i] # Expand this ground state to the whole vector space number_indices = jw_number_indices(particle_number, n_qubits) expanded_ground_state = scipy.sparse.csc_matrix( (restricted_ground_state.flatten(), (number_indices, [0] * len(number_indices))), shape=(2**n_qubits, 1)) # Add the expanded ground state to the list ground_states.append(expanded_ground_state) return ground_energy, ground_states
def test_commutator_operator_b_bad_type(self): with self.assertRaises(TypeError): commutator(self.qubit_operator, "hello")