def reverse_jordan_wigner(qubit_operator, n_qubits=None): """Transforms a QubitOperator into a FermionOperator using the Jordan-Wigner transform. Operators are mapped as follows: Z_j -> I - 2 a^\dagger_j a_j X_j -> (a^\dagger_j + a_j) Z_{j-1} Z_{j-2} .. Z_0 Y_j -> i (a^\dagger_j - a_j) Z_{j-1} Z_{j-2} .. Z_0 Args: qubit_operator: the QubitOperator to be transformed. n_qubits: the number of qubits term acts on. If not set, defaults to the maximum qubit number acted on by term. Returns: transformed_term: An instance of the FermionOperator class. Raises: TypeError: Input must be a QubitOperator. TypeError: Invalid number of qubits specified. TypeError: Pauli operators must be X, Y or Z. """ if not isinstance(qubit_operator, QubitOperator): raise TypeError('Input must be a QubitOperator.') if n_qubits is None: n_qubits = count_qubits(qubit_operator) if n_qubits < count_qubits(qubit_operator): raise ValueError('Invalid number of qubits specified.') # Loop through terms. transformed_operator = FermionOperator() for term in qubit_operator.terms: transformed_term = FermionOperator(()) if term: working_term = QubitOperator(term) pauli_operator = term[-1] while pauli_operator is not None: # Handle Pauli Z. if pauli_operator[1] == 'Z': transformed_pauli = FermionOperator( ()) + number_operator(n_qubits, pauli_operator[0], -2.) # Handle Pauli X and Y. else: raising_term = FermionOperator(((pauli_operator[0], 1), )) lowering_term = FermionOperator(((pauli_operator[0], 0), )) if pauli_operator[1] == 'Y': raising_term *= 1.j lowering_term *= -1.j transformed_pauli = raising_term + lowering_term # Account for the phase terms. for j in reversed(range(pauli_operator[0])): z_term = QubitOperator(((j, 'Z'), )) working_term = z_term * working_term term_key = list(working_term.terms)[0] transformed_pauli *= working_term.terms[term_key] working_term.terms[list(working_term.terms)[0]] = 1. # Get next non-identity operator acting below 'working_qubit'. assert len(working_term.terms) == 1 working_qubit = pauli_operator[0] - 1 for working_operator in reversed(list(working_term.terms)[0]): if working_operator[0] <= working_qubit: pauli_operator = working_operator break else: pauli_operator = None # Multiply term by transformed operator. transformed_term *= transformed_pauli # Account for overall coefficient transformed_term *= qubit_operator.terms[term] transformed_operator += transformed_term return transformed_operator
def bravyi_kitaev_fast_interaction_op(iop): """ Transform from InteractionOpeator to QubitOperator for Bravyi-Kitaev fast algorithm. The electronic Hamiltonian is represented in terms of creation and annihilation operators. These creation and annihilation operators could be used to define Majorana modes as follows: c_{2i} = a_i + a^{\dagger}_i, c_{2i+1} = (a_i - a^{\dagger}_{i})/(1j) These Majorana modes can be used to define edge operators B_i and A_{ij}: B_i=c_{2i}c_{2i+1}, A_{ij}=c_{2i}c_{2j} using these edge operators the fermionic algebra can be generated and hence all the terms in the electronic Hamiltonian can be expressed in terms of edge operators. The terms in electronic Hamiltonian can be divided into five types (arXiv 1208.5986). We can find the edge operator expression for each of those five types. For example, the excitation operator term in Hamiltonian when represented in terms of edge operators becomes: a_i^{\dagger}a_j+a_j^{\dagger}a_i = (-1j/2)*(A_ij*B_i+B_j*A_ij) For the sake of brevity the reader is encouraged to look up the expressions of other terms from the code below. The variables for edge operators are chosen according to the nomenclature defined above (B_i and A_ij). Args: iop (Interaction Operator): n_qubit (int): Number of qubits Returns: qubit_operator: An instance of the QubitOperator class. """ n_qubits = count_qubits(iop) # Initialize qubit operator as constant. qubit_operator = QubitOperator((), iop.constant) edge_matrix = bravyi_kitaev_fast_edge_matrix(iop) edge_matrix_indices = numpy.array(numpy.nonzero(numpy.triu(edge_matrix) - numpy.diag(numpy.diag(edge_matrix)))) # Loop through all indices. for p in range(n_qubits): for q in range(n_qubits): # Handle one-body terms. coefficient = complex(iop[p, q]) if coefficient and p >= q: qubit_operator += (coefficient * one_body(edge_matrix_indices, p, q)) # Keep looping for the two-body terms. for r in range(n_qubits): for s in range(n_qubits): coefficient = complex(iop[p, q, r, s]) # Skip zero terms. if (not coefficient) or (p == q) or (r == s): continue # Identify and skip one of the complex conjugates. if [p, q, r, s] != [s, r, q, p]: if len(set([p, q, r, s])) == 4: if min(r, s) < min(p, q): continue elif p != r and q < p: continue # Handle the two-body terms. transformed_term = two_body(edge_matrix_indices, p, q, r, s) transformed_term *= coefficient qubit_operator += transformed_term return qubit_operator
def get_interaction_operator(fermion_operator, n_qubits=None): """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.') 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) 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: continue # 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
def apply_constraints(operator, n_fermions, use_scipy=True): """Function to use linear programming to apply constraints. Args: operator(FermionOperator): FermionOperator with only 1- and 2-body terms that we wish to vectorize. n_fermions(int): The number of particles in the simulation. use_scipy(bool): Whether to use scipy (True) or cvxopt (False). Returns: modified_operator(FermionOperator): The operator with reduced norm that has been modified with equality constraints. """ # Get constraint matrix. n_orbitals = count_qubits(operator) constraints = constraint_matrix(n_orbitals, n_fermions) n_constraints, n_terms = constraints.get_shape() # Get vectorized operator. vectorized_operator = operator_to_vector(operator) initial_bound = numpy.sum(numpy.absolute(vectorized_operator[1::]))**2 print('Initial bound on measurements is %f.' % initial_bound) # Get linear programming coefficient vector. n_variables = n_constraints + n_terms lp_vector = numpy.zeros(n_variables, float) lp_vector[-n_terms:] = 1. # Get linear programming constraint matrix. lp_constraint_matrix = scipy.sparse.dok_matrix((2 * n_terms, n_variables)) for (i, j), value in constraints.items(): if j: lp_constraint_matrix[j, i] = value lp_constraint_matrix[n_terms + j, i] = -value for i in range(n_terms): lp_constraint_matrix[i, n_constraints + i] = -1. lp_constraint_matrix[n_terms + i, n_constraints + i] = -1. # Get linear programming constraint vector. lp_constraint_vector = numpy.zeros(2 * n_terms, float) lp_constraint_vector[:n_terms] = vectorized_operator lp_constraint_vector[-n_terms:] = -vectorized_operator # Perform linear programming. print('Starting linear programming.') if use_scipy: options = {'maxiter': int(1e6)} bound = n_constraints * [(None, None)] + n_terms * [(0, None)] solution = scipy.optimize.linprog(c=lp_vector, A_ub=lp_constraint_matrix.toarray(), b_ub=lp_constraint_vector, bounds=bound, options=options) # Analyze results. print(solution['message']) assert solution['success'] solution_vector = solution['x'] objective = solution['fun']**2 print('Program terminated after %i iterations.' % solution['nit']) else: # Convert to CVXOpt sparse matrix. from cvxopt import matrix, solvers, spmatrix lp_vector = matrix(lp_vector) lp_constraint_matrix = lp_constraint_matrix.tocoo() lp_constraint_matrix = spmatrix(lp_constraint_matrix.data, lp_constraint_matrix.row.tolist(), lp_constraint_matrix.col.tolist()) lp_constraint_vector = matrix(lp_constraint_vector) # Run linear programming. solution = solvers.lp(c=lp_vector, G=lp_constraint_matrix, h=lp_constraint_vector, solver='glpk') # Analyze results. print(solution['status']) solution_vector = numpy.array(solution['x']).transpose()[0] # Alternative bound. residuals = solution_vector[-n_terms:] alternative_bound = numpy.sum(numpy.absolute(residuals[1::]))**2 print('Bound implied by solution vector is %f.' % alternative_bound) # Make sure residuals are positive. for residual in residuals: assert residual > -1e-6 # Get bound on updated Hamiltonian. weights = solution_vector[:n_constraints] final_vectorized_operator = (vectorized_operator - constraints.transpose() * weights) final_bound = numpy.sum(numpy.absolute(final_vectorized_operator[1::]))**2 print('Actual bound determined is %f.' % final_bound) # Return modified operator. modified_operator = vector_to_operator(final_vectorized_operator, n_orbitals) return (modified_operator + hermitian_conjugated(modified_operator)) / 2.
def get_linear_qubit_operator(qubit_operator, n_qubits=None): """ Return a linear operator with matvec defined to avoid instantiating a huge matrix, which requires lots of memory. The idea is that a single i_th qubit operator, O_i, is a 2-by-2 matrix, to be applied on a vector of length n_hilbert / 2^i, performs permutations and/ or adds an extra factor for its first half and the second half, e.g. a `Z` operator keeps the first half unchanged, while adds a factor of -1 to the second half, while an `I` keeps it both components unchanged. Note that the vector length is n_hilbert / 2^i, therefore when one works on i monotonically (in increasing order), one keeps splitting the vector to the right size and then apply O_i on them independently. Also note that operator O_i, is an *envelop operator* for all operators after it, i.e. {O_j | j > i}, which implies that starting with i = 0, one can split the vector, apply O_i, split the resulting vector (cached) again for the next operator. Args: qubit_operator(QubitOperator): A qubit operator to be applied on vectors. Returns: linear_operator(LinearOperator): The equivalent operator which is well defined when applying to a vector. """ if n_qubits is None: n_qubits = count_qubits(qubit_operator) if n_qubits < count_qubits(qubit_operator): raise ValueError('Invalid number of qubits specified.') n_hilbert = 2 ** n_qubits def matvec(vec): """matvec for the LinearOperator class. Args: vec(numpy.ndarray): 1D numpy array. Returns: retvec(numpy.ndarray): same to the shape of input vec. """ if len(vec) != n_hilbert: raise ValueError('Invalid length of vector specified: %d != %d' %(len(vec), n_hilbert)) retvec = numpy.zeros(vec.shape, dtype=complex) # Loop through the terms. for qubit_term in qubit_operator.terms: vecs = [vec] tensor_factor = 0 coefficient = qubit_operator.terms[qubit_term] for pauli_operator in qubit_term: # Split vector by half and half for each bit. if pauli_operator[0] > tensor_factor: vecs = [v for iter_v in vecs for v in numpy.split( iter_v, 2 ** (pauli_operator[0] - tensor_factor))] # Note that this is to make sure that XYZ operations always work # on vector pairs. vec_pairs = [numpy.split(v, 2) for v in vecs] # There is an non-identity op here, transform the vector. xyz = { 'X' : lambda vps: [[vp[1], vp[0]] for vp in vps], 'Y' : lambda vps: [[-1j * vp[1], 1j * vp[0]] for vp in vps], 'Z' : lambda vps: [[vp[0], -vp[1]] for vp in vps], } vecs = [v for vp in xyz[pauli_operator[1]](vec_pairs) for v in vp] tensor_factor = pauli_operator[0] + 1 # No need to check tensor_factor, i.e. to deal with bits left. retvec += coefficient * numpy.concatenate(vecs) return retvec return scipy.sparse.linalg.LinearOperator((n_hilbert, n_hilbert), matvec=matvec, dtype=complex)
def simulation_ordered_grouped_dual_basis_terms_with_info( dual_basis_hamiltonian, input_ordering=None): """Give terms from the dual basis Hamiltonian in simulated order. Uses the simulation ordering, grouping terms into hopping (i^ j + j^ i) and number (i^j^ i j + c_i i^ i + c_j j^ j) operators. Pre-computes term information (indices each operator acts on, as well as whether each operator is a hopping operator. Args: dual_basis_hamiltonian (FermionOperator): The Hamiltonian. input_ordering (list): The initial Jordan-Wigner canonical order. Returns: A 3-tuple of terms from the plane-wave dual basis Hamiltonian in order of simulation, the indices they act on, and whether they are hopping operators (both also in the same order). """ zero = FermionOperator.zero() hamiltonian = dual_basis_hamiltonian n_qubits = count_qubits(hamiltonian) ordered_terms = [] ordered_indices = [] ordered_is_hopping_operator = [] # If no input mode ordering is specified, default to range(n_qubits). if not input_ordering: input_ordering = list(range(n_qubits)) # Half a second-order Trotter step reverses the input ordering: this tells # us how much we need to include in the ordered list of terms. final_ordering = list(reversed(input_ordering)) # Follow odd-even transposition sort. In alternating steps, swap each even # qubits with the odd qubit to its right, and in the next step swap each # the odd qubits with the even qubit to its right. Do this until the input # ordering has been reversed. odd = 0 while input_ordering != final_ordering: for i in range(odd, n_qubits - 1, 2): # Always keep the max on the left to avoid having to normal order. left = max(input_ordering[i], input_ordering[i + 1]) right = min(input_ordering[i], input_ordering[i + 1]) # Calculate the hopping operators in the Hamiltonian. left_hopping_operator = FermionOperator( ((left, 1), (right, 0)), hamiltonian.terms.get( ((left, 1), (right, 0)), 0.0)) right_hopping_operator = FermionOperator( ((right, 1), (left, 0)), hamiltonian.terms.get( ((right, 1), (left, 0)), 0.0)) # Calculate the two-number operator l^ r^ l r in the Hamiltonian. two_number_operator = FermionOperator( ((left, 1), (right, 1), (left, 0), (right, 0)), hamiltonian.terms.get( ((left, 1), (right, 1), (left, 0), (right, 0)), 0.0)) # Calculate the left number operator, left^ left. left_number_operator = FermionOperator( ((left, 1), (left, 0)), hamiltonian.terms.get( ((left, 1), (left, 0)), 0.0)) # Calculate the right number operator, right^ right. right_number_operator = FermionOperator( ((right, 1), (right, 0)), hamiltonian.terms.get( ((right, 1), (right, 0)), 0.0)) # Divide single-number terms by n_qubits-1 to avoid over-counting. # Each qubit is swapped n_qubits-1 times total. left_number_operator /= (n_qubits - 1) right_number_operator /= (n_qubits - 1) # If the overall hopping operator isn't close to zero, append it. # Include the indices it acts on and that it's a hopping operator. if not (left_hopping_operator + right_hopping_operator).isclose(zero): ordered_terms.append(left_hopping_operator + right_hopping_operator) ordered_indices.append(set((left, right))) ordered_is_hopping_operator.append(True) # If the overall number operator isn't close to zero, append it. # Include the indices it acts on and that it's a number operator. if not (two_number_operator + left_number_operator + right_number_operator).isclose(zero): ordered_terms.append(two_number_operator + left_number_operator + right_number_operator) ordered_indices.append(set((left, right))) ordered_is_hopping_operator.append(False) # Track the current Jordan-Wigner canonical ordering. input_ordering[i], input_ordering[i + 1] = (input_ordering[i + 1], input_ordering[i]) # Alternate even and odd steps of the reversal procedure. odd = 1 - odd return (ordered_terms, ordered_indices, ordered_is_hopping_operator)
def stagger_with_info(hamiltonian, input_ordering, parity): """Give terms simulated in a single stagger of a Trotter step. Groups terms into hopping (i^ j + j^ i) and number (i^j^ i j + c_i i^ i + c_j j^ j) operators. Pre-computes term information (indices each operator acts on, as well as whether each operator is a hopping operator). Args: hamiltonian (FermionOperator): The Hamiltonian. input_ordering (list): The initial Jordan-Wigner canonical order. parity (boolean): Whether to determine the terms from the next even (False = 0) or odd (True = 1) stagger. Returns: A 3-tuple of terms from the Hamiltonian that are simulated in the stagger, the indices they act on, and whether they are hopping operators (all in the same order). Notes: The "staggers" used here are the left (parity=False) and right (parity=True) staggers detailed in Kivlichan et al., "Quantum Simulation of Electronic Structure with Linear Depth and Connectivity", arxiv:1711.04789. As such, the Hamiltonian must be in the form discussed in that paper. This constrains it to have only hopping terms (i^ j + j^ i) and potential terms which are products of at most two number operators (n_i or n_i n_j). """ terms_in_step = [] indices_in_step = [] is_hopping_operator_in_step = [] zero = FermionOperator.zero() n_qubits = count_qubits(hamiltonian) # A single round of odd-even transposition sort. for i in range(parity, n_qubits - 1, 2): # Always keep the max on the left to avoid having to normal order. left = max(input_ordering[i], input_ordering[i + 1]) right = min(input_ordering[i], input_ordering[i + 1]) # Calculate the hopping operators in the Hamiltonian. left_hopping_operator = FermionOperator( ((left, 1), (right, 0)), hamiltonian.terms.get(((left, 1), (right, 0)), 0.0)) right_hopping_operator = FermionOperator( ((right, 1), (left, 0)), hamiltonian.terms.get(((right, 1), (left, 0)), 0.0)) # Calculate the two-number operator l^ r^ l r in the Hamiltonian. two_number_operator = FermionOperator( ((left, 1), (right, 1), (left, 0), (right, 0)), hamiltonian.terms.get( ((left, 1), (right, 1), (left, 0), (right, 0)), 0.0)) # Calculate the left number operator, left^ left. left_number_operator = FermionOperator( ((left, 1), (left, 0)), hamiltonian.terms.get(((left, 1), (left, 0)), 0.0)) # Calculate the right number operator, right^ right. right_number_operator = FermionOperator( ((right, 1), (right, 0)), hamiltonian.terms.get(((right, 1), (right, 0)), 0.0)) # Divide single-number terms by n_qubits-1 to avoid over-counting. # Each qubit is swapped n_qubits-1 times total. left_number_operator /= (n_qubits - 1) right_number_operator /= (n_qubits - 1) # If the overall hopping operator isn't close to zero, append it. # Include the indices it acts on and that it's a hopping operator. if not (left_hopping_operator + right_hopping_operator) == zero: terms_in_step.append(left_hopping_operator + right_hopping_operator) indices_in_step.append(set((left, right))) is_hopping_operator_in_step.append(True) # If the overall number operator isn't close to zero, append it. # Include the indices it acts on and that it's a number operator. if not (two_number_operator + left_number_operator + right_number_operator) == zero: terms_in_step.append(two_number_operator + left_number_operator + right_number_operator) indices_in_step.append(set((left, right))) is_hopping_operator_in_step.append(False) # Modify the current Jordan-Wigner canonical ordering in-place. input_ordering[i], input_ordering[i + 1] = (input_ordering[i + 1], input_ordering[i]) return terms_in_step, indices_in_step, is_hopping_operator_in_step
def test_n_qubits_interaction_operator(self): self.assertEqual(self.n_qubits, count_qubits(self.interaction_operator))
def test_n_qubits_bad_type(self): with self.assertRaises(TypeError): count_qubits('twelve')
def test_n_qubits_fermion_operator(self): self.assertEqual(self.n_qubits, count_qubits(self.fermion_operator))
def test_n_qubits_qubit_operator(self): self.assertEqual(self.n_qubits, count_qubits(self.qubit_operator))
def test_n_qubits_single_fermion_term(self): self.assertEqual(self.n_qubits, count_qubits(self.fermion_term))
def simulation_gate_trotter_step(register, hamiltonian, input_ordering=None, first_order=True): """Simulate a unit time Trotter step under the plane wave dual basis Hamiltonian using the fermionic simulation gate. Args: register (projectq.QuReg): The register to apply the unitary to. hamiltonian (QubitOperator): Qubit operator representing the problem in the plane-wave dual basis. input_ordering (list): The input Jordan-Wigner ordering. first_order (bool): Whether to apply a first or second-order Trotter step. Notes: Applying a first-order Trotter step reverses the input ordering. """ n_qubits = count_qubits(hamiltonian) if not input_ordering: input_ordering = list(range(n_qubits)) # The intermediate ordering is the halfway point for second-order Trotter. intermediate_ordering = input_ordering[::-1] if first_order: final_ordering = intermediate_ordering else: final_ordering = input_ordering[:] # Whether we're in an odd or an even stagger. Alternates at each step. odd = 0 # If we do a second order Trotter step, the input and final orderings # are the same. Use a flag to force us to leave the initial step. moved = False while input_ordering != final_ordering or not moved: moved = True for i in range(odd, n_qubits - 1, 2): left = input_ordering[i] right = input_ordering[i + 1] # For real c, c*([3^ 2] + [2^ 3]) transforms under JW to # c/2 (X_2 X_3 + Y_2 Y_3); evolution is exp(-ic/2(XX+YY)) xx_yy_angle = hamiltonian.terms.get(((left, 1), (right, 0)), 0.0) left, right = max(left, right), min(left, right) # Two-body terms c 2^ 1^ 2 1 are equal to -c n_2 n_1. JW maps this # to -c(I-Z_2)(I-Z_1)/4 = -c I/4 + c Z_1/4 + c Z_2/4 - c Z_1 Z_2/4. # Evolution is thus exp(ic/4*(I - Z_1 - Z_2 + Z_1 Z_2)). zz_angle = -hamiltonian.terms.get( ((left, 1), (right, 1), (left, 0), (right, 0)), 0.0) / 2. # Divide by two for second order Trotter. if not first_order: xx_yy_angle /= 2 zz_angle /= 2 special_F_adjacent(register, i, xx_yy_angle, zz_angle) # The single-Z rotation angle is the opposite of the ZZ angle. if numpy.abs(zz_angle) > 0.0: Rz(-zz_angle) | register[i] Rz(-zz_angle) | register[i + 1] Ph(-zz_angle / 2.) | register[0] num_operator_left = ((input_ordering[i], 1), (input_ordering[i], 0)) if num_operator_left in hamiltonian.terms: # Jordan-Wigner maps a number term c*n_i to c*(I-Z_i)/2. # Time evolution is then exp(-i c*(I-Z_i)/2), which is equal to # a phase exp(-ic/2) and a rotation Rz(-c). z_angle = (-hamiltonian.terms[num_operator_left] / (n_qubits - 1)) # Divide by two for second order Trotter. if not first_order: z_angle /= 2. # After special_F_adjacent, qubits i and i+1 have swapped; # the ith rotation must thus be applied to qubit i+1. if numpy.abs(z_angle) > 0.0: Rz(z_angle) | register[i + 1] Ph(z_angle / 2.) | register[0] num_operator_right = ((input_ordering[i + 1], 1), (input_ordering[i + 1], 0)) if num_operator_right in hamiltonian.terms: # Jordan-Wigner maps a number term c*n_i to c*(I-Z_i)/2. # Time evolution is then exp(-i c*(I-Z_i)/2), which is equal to # a phase exp(-ic/2) and a rotation Rz(-c). z_angle = (-hamiltonian.terms[num_operator_right] / (n_qubits - 1)) # Divide by two for second order Trotter. if not first_order: z_angle /= 2 # After special_F_adjacent, qubits i and i+1 have swapped; # the (i+1)th rotation must thus be applied to qubit i. if numpy.abs(z_angle) > 0.0: Rz(z_angle) | register[i] Ph(z_angle / 2.) | register[0] # Finally, swap the two modes in input_ordering. input_ordering[i], input_ordering[i + 1] = (input_ordering[i + 1], input_ordering[i]) # Unless we're at the intermediate ordering, odd should flip. # This is only needed for second-order Trotter. if input_ordering != intermediate_ordering: odd = 1 - odd return input_ordering
def build_qaoa_circuit_grads(params, hamiltonians): """ Generates gradient circuits and corresponding factors for the QAOA ansatz defined in the function build_qaoa_circuit. Args: hamiltonians (list): A list of dict or zquantum.core.qubitoperator.QubitOperator objects representing Hamiltonians H1, H2, ..., Hk which forms one layer of the ansatz exp(-i Hk tk) ... exp(-i H2 t2) exp(-i H1 t1) For example, in the case of QAOA proposed by Farhi et al, the list the list is then [H1, H2] where H1 is the Hamiltonian for which the ground state is sought, and H2 is the Hamiltonian for which the time evolution act as a diffuser in the search space. params (numpy.ndarray): A list of sets of parameters. Each parameter in a set specifies the time duration of evolution under each of the Hamiltonians H1, H2, ... Hk. Returns: gradient_circuits (list of lists of zquantum.core.circuit.Circuit: the circuits) circuit_factors (list of lists of floats): combination coefficients for the expectation values of the list of circuits. """ if mod(len(params), len(hamiltonians)) != 0: raise Warning('There are {} input parameters and {} Hamiltonians. Since {} does not divide {} the last layer will be incomplete.'.\ format(len(params), len(hamiltonians), len(params), len(hamiltonians))) # Convert qubit operators from dicts to QubitOperator objects, if needed for index, hamiltonian in enumerate(hamiltonians): if isinstance(hamiltonian, dict): hamiltonians[index] = convert_dict_to_qubitop(hamiltonian) hadamard_layer = Circuit() # Start with a layer of Hadarmard gates n_qubits = count_qubits(hamiltonians[0]) qubits = [Qubit(qubit_index) for qubit_index in range(n_qubits)] hadamard_layer.qubits = qubits for qubit_index in range(n_qubits): hadamard_layer.gates.append(Gate('H', (qubits[qubit_index], ))) # Add time evolution layers gradient_circuits = [] circuit_factors = [] for index1 in range(params.shape[0]): hamiltonian_index1 = mod(index1, len(hamiltonians)) current_hamiltonian = qubitop_to_pyquilpauli( hamiltonians[hamiltonian_index1]) derivative_circuits_for_index1, factors = time_evolution_derivatives( current_hamiltonian, params[index1]) param_circuits = [] for derivative_circuit in derivative_circuits_for_index1: output_circuit = Circuit() output_circuit.qubits = qubits output_circuit += hadamard_layer for index2 in range(params.shape[0]): hamiltonian_index2 = mod(index2, len(hamiltonians)) if index2 == index1: output_circuit += derivative_circuit else: current_hamiltonian = qubitop_to_pyquilpauli( hamiltonians[hamiltonian_index2]) output_circuit += time_evolution(current_hamiltonian, params[index2]) param_circuits.append(output_circuit) circuit_factors.append(factors) gradient_circuits.append(param_circuits) return gradient_circuits, circuit_factors
def get_diagonal_coulomb_hamiltonian(fermion_operator, n_qubits=None, ignore_incompatible_terms=False): r"""Convert a FermionOperator to a DiagonalCoulombHamiltonian. Args: fermion_operator(FermionOperator): The operator to convert. n_qubits(int): Optionally specify the total number of qubits in the system ignore_incompatible_terms(bool): This flag determines the behavior of this method when it encounters terms that are not represented by the DiagonalCoulombHamiltonian class, namely, terms that are not quadratic and not quartic of the form a^\dagger_p a_p a^\dagger_q a_q. If set to True, this method will simply ignore those terms. If False, then this method will raise an error if it encounters such a term. The default setting is False. """ 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.') 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), float) for term, coefficient in fermion_operator.terms.items(): # Ignore this term if the coefficient is zero if abs(coefficient) < EQ_TOLERANCE: continue if len(term) == 0: constant = coefficient else: actions = [operator[1] for operator in term] if actions == [1, 0]: p, q = [operator[0] for operator in term] one_body[p, q] = coefficient elif actions == [1, 1, 0, 0]: p, q, r, s = [operator[0] for operator in term] if p == r and q == s: if abs(numpy.imag(coefficient)) > EQ_TOLERANCE: raise ValueError( 'FermionOperator does not map to ' 'DiagonalCoulombHamiltonian (not Hermitian).') coefficient = numpy.real(coefficient) two_body[p, q] = -.5 * coefficient two_body[q, p] = -.5 * coefficient elif not ignore_incompatible_terms: raise ValueError('FermionOperator does not map to ' 'DiagonalCoulombHamiltonian ' '(contains terms with indices ' '{}).'.format((p, q, r, s))) elif not ignore_incompatible_terms: raise ValueError('FermionOperator does not map to ' 'DiagonalCoulombHamiltonian (contains terms ' 'with action {}.'.format(tuple(actions))) # Check that the operator is Hermitian if not is_hermitian(one_body): raise ValueError( 'FermionOperator does not map to DiagonalCoulombHamiltonian ' '(not Hermitian).') return DiagonalCoulombHamiltonian(one_body, two_body, constant)
def save(self): """Method to save the class under a systematic name.""" self.get_energies() self.get_integrals_FCIDUMP() self.molecular_hamiltonian, self.one_body_coeff, self.two_body_coeff = self.get_molecular_hamiltonian( ) self.n_qubits = count_qubits(self.molecular_hamiltonian) self.n_orbitals = len(self.spinor) tmp_name = uuid.uuid4() with h5py.File("{}.hdf5".format(tmp_name), "w") as f: # Save geometry: d_geom = f.create_group("geometry") if not isinstance(self.geometry, basestring): atoms = [numpy.string_(item[0]) for item in self.geometry] positions = numpy.array( [list(item[1]) for item in self.geometry]) else: atoms = numpy.string_(self.geometry) positions = None d_geom.create_dataset("atoms", data=(atoms if atoms is not None else False)) d_geom.create_dataset( "positions", data=(positions if positions is not None else False)) # Save basis: f.create_dataset("basis", data=numpy.string_(self.basis)) # Save multiplicity: f.create_dataset("multiplicity", data=self.multiplicity) # Save charge: f.create_dataset("charge", data=self.charge) # Save description: f.create_dataset("description", data=numpy.string_(self.description)) # Save name: f.create_dataset("name", data=numpy.string_(self.name)) # Save n_atoms: f.create_dataset("n_atoms", data=self.n_atoms) # Save atoms: f.create_dataset("atoms", data=numpy.string_(self.atoms)) # Save protons: f.create_dataset("protons", data=self.protons) # Save n_electrons: f.create_dataset("n_electrons", data=self.n_electrons) # Save generic attributes from calculations: f.create_dataset("n_orbitals", data=(self.n_orbitals if self.n_orbitals is not None else False)) f.create_dataset( "n_qubits", data=(self.n_qubits if self.n_qubits is not None else False)) f.create_dataset( "nuclear_repulsion", data=(self.E_core if self.E_core is not None else False)) # Save attributes generated from SCF calculation. f.create_dataset( "hf_energy", data=(self.hf_energy if self.hf_energy is not None else False)) f.create_dataset( "orbital_energies", data=(str(self.spinor) if self.spinor is not None else False)) # Save attributes generated from integrals. f.create_dataset("one_body_integrals", data=(str(self.one_body_int) if self.one_body_int is not None else False)) f.create_dataset("two_body_integrals", data=(str(self.two_body_int) if self.two_body_int is not None else False)) f.create_dataset("one_body_coefficients", data=(self.one_body_coeff if self.one_body_coeff is not None else False)) f.create_dataset("two_body_coefficients", data=(self.two_body_coeff if self.two_body_coeff is not None else False)) f.create_dataset( "print_molecular_hamiltonian", data=(str(self.molecular_hamiltonian) if self.molecular_hamiltonian is not None else False)) # Save attributes generated from MP2 calculation. f.create_dataset("mp2_energy", data=(self.mp2_energy if self.mp2_energy is not None else False)) # Save attributes generated from CCSD calculation. f.create_dataset("ccsd_energy", data=(self.ccsd_energy if self.ccsd_energy is not None else False)) # Remove old file first for compatibility with systems that don't allow # rename replacement. Catching OSError for when file does not exist # yet try: os.remove("{}.hdf5".format(self.filename)) except OSError: pass os.rename("{}.hdf5".format(tmp_name), "{}.hdf5".format(self.filename))
def hartree_fock_state_jellium(grid, n_electrons, spinless=True, plane_wave=False): """Give the Hartree-Fock state of jellium. Args: grid (Grid): The discretization to use. n_electrons (int): Number of electrons in the system. spinless (bool): Whether to use the spinless model or not. plane_wave (bool): Whether to return the Hartree-Fock state in the plane wave (True) or dual basis (False). Notes: The jellium model is built up by filling the lowest-energy single-particle states in the plane-wave Hamiltonian until n_electrons states are filled. """ from openfermion.hamiltonians import plane_wave_kinetic # Get the jellium Hamiltonian in the plane wave basis. # For determining the Hartree-Fock state in the PW basis, only the # kinetic energy terms matter. hamiltonian = plane_wave_kinetic(grid, spinless=spinless) hamiltonian = normal_ordered(hamiltonian) hamiltonian.compress() # The number of occupied single-particle states is the number of electrons. # Those states with the lowest single-particle energies are occupied first. occupied_states = lowest_single_particle_energy_states( hamiltonian, n_electrons) occupied_states = numpy.array(occupied_states) if plane_wave: # In the plane wave basis the HF state is a single determinant. hartree_fock_state_index = numpy.sum(2 ** occupied_states) hartree_fock_state = csr_matrix( ([1.0], ([hartree_fock_state_index], [0])), shape=(2 ** count_qubits(hamiltonian), 1)) else: # Inverse Fourier transform the creation operators for the state to get # to the dual basis state, then use that to get the dual basis state. hartree_fock_state_creation_operator = FermionOperator.identity() for state in occupied_states[::-1]: hartree_fock_state_creation_operator *= ( FermionOperator(((int(state), 1),))) dual_basis_hf_creation_operator = inverse_fourier_transform( hartree_fock_state_creation_operator, grid, spinless) dual_basis_hf_creation = normal_ordered( dual_basis_hf_creation_operator) # Initialize the HF state as a sparse matrix. hartree_fock_state = dok_matrix( (2 ** count_qubits(hamiltonian), 1), dtype=complex) # Populate the elements of the HF state in the dual basis. for term in dual_basis_hf_creation.terms: index = 0 for operator in term: index += 2 ** operator[0] hartree_fock_state[index, 0] = dual_basis_hf_creation.terms[term] hartree_fock_state = hartree_fock_state.tocsr() return hartree_fock_state
def simulation_ordered_grouped_low_depth_terms_with_info( hamiltonian, input_ordering=None, external_potential_at_end=False): """Give terms from the dual basis Hamiltonian in simulated order. Uses the simulation ordering, grouping terms into hopping (i^ j + j^ i) and number (i^j^ i j + c_i i^ i + c_j j^ j) operators. Pre-computes term information (indices each operator acts on, as well as whether each operator is a hopping operator. Args: hamiltonian (FermionOperator): The Hamiltonian. input_ordering (list): The initial Jordan-Wigner canonical order. If no input ordering is specified, defaults to [0..n_qubits] where n_qubits is the number of qubits in the Hamiltonian. external_potential_at_end (bool): Whether to include the rotations from the external potential at the end of the Trotter step, or intersperse them throughout it. Returns: A 3-tuple of terms from the Hamiltonian in order of simulation, the indices they act on, and whether they are hopping operators (both also in the same order). Notes: Follows the "stagger"-based simulation order discussed in Kivlichan et al., "Quantum Simulation of Electronic Structure with Linear Depth and Connectivity", arxiv:1711.04789; as such, the only permitted types of terms are hopping (i^ j + j^ i) and potential terms which are products of at most two number operators. """ n_qubits = count_qubits(hamiltonian) hamiltonian = normal_ordered(hamiltonian) ordered_terms = [] ordered_indices = [] ordered_is_hopping_operator = [] # If no input mode ordering is specified, default to range(n_qubits). try: input_ordering = list(input_ordering) except TypeError: input_ordering = list(range(n_qubits)) # Half a second-order Trotter step reverses the input ordering: this tells # us how much we need to include in the ordered list of terms. final_ordering = list(reversed(input_ordering)) # Follow odd-even transposition sort. In alternating steps, swap each even # qubits with the odd qubit to its right, and in the next step swap each # the odd qubits with the even qubit to its right. Do this until the input # ordering has been reversed. parity = 0 while input_ordering != final_ordering: results = stagger_with_info(hamiltonian, input_ordering, parity, external_potential_at_end) terms_in_layer, indices_in_layer, is_hopping_operator_in_layer = ( results) ordered_terms.extend(terms_in_layer) ordered_indices.extend(indices_in_layer) ordered_is_hopping_operator.extend(is_hopping_operator_in_layer) # Alternate even and odd steps of the reversal procedure. parity = 1 - parity # If all the rotations from the external potential are in the final layer, # i.e. we don't intersperse them throughout the Trotter step. if external_potential_at_end: terms_in_final_layer = [] indices_in_final_layer = [] is_hopping_operator_in_final_layer = [] for qubit in range(n_qubits): coeff = hamiltonian.terms.get(((qubit, 1), (qubit, 0)), 0.0) if coeff: terms_in_final_layer.append( FermionOperator(((qubit, 1), (qubit, 0)), coeff)) indices_in_final_layer.append(set((qubit, ))) is_hopping_operator_in_final_layer.append(False) ordered_terms.extend(terms_in_final_layer) ordered_indices.extend(indices_in_final_layer) ordered_is_hopping_operator.extend(is_hopping_operator_in_final_layer) return (ordered_terms, ordered_indices, ordered_is_hopping_operator)
def taper_off_qubits(operator, stabilizers, manual_input=False, fixed_positions=None, output_tapered_positions=False): r""" Remove qubits from given operator. Qubits are removed by eliminating an equivalent number of stabilizer conditions. Which qubits that are can either be determined automatically or their positions can be set manually. Qubits can be disregarded from the Hamiltonian when the effect of all its terms on them is rendered trivial. This algorithm employs a stabilizers like :math:`\pm X \otimes p` to fix the action of every Pauli string on the first qubit to :math:`Z` or the identity. A string :math:`X \otimes h` would for instance be multiplied with the stabilizer to obtain :math:`1 \otimes (\pm h\cdot p)` while a string :math:`Z \otimes h^\prime` would pass without correction. The first qubit can subsequently be removed as it must be in the computational basis in Hamiltonian eigenstates. For stabilizers acting as :math:`Y` (:math:`Z`) on selected qubits, the algorithm would fix the action of every Hamiltonian string to :math:`Z` (:math:`X`). Updating also the list of remaining stabilizer generators, the algorithm is run iteratively. Args: operator (QubitOperator): Operator of which qubits will be removed. stabilizers (QubitOperator): Stabilizer generators for the tapering. Can also be passed as a list of QubitOperator. manual_input (Boolean): Option to pass the list of fixed qubits positions manually. Set to False by default. fixed_positions (list): (optional) List of fixed qubit positions. Passing a list is only effective if manual_input is True. output_tapered_positions (Boolean): Option to output the positions of qubits that have been removed. Returns: skimmed_operator (QubitOperator): Operator with fewer qubits. removed_positions (list): (optional) List of removed qubit positions. For the qubits to be gone in the qubit count, the remaining qubits have been moved up to those indices. """ n_qbits = count_qubits(operator) (ham_to_update, qbts_to_rm) = reduce_number_of_terms(operator, stabilizers, maintain_length=False, manual_input=manual_input, fixed_positions=fixed_positions, output_fixed_positions=True) # Gets a list of the order of the qubits after tapering qbit_order = list(numpy.arange(n_qbits - len(qbts_to_rm), dtype=int)) # Save the original list before it gets ordered removed_positions = qbts_to_rm qbts_to_rm.sort() for x in qbts_to_rm: qbit_order.insert(x, 'remove') # Remove the qubits skimmed_operator = QubitOperator() for term, coef in ham_to_update.terms.items(): if term == (): skimmed_operator += QubitOperator('', coef) continue tap_tpls = [] for p in term: if qbit_order[p[0]] != 'remove': tap_tpls.append((qbit_order[p[0]].item(), p[1])) skimmed_operator += QubitOperator(tuple(tap_tpls), coef) if output_tapered_positions: return skimmed_operator, removed_positions else: return skimmed_operator
from openfermion.ops import QubitOperator as QOp from openfermion.utils import count_qubits @qjit def state_prep(q: qreg): X(q[0]) qsearch_optimizer = createTransformation("qsearch") observable = QOp('', 5.907) + QOp('Y0 Y1', -2.1433) + \ QOp('X0 X1', -2.1433) + QOp('Z0', .21829) + QOp('Z1', -6.125) n_steps = 3 step_size = 0.1 n_qubits = count_qubits(observable) problemModel = QuaSiMo.ModelFactory.createModel(state_prep, observable, n_qubits) workflow = QuaSiMo.getWorkflow( "qite", { "steps": n_steps, "step-size": step_size, "circuit-optimizer": qsearch_optimizer }) set_verbose(True) result = workflow.execute(problemModel) energy = result["energy"] finalCircuit = result["circuit"]
def test_ucc_h2_singlet(self): 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') self.molecule = MolecularData(geometry, basis, multiplicity, filename=filename) self.molecule.load() # Get molecular Hamiltonian. self.molecular_hamiltonian = self.molecule.get_molecular_hamiltonian() # Get FCI RDM. self.fci_rdm = self.molecule.get_molecular_rdm(use_fci=1) # Get explicit coefficients. self.nuclear_repulsion = self.molecular_hamiltonian.constant self.one_body = self.molecular_hamiltonian.one_body_tensor self.two_body = self.molecular_hamiltonian.two_body_tensor # Get fermion Hamiltonian. self.fermion_hamiltonian = normal_ordered( get_fermion_operator(self.molecular_hamiltonian)) # Get qubit Hamiltonian. self.qubit_hamiltonian = jordan_wigner(self.fermion_hamiltonian) # Get the sparse matrix. self.hamiltonian_matrix = get_sparse_operator( self.molecular_hamiltonian) # Test UCCSD for accuracy against FCI using loaded t amplitudes. ucc_operator = uccsd_generator(self.molecule.ccsd_single_amps, self.molecule.ccsd_double_amps) hf_state = jw_hartree_fock_state(self.molecule.n_electrons, count_qubits(self.qubit_hamiltonian)) uccsd_sparse = jordan_wigner_sparse(ucc_operator) uccsd_state = scipy.sparse.linalg.expm_multiply(uccsd_sparse, hf_state) expected_uccsd_energy = expectation(self.hamiltonian_matrix, uccsd_state) self.assertAlmostEqual(expected_uccsd_energy, self.molecule.fci_energy, places=4) print("UCCSD ENERGY: {}".format(expected_uccsd_energy)) # Test CCSD singlet for precise match against FCI using loaded t # amplitudes packed_amplitudes = uccsd_singlet_get_packed_amplitudes( self.molecule.ccsd_single_amps, self.molecule.ccsd_double_amps, self.molecule.n_qubits, self.molecule.n_electrons) ccsd_operator = uccsd_singlet_generator(packed_amplitudes, self.molecule.n_qubits, self.molecule.n_electrons, anti_hermitian=False) ccsd_sparse_r = jordan_wigner_sparse(ccsd_operator) ccsd_sparse_l = jordan_wigner_sparse( -hermitian_conjugated(ccsd_operator)) ccsd_state_r = scipy.sparse.linalg.expm_multiply( ccsd_sparse_r, hf_state) ccsd_state_l = scipy.sparse.linalg.expm_multiply( ccsd_sparse_l, hf_state) expected_ccsd_energy = ccsd_state_l.conjugate().dot( self.hamiltonian_matrix.dot(ccsd_state_r)) self.assertAlmostEqual(expected_ccsd_energy, self.molecule.fci_energy)
def XBK(qubit_Hs, qubit_Cs, r, sampler, starting_lam=0, num_samples=1000, strength=1e3, verbose=False): n = count_qubits(qubit_Hs[0]) min_energies, ground_states = [], [] for p in range(int(math.ceil(r / 2 + 1))): qubit_H, qubit_C = qubit_Hs[p], qubit_Cs[p] #create C function to evalute sum(b^2) C_dict = convert_dict(qubit_C.terms) C_func = dict_to_func(C_dict) #calculate minimum energy for particular p value lam = starting_lam eigenvalue = min_energy = -1 ground_state = [] cycles = 0 while min_energy < 0 and cycles < 10: #subtract lambda C from H H_prime = qubit_H - lam * qubit_C #construct qubo from reduced Hamiltonian bqm = dimod.higherorder.utils.make_quadratic( convert_dict(H_prime.terms), strength, dimod.SPIN) qubo, constant = bqm.to_qubo() if qubo == {}: break #run sampler response = sampler.sample_qubo(qubo, num_reads=num_samples) solutions = pd.DataFrame(response.data()) #get mininum energy solution index = int(solutions[['energy']].idxmin()) min_energy = round(solutions['energy'][index], 14) + constant full_solution = solutions['sample'][index] solution = [] for key in ['s' + str(i) for i in range(n)]: try: solution += [2 * full_solution[key] - 1] except KeyError: solution += [0] print('KeyError: ' + str(key)) #calculate sum(b^2) try: sumBsq = int(C_func(*solution)) except TypeError: sumBsq = int(C_func) if sumBsq == 0: #stop the loop in zero case cycles += 1 break #calculate the eigenvalue of H eigenvalue = lam + min_energy / sumBsq #set lam equal to eigenvalue for next cycle if min_energy < 0: lam = eigenvalue ground_state = [(val + 1) // 2 for val in solution] cycles += 1 min_energies += [round(lam, 14)] ground_states += [ground_state] if verbose: print('P:', p, 'E:', round(lam, 5)) index = min_energies.index(min(min_energies)) min_energy = min_energies[index] ground_state = ground_states[index] if verbose: print('Energy:', round(min_energy, 5)) return min_energy, ground_state
def qubit_operator_sparse(qubit_operator, n_qubits=None): """Initialize a Scipy sparse matrix from a QubitOperator. Args: qubit_operator(QubitOperator): instance of the QubitOperator class. n_qubits (int): Number of qubits. Returns: The corresponding Scipy sparse matrix. """ if n_qubits is None: n_qubits = count_qubits(qubit_operator) if n_qubits < count_qubits(qubit_operator): raise ValueError('Invalid number of qubits specified.') # Construct the Scipy sparse matrix. n_hilbert = 2 ** n_qubits values_list = [[]] row_list = [[]] column_list = [[]] # Loop through the terms. for qubit_term in qubit_operator.terms: tensor_factor = 0 coefficient = qubit_operator.terms[qubit_term] sparse_operators = [coefficient] for pauli_operator in qubit_term: # Grow space for missing identity operators. if pauli_operator[0] > tensor_factor: identity_qubits = pauli_operator[0] - tensor_factor identity = scipy.sparse.identity( 2 ** identity_qubits, dtype=complex, format='csc') sparse_operators += [identity] # Add actual operator to the list. sparse_operators += [pauli_matrix_map[pauli_operator[1]]] tensor_factor = pauli_operator[0] + 1 # Grow space at end of string unless operator acted on final qubit. if tensor_factor < n_qubits or not qubit_term: identity_qubits = n_qubits - tensor_factor identity = scipy.sparse.identity( 2 ** identity_qubits, dtype=complex, format='csc') sparse_operators += [identity] # Extract triplets from sparse_term. sparse_matrix = kronecker_operators(sparse_operators) values_list.append(sparse_matrix.tocoo(copy=False).data) (column, row) = sparse_matrix.nonzero() column_list.append(column) row_list.append(row) # Create sparse operator. values_list = numpy.concatenate(values_list) row_list = numpy.concatenate(row_list) column_list = numpy.concatenate(column_list) sparse_operator = scipy.sparse.coo_matrix(( values_list, (row_list, column_list)), shape=(n_hilbert, n_hilbert)).tocsc(copy=False) sparse_operator.eliminate_zeros() return sparse_operator
def XBK_transform(op, r, p): n = count_qubits(op) op_terms = op.terms new_op = QubitOperator() #transform operator term by term for key in op_terms: coeff = op_terms[key] term = QubitOperator() #cycle through each of the r ancillarly qubit copies for j in range(r): for k in range(r): sign = 1 if (j < p) == (k < p) else -1 sub_term = QubitOperator('', 1) #cycle through each of the n original qubits spot = 0 for i in range(n): try: if key[spot][0] == i: char = key[spot][1] spot += 1 else: char = 'I' except IndexError: char = 'I' #use variable type to apply correct mapping if char == 'X': if j == k: sub_term = QubitOperator('', 0) break else: sub_term *= QubitOperator( '', 1 / 2) - QubitOperator( 'Z' + str(i + n * j) + ' Z' + str(i + n * k), 1 / 2) elif char == 'Y': if j == k: sub_term = QubitOperator('', 0) break else: sub_term *= QubitOperator( 'Z' + str(i + n * k), 1j / 2) - QubitOperator( 'Z' + str(i + n * j), 1j / 2) elif char == 'Z': if j == k: sub_term *= QubitOperator('Z' + str(i + n * j), 1) else: sub_term *= QubitOperator( 'Z' + str(i + n * j), 1 / 2) + QubitOperator( 'Z' + str(i + n * k), 1 / 2) else: if j == k: continue else: sub_term *= QubitOperator( '', 1 / 2) + QubitOperator( 'Z' + str(i + n * j) + ' Z' + str(i + n * k), 1 / 2) term += sign * sub_term new_op += coeff * term new_op.compress() return new_op
def bravyi_kitaev_fast_edge_matrix(iop, n_qubits=None): """ Use InteractionOperator to construct edge matrix required for the algorithm Edge matrix contains the information about the edges between vertices. Edge matrix is required to build the operators in bravyi_kitaev_fast model. Args: iop (Interaction Operator): Returns: edge_matrix (Numpy array): A square numpy array containing information about the edges present in the model. """ n_qubits = count_qubits(iop) edge_matrix = 1j*numpy.zeros((n_qubits, n_qubits)) # Loop through all indices. for p in range(n_qubits): for q in range(n_qubits): # Handle one-body terms. coefficient = complex(iop[p, q]) if coefficient and p >= q: edge_matrix[p, q] = bool(complex(iop[p, q])) # Keep looping for the two-body terms. for r in range(n_qubits): for s in range(n_qubits): coefficient2 = complex(iop[p, q, r, s]) # Skip zero terms. if (not coefficient2) or (p == q) or (r == s): continue # Identify and skip one of the complex conjugates. if [p, q, r, s] != [s, r, q, p]: if len(set([p, q, r, s])) == 4: if min(r, s) < min(p, q): continue elif p != r and q < p: continue # Handle case of four unique indices. if len(set([p, q, r, s])) == 4: if coefficient2 and p >= q: edge_matrix[p, q] = bool(complex(iop[p, q, r, s])) a, b = sorted([r, s]) edge_matrix[b, a] = bool(complex(iop[p, q, r, s])) # Handle case of three unique indices. elif len(set([p, q, r, s])) == 3: # Identify equal tensor factors. if p == r: a, b = sorted([q, s]) edge_matrix[b, a] = bool(complex(iop[p, q, r, s])) elif p == s: a, b = sorted([q, r]) edge_matrix[b, a] = bool(complex(iop[p, q, r, s])) elif q == r: a, b = sorted([p, s]) edge_matrix[b, a] = bool(complex(iop[p, q, r, s])) elif q == s: a, b = sorted([p, r]) edge_matrix[b, a] = bool(complex(iop[p, q, r, s])) # Handle case of two unique indices. elif len(set([p, q, r, s])) == 2: if p == s: a, b = sorted([p, q]) edge_matrix[b, a] = bool(complex(iop[p, q, r, s])) else: a, b = sorted([p, q]) edge_matrix[b, a] = bool(complex(iop[p, q, r, s])) return edge_matrix.transpose()
def simulation_ordered_grouped_hubbard_terms_with_info(hubbard_hamiltonian): """Give terms from the Fermi-Hubbard Hamiltonian in simulated order. Uses the simulation ordering, grouping terms into hopping (i^ j + j^ i) and on-site potential (i^j^ i j) operators. Pre-computes term information (indices each operator acts on, as well as whether each operator is a hopping operator). Args: hubbard_hamiltonian (FermionOperator): The Hamiltonian. original_ordering (list): The initial Jordan-Wigner canonical order. Returns: A 3-tuple of terms from the Hubbard Hamiltonian in order of simulation, the indices they act on, and whether they are hopping operators (both also in the same order). Notes: Assumes that the Hubbard model has spin and is on a 2D square aperiodic lattice. Uses the "stagger"-based Trotter step for the Hubbard model detailed in Kivlichan et al., "Quantum Simulation of Electronic Structure with Linear Depth and Connectivity", arxiv:1711.04789. """ hamiltonian = normal_ordered(hubbard_hamiltonian) n_qubits = count_qubits(hamiltonian) side_length = int(numpy.sqrt(n_qubits / 2.0)) ordered_terms = [] ordered_indices = [] ordered_is_hopping_operator = [] original_ordering = list(range(n_qubits)) for i in range(0, n_qubits - side_length, 2 * side_length): for j in range(2 * bool(i % (4 * side_length)), 2 * side_length, 4): original_ordering[i + j], original_ordering[i + j + 1] = ( original_ordering[i + j + 1], original_ordering[i + j]) input_ordering = list(original_ordering) # Follow odd-even transposition sort. In alternating steps, swap each even # qubits with the odd qubit to its right, and in the next step swap each # the odd qubits with the even qubit to its right. Do this until the input # ordering has been reversed. parities = [False, True] * int(side_length / 2 + 1) # int floors here for parity in parities: results = stagger_with_info(hubbard_hamiltonian, input_ordering, parity) terms_in_step, indices_in_step, is_hopping_operator_in_step = results ordered_terms.extend(terms_in_step) ordered_indices.extend(indices_in_step) ordered_is_hopping_operator.extend(is_hopping_operator_in_step) input_ordering = list(original_ordering) parities = [True] + [False, True] * int(side_length / 2) # int floors here for parity in parities: results = stagger_with_info(hubbard_hamiltonian, input_ordering, parity) terms_in_step, indices_in_step, is_hopping_operator_in_step = results ordered_terms.extend(terms_in_step) ordered_indices.extend(indices_in_step) ordered_is_hopping_operator.extend(is_hopping_operator_in_step) return (ordered_terms, ordered_indices, ordered_is_hopping_operator)
def get_quadratic_hamiltonian(fermion_operator, chemical_potential=0., n_qubits=None): """Convert a quadratic fermionic operator to QuadraticHamiltonian. This function should only be called on fermionic operators which consist of only a_p^\dagger a_q, a_p^\dagger a_q^\dagger, and a_p a_q terms. Returns: quadratic_hamiltonian: An instance of the QuadraticHamiltonian class. Raises: TypeError: Input must be a FermionOperator. TypeError: FermionOperator does not map to QuadraticHamiltonian. 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.') 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) constant = 0. combined_hermitian_part = numpy.zeros((n_qubits, n_qubits), complex) antisymmetric_part = numpy.zeros((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: continue if len(term) == 0: # Constant term constant = coefficient elif len(term) == 2: ladder_type = [operator[1] for operator in term] p, q = [operator[0] for operator in term] if ladder_type == [1, 0]: combined_hermitian_part[p, q] = coefficient elif ladder_type == [1, 1]: # Need to check that the corresponding [0, 0] term is present conjugate_term = ((p, 0), (q, 0)) if conjugate_term not in fermion_operator.terms: raise QuadraticHamiltonianError( 'FermionOperator does not map ' 'to QuadraticHamiltonian (not Hermitian).') else: matching_coefficient = -fermion_operator.terms[ conjugate_term].conjugate() discrepancy = abs(coefficient - matching_coefficient) if discrepancy > EQ_TOLERANCE: raise QuadraticHamiltonianError( 'FermionOperator does not map ' 'to QuadraticHamiltonian (not Hermitian).') antisymmetric_part[p, q] += .5 * coefficient antisymmetric_part[q, p] -= .5 * coefficient else: # ladder_type == [0, 0] # Need to check that the corresponding [1, 1] term is present conjugate_term = ((p, 1), (q, 1)) if conjugate_term not in fermion_operator.terms: raise QuadraticHamiltonianError( 'FermionOperator does not map ' 'to QuadraticHamiltonian (not Hermitian).') else: matching_coefficient = -fermion_operator.terms[ conjugate_term].conjugate() discrepancy = abs(coefficient - matching_coefficient) if discrepancy > EQ_TOLERANCE: raise QuadraticHamiltonianError( 'FermionOperator does not map ' 'to QuadraticHamiltonian (not Hermitian).') antisymmetric_part[p, q] -= .5 * coefficient.conjugate() antisymmetric_part[q, p] += .5 * coefficient.conjugate() else: # Operator contains non-quadratic terms raise QuadraticHamiltonianError('FermionOperator does not map ' 'to QuadraticHamiltonian ' '(contains non-quadratic terms).') # Compute Hermitian part hermitian_part = (combined_hermitian_part + chemical_potential * numpy.eye(n_qubits)) # Check that the operator is Hermitian difference = hermitian_part - hermitian_part.T.conj() discrepancy = numpy.max(numpy.abs(difference)) if discrepancy > EQ_TOLERANCE: raise QuadraticHamiltonianError( 'FermionOperator does not map ' 'to QuadraticHamiltonian (not Hermitian).') # Form QuadraticHamiltonian and return. discrepancy = numpy.max(numpy.abs(antisymmetric_part)) if discrepancy < EQ_TOLERANCE: # Hamiltonian conserves particle number quadratic_hamiltonian = QuadraticHamiltonian( constant, hermitian_part, chemical_potential=chemical_potential) else: # Hamiltonian does not conserve particle number quadratic_hamiltonian = QuadraticHamiltonian(constant, hermitian_part, antisymmetric_part, chemical_potential) return quadratic_hamiltonian
def get_quadratic_hamiltonian(fermion_operator, chemical_potential=0., n_qubits=None, ignore_incompatible_terms=False): r"""Convert a quadratic fermionic operator to QuadraticHamiltonian. Args: fermion_operator(FermionOperator): The operator to convert. chemical_potential(float): A chemical potential to include in the returned operator n_qubits(int): Optionally specify the total number of qubits in the system ignore_incompatible_terms(bool): This flag determines the behavior of this method when it encounters terms that are not quadratic that is, terms that are not of the form a^\dagger_p a_q. If set to True, this method will simply ignore those terms. If False, then this method will raise an error if it encounters such a term. The default setting is False. Returns: quadratic_hamiltonian: An instance of the QuadraticHamiltonian class. Raises: TypeError: Input must be a FermionOperator. TypeError: FermionOperator does not map to QuadraticHamiltonian. 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.') 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) constant = 0. combined_hermitian_part = numpy.zeros((n_qubits, n_qubits), complex) antisymmetric_part = numpy.zeros((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: continue if len(term) == 0: # Constant term constant = coefficient elif len(term) == 2: ladder_type = [operator[1] for operator in term] p, q = [operator[0] for operator in term] if ladder_type == [1, 0]: combined_hermitian_part[p, q] = coefficient elif ladder_type == [1, 1]: # Need to check that the corresponding [0, 0] term is present conjugate_term = ((p, 0), (q, 0)) if conjugate_term not in fermion_operator.terms: raise QuadraticHamiltonianError( 'FermionOperator does not map ' 'to QuadraticHamiltonian (not Hermitian).') else: matching_coefficient = -fermion_operator.terms[ conjugate_term].conjugate() discrepancy = abs(coefficient - matching_coefficient) if discrepancy > EQ_TOLERANCE: raise QuadraticHamiltonianError( 'FermionOperator does not map ' 'to QuadraticHamiltonian (not Hermitian).') antisymmetric_part[p, q] += .5 * coefficient antisymmetric_part[q, p] -= .5 * coefficient else: # ladder_type == [0, 0] # Need to check that the corresponding [1, 1] term is present conjugate_term = ((p, 1), (q, 1)) if conjugate_term not in fermion_operator.terms: raise QuadraticHamiltonianError( 'FermionOperator does not map ' 'to QuadraticHamiltonian (not Hermitian).') else: matching_coefficient = -fermion_operator.terms[ conjugate_term].conjugate() discrepancy = abs(coefficient - matching_coefficient) if discrepancy > EQ_TOLERANCE: raise QuadraticHamiltonianError( 'FermionOperator does not map ' 'to QuadraticHamiltonian (not Hermitian).') antisymmetric_part[p, q] -= .5 * coefficient.conjugate() antisymmetric_part[q, p] += .5 * coefficient.conjugate() elif not ignore_incompatible_terms: # Operator contains non-quadratic terms raise QuadraticHamiltonianError('FermionOperator does not map ' 'to QuadraticHamiltonian ' '(contains non-quadratic terms).') # Compute Hermitian part hermitian_part = (combined_hermitian_part + chemical_potential * numpy.eye(n_qubits)) # Check that the operator is Hermitian if not is_hermitian(hermitian_part): raise QuadraticHamiltonianError( 'FermionOperator does not map ' 'to QuadraticHamiltonian (not Hermitian).') # Form QuadraticHamiltonian and return. discrepancy = numpy.max(numpy.abs(antisymmetric_part)) if discrepancy < EQ_TOLERANCE: # Hamiltonian conserves particle number quadratic_hamiltonian = QuadraticHamiltonian( hermitian_part, constant=constant, chemical_potential=chemical_potential) else: # Hamiltonian does not conserve particle number quadratic_hamiltonian = QuadraticHamiltonian(hermitian_part, antisymmetric_part, constant, chemical_potential) return quadratic_hamiltonian
def pauli_exp_to_qasm(qubit_operator_list, evolution_time=1.0, qubit_list=None, ancilla=None): """Exponentiate a list of QubitOperators to a QASM string generator. Exponentiates a list of QubitOperators, and yields string generators in QASM format using the formula: exp(-1.0j * evolution_time * op). Args: qubit_operator_list (list of QubitOperators): list of single Pauli-term QubitOperators to be exponentiated evolution_time (float): evolution time of the operators in the list qubit_list: (list/tuple or None)Specifies the labels for the qubits to be output in qasm. If a list/tuple, must have length greater than or equal to the number of qubits in the QubitOperator. Entries in the list must be castable to string. If None, qubits are labeled by index (i.e. an integer). ancilla (string or None): if any, an ancilla qubit to perform the rotation conditional on (for quantum phase estimation) Yields: string """ num_qubits = max([count_qubits(qubit_operator) for qubit_operator in qubit_operator_list]) if qubit_list is None: qubit_list = list(range(num_qubits)) else: if type(qubit_list) is not tuple and type(qubit_list) is not list: raise TypeError('qubit_list must be one of None, tuple, or list.') if len(qubit_list) < num_qubits: raise TypeError('qubit_list must have an entry for every qubit') for qubit_operator in qubit_operator_list: # ret_val = "" ret_list = [] for term in qubit_operator.terms: term_coeff = qubit_operator.terms[term] # Force float term_coeff = float(numpy.real(term_coeff)) # List of operators and list of qubit ids ops = [] qids = [] string_basis_1 = [] # Basis rotations 1 string_basis_2 = [] # Basis rotations 2 for p in term: # p = single pauli term qid = qubit_list[p[0]] pop = p[1] # Pauli op qids.append(qid) # Qubit index ops.append(pop) # Pauli operator if pop == 'X': string_basis_1.append("H {}".format(qid)) # Hadamard string_basis_2.append("H {}".format(qid)) # Hadamard elif pop == 'Y': string_basis_1.append( "Rx 1.5707963267948966 {}".format(qid)) string_basis_2.append( "Rx -1.5707963267948966 {}".format(qid)) # Prep for CNOTs cnot_pairs = numpy.vstack((qids[:-1], qids[1:])) cnots1 = [] cnots2 = [] for i in range(cnot_pairs.shape[1]): pair = cnot_pairs[:, i] cnots1.append("CNOT {} {}".format(pair[0], pair[1])) for i in numpy.arange(cnot_pairs.shape[1])[::-1]: pair = cnot_pairs[:, i] cnots2.append("CNOT {} {}".format(pair[0], pair[1])) # Exponentiating each Pauli string requires five parts # 1. Perform basis rotations ret_list = ret_list + string_basis_1 # 2. First set CNOTs ret_list = ret_list + cnots1 # 3. Rotation (Note kexp & Ntrot) if ancilla is not None: if len(qids) > 0: ret_list = ret_list + ["C-Phase {} {} {}".format( -2 * term_coeff * evolution_time, ancilla, qids[-1])] ret_list = ret_list + ["Rz {} {}".format( 1 * term_coeff * evolution_time, ancilla)] else: ret_list = ret_list + ["Rz {} {}".format( 1 * term_coeff*evolution_time, ancilla)] else: if len(qids) > 0: ret_list = ret_list + ["Rz {} {}".format( term_coeff * evolution_time, qids[-1])] # 4. Second set of CNOTs ret_list = ret_list + cnots2 # 5. Rotate back to Z basis ret_list = ret_list + string_basis_2 for gate in ret_list: yield gate
def number_of_qubits(self): """Returns number of qubits used for the ansatz circuit.""" return count_qubits( change_operator_type(self._cost_hamiltonian, QubitOperator))