def cost_circuit(gamma, n_qubits, ising, device): """ returns circuit for evolution with cost Hamiltonian """ # instantiate circuit object circ = Circuit() # get all non-zero entries (edges) from Ising matrix idx = ising.nonzero() edges = list(zip(idx[0], idx[1])) # apply ZZ gate for every edge (with corresponding interation strength) for qubit_pair in edges: # get interaction strength from Ising matrix int_strength = ising[qubit_pair[0], qubit_pair[1]] # for Rigetti we decompose ZZ using CNOT gates if device.name == 'Rigetti': gate = ZZgate(qubit_pair[0], qubit_pair[1], gamma * int_strength) circ.add(gate) # classical simulators and IonQ support ZZ gate else: gate = Circuit().zz(qubit_pair[0], qubit_pair[1], angle=2 * gamma * int_strength) circ.add(gate) return circ
def qft_no_swap(qubits): """ Subroutine of the QFT excluding the final SWAP gates, applied to the qubits argument. Returns the a circuit object. Args: qubits (int): The list of qubits on which to apply the QFT """ # On a single qubit, the QFT is just a Hadamard. if len(qubits) == 1: return Circuit().h(qubits) # For more than one qubit, we define the QFT recursively (as shown on the right half of the image above): else: qftcirc = Circuit() # First add a Hadamard gate qftcirc.h(qubits[0]) # Then apply the controlled rotations, with weights (angles) defined by the distance to the control qubit. for k, qubit in enumerate(qubits[1:]): qftcirc.cphaseshift(qubit, qubits[0], 2*math.pi/(2**(k+2))) # Now apply the above gates recursively to the rest of the qubits qftcirc.add(qft_no_swap(qubits[1:])) return qftcirc
def driver(beta, n_qubits): """ Returns circuit for driver Hamiltonian U(Hb, beta) """ # instantiate circuit object circ = Circuit() # apply parametrized rotation around x to every qubit for qubit in range(n_qubits): gate = Circuit().rx(qubit, 2 * beta) circ.add(gate) return circ
def qft_recursive(qubits): """ Construct a circuit object corresponding to the Quantum Fourier Transform (QFT) algorithm, applied to the argument qubits. Args: qubits (int): The list of qubits on which to apply the QFT """ qftcirc = Circuit() # First add the QFT subroutine above qftcirc.add(qft_no_swap(qubits)) # Then add SWAP gates to reverse the order of the qubits: for i in range(math.floor(len(qubits)/2)): qftcirc.swap(qubits[i], qubits[-i-1]) return qftcirc
def circuit(params, device, n_qubits, ising): """ function to return full QAOA circuit; depends on device as ZZ implementation depends on gate set of backend """ # initialize qaoa circuit with first Hadamard layer: for minimization start in |-> circ = Circuit() X_on_all = Circuit().x(range(0, n_qubits)) circ.add(X_on_all) H_on_all = Circuit().h(range(0, n_qubits)) circ.add(H_on_all) # setup two parameter families circuit_length = int(len(params) / 2) gammas = params[:circuit_length] betas = params[circuit_length:] # add QAOA circuit layer blocks for mm in range(circuit_length): circ.add(cost_circuit(gammas[mm], n_qubits, ising, device)) circ.add(driver(betas[mm], n_qubits)) return circ
def adjoint(self): """Generates a circuit object corresponding to the adjoint of a given circuit, in which the order of gates is reversed, and each gate is the adjoint (i.e., conjugate transpose) of the original. """ adjoint_circ = Circuit() # Loop through the instructions (gates) in the circuit: for instruction in self.instructions: # Save the operator name and target op_name = instruction.operator.name target = instruction.target angle = None # If the operator has an attribute called 'angle', save that too if hasattr(instruction.operator, "angle"): angle = instruction.operator.angle # To make use of native gates, we'll define the adjoint for each if op_name == "H": adjoint_gate = Circuit().h(target) elif op_name == "I": adjoint_gate = Circuit().i(target) elif op_name == "X": adjoint_gate = Circuit().x(target) elif op_name == "Y": adjoint_gate = Circuit().y(target) elif op_name == "Z": adjoint_gate = Circuit().z(target) elif op_name == "S": adjoint_gate = Circuit().si(target) elif op_name == "Si": adjoint_gate = Circuit().s(target) elif op_name == "T": adjoint_gate = Circuit().ti(target) elif op_name == "Ti": adjoint_gate = Circuit().t(target) elif op_name == "V": adjoint_gate = Circuit().vi(target) elif op_name == "Vi": adjoint_gate = Circuit().v(target) elif op_name == "Rx": adjoint_gate = Circuit().rx(target, -angle) elif op_name == "Ry": adjoint_gate = Circuit().ry(target, -angle) elif op_name == "Rz": adjoint_gate = Circuit().rz(target, -angle) elif op_name == "PhaseShift": adjoint_gate = Circuit().phaseshift(target, -angle) elif op_name == "CNot": adjoint_gate = Circuit().cnot(*target) elif op_name == "Swap": adjoint_gate = Circuit().swap(*target) elif op_name == "ISwap": adjoint_gate = Circuit().pswap(*target, -np.pi / 2) elif op_name == "PSwap": adjoint_gate = Circuit().pswap(*target, -angle) elif op_name == "XY": adjoint_gate = Circuit().xy(*target, -angle) elif op_name == "CPhaseShift": adjoint_gate = Circuit().cphaseshift(*target, -angle) elif op_name == "CPhaseShift00": adjoint_gate = Circuit().cphaseshift00(*target, -angle) elif op_name == "CPhaseShift01": adjoint_gate = Circuit().cphaseshift01(*target, -angle) elif op_name == "CPhaseShift10": adjoint_gate = Circuit().cphaseshift10(*target, -angle) elif op_name == "CY": adjoint_gate = Circuit().cy(*target) elif op_name == "CZ": adjoint_gate = Circuit().cz(*target) elif op_name == "XX": adjoint_gate = Circuit().xx(*target, -angle) elif op_name == "YY": adjoint_gate = Circuit().yy(*target, -angle) elif op_name == "ZZ": adjoint_gate = Circuit().zz(*target, -angle) elif op_name == "CCNot": adjoint_gate = Circuit().ccnot(*target) elif op_name == "CSwap": adjoint_gate = Circuit().cswap(*target) # If the gate is a custom unitary, we'll create a new custom unitary else: # Extract the transpose of the unitary matrix for the unitary gate adjoint_matrix = instruction.operator.to_matrix().T.conj() # Define a gate for which the unitary matrix is the adjoint found above. # Add an "H" to the display name. adjoint_gate = Circuit().unitary( matrix=adjoint_matrix, targets=instruction.target, display_name="".join(instruction.operator.ascii_symbols) + "H", ) # Add the new gate to the adjoint circuit. Note the order of operations here: # (AB)^H = B^H A^H, where H is adjoint, thus we prepend new gates, rather than append. adjoint_circ = adjoint_gate.add(adjoint_circ) return adjoint_circ