Ejemplo n.º 1
0
    def test_create_circuits_from_qubit_operator(self):
        # Initialize target
        qubits = [Qubit(i) for i in range(0, 2)]

        gate_Z0 = Gate("Z", [qubits[0]])
        gate_X1 = Gate("X", [qubits[1]])

        gate_Y0 = Gate("Y", [qubits[0]])
        gate_Z1 = Gate("Z", [qubits[1]])

        circuit1 = Circuit()
        circuit1.qubits = qubits
        circuit1.gates = [gate_Z0, gate_X1]

        circuit2 = Circuit()
        circuit2.qubits = qubits
        circuit2.gates = [gate_Y0, gate_Z1]

        target_circuits_list = [circuit1, circuit2]

        # Given
        qubit_op = QubitOperator("Z0 X1") + QubitOperator("Y0 Z1")

        # When
        pauli_circuits = create_circuits_from_qubit_operator(qubit_op)

        # Then
        self.assertEqual(pauli_circuits[0].gates, target_circuits_list[0].gates)
        self.assertEqual(pauli_circuits[1].gates, target_circuits_list[1].gates)
        self.assertEqual(
            str(pauli_circuits[0].qubits), str(target_circuits_list[0].qubits)
        )
        self.assertEqual(
            str(pauli_circuits[1].qubits), str(target_circuits_list[1].qubits)
        )
Ejemplo n.º 2
0
def get_all_topology(params, n_qubits, static_entangler):
    """Builds an entangling layer according to the all-to-all topology.

    Args:
        n_qubits (int): number of qubits in the circuit.
        params (numpy.array): parameters of the circuit.
        static_entangler (str): gate specification for the entangling layer.

    Returns:
        Circuit: a zquantum.core.circuit.Circuit object
    """

    assert (params.shape[0] == int((n_qubits * (n_qubits - 1)) / 2))
    output = Circuit()
    qubits = [Qubit(qubit_index) for qubit_index in range(n_qubits)]
    output.qubits = qubits
    i = 0
    for qubit1_index in range(0, n_qubits - 1):
        for qubit2_index in range(qubit1_index + 1, n_qubits):
            output.gates.append(
                Gate(static_entangler,
                     [qubits[qubit1_index], qubits[qubit2_index]],
                     [params[i]]))
            i += 1
    return output
Ejemplo n.º 3
0
    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
Ejemplo n.º 4
0
def get_entangling_layer_line_topology(params: np.ndarray, n_qubits: int,
                                       entangling_gate: str) -> Circuit:
    """Builds a circuit representing an entangling layer according to the line topology.

    Args:
        params (numpy.array): parameters of the circuit.
        n_qubits (int): number of qubits in the circuit.
        entangling_gate (str): gate specification for the entangling layer.

    Returns:
        Circuit: a zquantum.core.circuit.Circuit object
    """
    assert params.shape[0] == n_qubits - 1

    circuit = Circuit()
    circuit.qubits = [Qubit(qubit_index) for qubit_index in range(n_qubits)]
    for qubit1_index in range(0, n_qubits - 1):
        circuit.gates.append(
            Gate(
                entangling_gate,
                [
                    circuit.qubits[qubit1_index],
                    circuit.qubits[qubit1_index + 1]
                ],
                [params[qubit1_index]],
            ))
    return circuit
def create_target_unitary(thetas, number_of_layers):
    target_circuit = Circuit()
    target_circuit.gates = []
    target_circuit.gates.append(Gate("Ry", [Qubit(0)], [thetas[0]]))
    target_circuit.gates.append(Gate("Ry", [Qubit(1)], [thetas[1]]))
    betas = create_betas(number_of_layers)
    gammas = create_gammas(number_of_layers)
    symbols_map = create_symbols_map(number_of_layers)
    for layer_id in range(number_of_layers):
        beta = betas[layer_id]
        gamma = gammas[layer_id]
        target_circuit.gates.append(Gate("Rz", [Qubit(0)], [2.0 * gamma]))
        target_circuit.gates.append(Gate("Rz", [Qubit(1)], [2.0 * gamma]))
        target_circuit.gates.append(Gate("Ry", [Qubit(0)], [-thetas[0]]))
        target_circuit.gates.append(Gate("Ry", [Qubit(1)], [-thetas[1]]))
        target_circuit.gates.append(Gate("Rz", [Qubit(0)], [-2.0 * beta]))
        target_circuit.gates.append(Gate("Rz", [Qubit(1)], [-2.0 * beta]))
        target_circuit.gates.append(Gate("Ry", [Qubit(0)], [thetas[0]]))
        target_circuit.gates.append(Gate("Ry", [Qubit(1)], [thetas[1]]))

    return target_circuit.evaluate(symbols_map).to_unitary()
Ejemplo n.º 6
0
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
Ejemplo n.º 7
0
 def target_unitary(self, beta, gamma, symbols_map):
     target_circuit = Circuit()
     target_circuit.gates = []
     target_circuit.gates.append(Gate("H", [Qubit(0)]))
     target_circuit.gates.append(Gate("H", [Qubit(1)]))
     target_circuit.gates.append(Gate("Rz", [Qubit(0)], [2.0 * gamma]))
     target_circuit.gates.append(Gate("Rz", [Qubit(1)], [2.0 * gamma]))
     target_circuit.gates.append(Gate("Rx", [Qubit(0)], [2.0 * beta]))
     target_circuit.gates.append(Gate("Rx", [Qubit(1)], [2.0 * beta]))
     return target_circuit.evaluate(symbols_map).to_unitary()
Ejemplo n.º 8
0
    def test_create_layer_of_gates_not_parametrized(self):
        # Given
        number_of_qubits = 4
        gate_name = "X"
        qubits = [Qubit(i) for i in range(0, number_of_qubits)]
        gate_0 = Gate(gate_name, qubits=[qubits[0]])
        gate_1 = Gate(gate_name, qubits=[qubits[1]])
        gate_2 = Gate(gate_name, qubits=[qubits[2]])
        gate_3 = Gate(gate_name, qubits=[qubits[3]])
        target_circuit = Circuit()
        target_circuit.qubits = qubits
        target_circuit.gates = [gate_0, gate_1, gate_2, gate_3]

        # When
        layer_of_x = create_layer_of_gates(number_of_qubits, gate_name)

        # Then
        self.assertEqual(layer_of_x, target_circuit)
Ejemplo n.º 9
0
def get_single_qubit_layer(n_qubits, params, single_qubit_gate):
    """Builds a layer of single-qubit gates acting on all qubits in a quantum circuit.

    Args:
        n_qubits (int): number of qubits in the circuit.
        params (numpy.array): parameters of the single-qubit gates.
        single_qubit_gate (str): the gate to be applied to each qubit.

    Returns:
        Circuit: a zquantum.core.circuit.Circuit object
    """

    output = Circuit()
    qubits = [Qubit(qubit_index) for qubit_index in range(n_qubits)]
    output.qubits = qubits
    for qi in range(n_qubits):
        output.gates.append(Gate(single_qubit_gate, [qubits[qi]], [params[qi]]))
    return output
Ejemplo n.º 10
0
    def circuits(self):
        circuits = [Circuit() for _ in range(5)]
        for circuit in circuits:
            circuit.qubits = [Qubit(i) for i in range(2)]

        circuits[1].gates = [
            Gate("Rx", [circuits[1].qubits[0]], [1.2]),
            Gate("Ry", [circuits[1].qubits[1]], [1.5]),
            Gate("Rx", [circuits[1].qubits[0]], [-0.0002]),
            Gate("Ry", [circuits[1].qubits[1]], [0]),
        ]

        for circuit in circuits[2:]:
            circuit.gates = [
                Gate("Rx", [circuit.qubits[0]], [sympy.Symbol("theta_0")]),
                Gate("Ry", [circuit.qubits[1]], [sympy.Symbol("theta_1")]),
                Gate("Rx", [circuit.qubits[0]], [sympy.Symbol("theta_2")]),
                Gate("Ry", [circuit.qubits[1]], [sympy.Symbol("theta_3")]),
            ]

        return circuits
Ejemplo n.º 11
0
    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
Ejemplo n.º 12
0
def get_single_qubit_layer(params, n_qubits, single_qubit_gates):
    """Builds a layer of single-qubit gates acting on all qubits in a quantum circuit.

    Args:
        n_qubits (int): number of qubits in the circuit.
        params (numpy.array): parameters of the single-qubit gates.
        single_qubit_gates (str): a list of single qubit gates to be applied to each qubit.

    Returns:
        Circuit: a zquantum.core.circuit.Circuit object
    """
    assert (len(params) == len(single_qubit_gates) * n_qubits)
    output = Circuit()
    qubits = [Qubit(qubit_index) for qubit_index in range(n_qubits)]
    output.qubits = qubits
    parameter_index = 0
    for gate in single_qubit_gates:
        for qubit_index in range(n_qubits):
            # Add single_qubit_gate to each qubit
            output.gates.append(
                Gate(gate, [qubits[qubit_index]], [params[parameter_index]]))
            parameter_index += 1

    return output
Ejemplo n.º 13
0
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