def _generate_circuit(self, params: Optional[np.ndarray] = None) -> Circuit: """Returns a parametrizable circuit represention of the ansatz. By convention the initial state is taken to be the |+..+> state and is evolved first under the cost Hamiltonian and then the mixer Hamiltonian. Args: params: parameters of the circuit. """ if params is not None: Warning( "This method retuns a parametrizable circuit, params will be ignored." ) circuit = Circuit() qubits = [ Qubit(qubit_index) for qubit_index in range(self.number_of_qubits) ] circuit.qubits = qubits # Prepare initial state circuit += create_layer_of_gates(self.number_of_qubits, "H") # Add time evolution layers pyquil_cost_hamiltonian = qubitop_to_pyquilpauli( change_operator_type(self._cost_hamiltonian, QubitOperator)) pyquil_mixer_hamiltonian = qubitop_to_pyquilpauli( self._mixer_hamiltonian) for i in range(self.number_of_layers): circuit += time_evolution(pyquil_cost_hamiltonian, sympy.Symbol(f"gamma_{i}")) circuit += time_evolution(pyquil_mixer_hamiltonian, sympy.Symbol(f"beta_{i}")) return circuit
def _generate_circuit(self, params: Optional[np.ndarray] = None) -> Circuit: """Returns a parametrizable circuit represention of the ansatz. By convention the initial state is taken to be the |+..+> state and is evolved first under the cost Hamiltonian and then the mixer Hamiltonian. Args: params: parameters of the circuit. """ if params is not None: Warning( "This method retuns a parametrizable circuit, params will be ignored." ) circuit = Circuit() # Prepare initial state circuit += create_layer_of_gates(self.number_of_qubits, H) # Add time evolution layers cost_circuit = time_evolution( change_operator_type(self._cost_hamiltonian, QubitOperator), sympy.Symbol(f"gamma"), ) mixer_circuit = time_evolution(self._mixer_hamiltonian, sympy.Symbol(f"beta")) for i in range(self.number_of_layers): circuit += cost_circuit.bind( {sympy.Symbol(f"gamma"): sympy.Symbol(f"gamma_{i}")}) circuit += mixer_circuit.bind( {sympy.Symbol(f"beta"): sympy.Symbol(f"beta_{i}")}) return circuit
def _generate_circuit(self, params: Optional[np.ndarray] = None) -> Circuit: """Returns a parametrizable circuit represention of the ansatz. Args: params: parameters of the circuit. """ if params is not None: Warning( "This method retuns a parametrizable circuit, params will be ignored." ) circuit = Circuit() # Prepare initial state circuit += create_layer_of_gates(self.number_of_qubits, RY, self._thetas) # Add time evolution layers cost_circuit = time_evolution( change_operator_type(self._cost_hamiltonian, QubitOperator), sympy.Symbol(f"gamma"), ) for i in range(self.number_of_layers): circuit += cost_circuit.bind( {sympy.Symbol(f"gamma"): sympy.Symbol(f"gamma_{i}")}) circuit += create_layer_of_gates(self.number_of_qubits, RY, -self._thetas) circuit += create_layer_of_gates( self.number_of_qubits, RZ, [-2 * sympy.Symbol(f"beta_{i}")] * self.number_of_qubits, ) circuit += create_layer_of_gates(self.number_of_qubits, RY, self._thetas) return circuit
def test_evolution_with_numerical_time_produces_correct_result( self, hamiltonian, time, order): expected_zquantum_circuit = _zquantum_exponentiate_hamiltonian( hamiltonian, time, order) reference_unitary = expected_zquantum_circuit.to_unitary() unitary = time_evolution(hamiltonian, time, trotter_order=order).to_unitary() assert compare_unitary(unitary, reference_unitary, tol=1e-10)
def test_evolution_with_numerical_time_produces_correct_result( self, hamiltonian, time, order ): cirq_qubits = cirq.LineQubit(0), cirq.LineQubit(1) expected_cirq_circuit = _cirq_exponentiate_hamiltonian( hamiltonian, cirq_qubits, time, order ) reference_unitary = cirq.unitary(expected_cirq_circuit) unitary = time_evolution(hamiltonian, time, trotter_order=order).to_unitary() assert compare_unitary(unitary, reference_unitary, tol=1e-10)
def build_qaoa_circuit(params, hamiltonians): """Generates a circuit for QAOA. This is not only for QAOA proposed by Farhi et al., but also general ansatz where alternating layers of time evolution under two different Hamiltonians H1 and H2 are considered. 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: zquantum.core.circuit.Circuit: the ansatz circuit """ 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) output = 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)] output.qubits = qubits for qubit_index in range(n_qubits): output.gates.append(Gate('H', (qubits[qubit_index], ))) # Add time evolution layers for i in range(params.shape[0]): hamiltonian_index = mod(i, len(hamiltonians)) current_hamiltonian = qubitop_to_pyquilpauli( hamiltonians[hamiltonian_index]) output += time_evolution(current_hamiltonian, params[i]) return output
def test_time_evolution_with_symbolic_time_produces_correct_unitary( self, hamiltonian, time_value, order): time_symbol = sympy.Symbol("t") symbols_map = {time_symbol: time_value} expected_zquantum_circuit = _zquantum_exponentiate_hamiltonian( hamiltonian, time_value, order) reference_unitary = expected_zquantum_circuit.to_unitary() unitary = (time_evolution( hamiltonian, time_symbol, trotter_order=order).bind(symbols_map).to_unitary()) assert compare_unitary(unitary, reference_unitary, tol=1e-10)
def test_time_evolution_with_symbolic_time_produces_correct_unitary( self, hamiltonian, time_value, order ): time_symbol = sympy.Symbol("t") symbols_map = [(time_symbol, time_value)] cirq_qubits = cirq.LineQubit(0), cirq.LineQubit(1) expected_cirq_circuit = _cirq_exponentiate_hamiltonian( hamiltonian, cirq_qubits, time_value, order ) reference_unitary = cirq.unitary(expected_cirq_circuit) unitary = ( time_evolution(hamiltonian, time_symbol, trotter_order=order) .evaluate(symbols_map) .to_unitary() ) assert compare_unitary(unitary, reference_unitary, tol=1e-10)
def _generate_circuit(self, params: Optional[np.ndarray] = None) -> Circuit: """Returns a parametrizable circuit represention of the ansatz. Args: params: parameters of the circuit. """ if params is not None: Warning( "This method retuns a parametrizable circuit, params will be ignored." ) circuit = Circuit() qubits = [ Qubit(qubit_index) for qubit_index in range(self.number_of_qubits) ] circuit.qubits = qubits # Prepare initial state circuit += create_layer_of_gates(self.number_of_qubits, "Ry", self._thetas) pyquil_cost_hamiltonian = qubitop_to_pyquilpauli( change_operator_type(self._cost_hamiltonian, QubitOperator)) # Add time evolution layers for i in range(self.number_of_layers): circuit += time_evolution(pyquil_cost_hamiltonian, sympy.Symbol(f"gamma_{i}")) circuit += create_layer_of_gates(self.number_of_qubits, "Ry", -self._thetas) circuit += create_layer_of_gates( self.number_of_qubits, "Rz", [-2 * sympy.Symbol(f"beta_{i}")] * self.number_of_qubits, ) circuit += create_layer_of_gates(self.number_of_qubits, "Ry", self._thetas) return circuit
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