diff --git a/doc/introduction/measurements.rst b/doc/introduction/measurements.rst index 6cbfac91a84..4f84a2e9972 100644 --- a/doc/introduction/measurements.rst +++ b/doc/introduction/measurements.rst @@ -195,9 +195,9 @@ For example, we could run the previous circuit with ``all_outcomes=True``: >>> print(result) {'00': 518, '01': 0, '10': 0, '11': 482} -Note: For complicated Hamiltonians, this can add considerable overhead time (due to the cost of calculating -eigenvalues to determine possible outcomes), and as the number of qubits increases, the length of the output -dictionary showing possible computational basis states grows rapidly. +Note: For complicated Hamiltonians, this can add considerable overhead time (due to the cost of calculating +eigenvalues to determine possible outcomes), and as the number of qubits increases, the length of the output +dictionary showing possible computational basis states grows rapidly. If counts are obtained along with a measurement function other than :func:`~.pennylane.sample`, a tuple is returned to provide differentiability for the outputs of QNodes. @@ -212,7 +212,7 @@ a tuple is returned to provide differentiability for the outputs of QNodes. return qml.expval(qml.PauliZ(0)),qml.expval(qml.PauliZ(1)), qml.counts() >>> circuit() -(-0.036, 0.036, {'01': 482, '10': 518}) +(-0.036, 0.036, {'01': 482, '10': 518}) Probability ----------- @@ -243,6 +243,9 @@ so corresponds to a :math:`99.75\%` probability of measuring state :math:`|00\rangle`, and a :math:`0.25\%` probability of measuring state :math:`|01\rangle`. + +.. _mid_circuit_measurements: + Mid-circuit measurements and conditional operations --------------------------------------------------- diff --git a/pennylane/io.py b/pennylane/io.py index 2536f2cc858..522bb5f1b66 100644 --- a/pennylane/io.py +++ b/pennylane/io.py @@ -95,39 +95,181 @@ def load(quantum_circuit_object, format: str, **load_kwargs): def from_qiskit(quantum_circuit, measurements=None): - """Loads Qiskit `QuantumCircuit `_ - objects by using the converter in the PennyLane-Qiskit plugin. + r"""Converts a Qiskit `QuantumCircuit `_ + into a PennyLane :ref:`quantum function `. - **Example:** + .. note:: - >>> qc = qiskit.QuantumCircuit(2) - >>> qc.rz(0.543, [0]) - >>> qc.cx(0, 1) - >>> my_circuit = qml.from_qiskit(qc) - - The ``my_circuit`` template can now be used within QNodes, as a - two-wire quantum template. - - >>> @qml.qnode(dev) - >>> def circuit(x): - >>> qml.RX(x, wires=1) - >>> my_circuit(wires=(1, 0)) - >>> return qml.expval(qml.Z(0)) + This function depends upon the PennyLane-Qiskit plugin. Follow the + `installation instructions `__ + to get up and running. You may need to restart your kernel if you are running in a notebook + environment. Args: quantum_circuit (qiskit.QuantumCircuit): a quantum circuit created in Qiskit - measurements (None | MeasurementProcess | list[MeasurementProcess]): the PennyLane - measurements that override the terminal measurements that may be present in the input - circuit + measurements (None | MeasurementProcess | list[MeasurementProcess]): an optional PennyLane + measurement or list of PennyLane measurements that overrides any terminal measurements + that may be present in the input circuit Returns: - function: the PennyLane template created based on the ``QuantumCircuit`` object + function: The PennyLane quantum function, created based on the input Qiskit + ``QuantumCircuit`` object. + + **Example:** + + .. code-block:: python + + import pennylane as qml + from qiskit import QuantumCircuit + + qc = QuantumCircuit(2, 2) + qc.rx(0.785, 0) + qc.ry(1.57, 1) + + my_qfunc = qml.from_qiskit(qc) + + The ``my_qfunc`` function can now be used within QNodes, as a two-wire quantum + template. We can also pass ``wires`` when calling the returned template to define + which wires it should operate on. If no wires are passed, it will default + to sequential wire labels starting at 0. + + .. code-block:: python + + dev = qml.device("default.qubit") + + @qml.qnode(dev) + def circuit(): + my_qfunc(wires=["a", "b"]) + return qml.expval(qml.Z("a")), qml.var(qml.Z("b")) + + >>> circuit() + (tensor(0.70738827, requires_grad=True), + tensor(0.99999937, requires_grad=True)) + + The measurements can also be passed directly to the function when creating the + quantum function, making it possible to create a PennyLane circuit with + :class:`qml.QNode `: + + >>> measurements = [qml.expval(qml.Z(0)), qml.var(qml.Z(1))] + >>> circuit = qml.QNode(qml.from_qiskit(qc, measurements), dev) + >>> circuit() + (tensor(0.70738827, requires_grad=True), + tensor(0.99999937, requires_grad=True)) + + .. note:: + + The ``measurements`` keyword allows one to add a list of PennyLane measurements + that will **override** any terminal measurements present in the ``QuantumCircuit``, + so that they are not performed before the operations specified in ``measurements``. + ``measurements=None``. + + If an existing ``QuantumCircuit`` already contains measurements, ``from_qiskit`` + will return those measurements, provided that they are not overriden as shown above. + These measurements can be used, e.g., for conditioning with + :func:`qml.cond() <~.cond>`, or simply included directly within the QNode's return: + + .. code-block:: python + + qc = QuantumCircuit(2, 2) + qc.rx(np.pi, 0) + qc.measure_all() + + @qml.qnode(dev) + def circuit(): + # Since measurements=None, the measurements present in the QuantumCircuit are returned. + measurements = qml.from_qiskit(qc)() + return [qml.expval(m) for m in measurements] + + >>> circuit() + [tensor(1., requires_grad=True), tensor(0., requires_grad=True)] + + .. note:: + + The ``measurements`` returned from a ``QuantumCircuit`` are in the computational basis + with 0 corresponding to :math:`|0\rangle` and 1 corresponding to :math:`|1 \rangle`. This + corresponds to the :math:`|1 \rangle \langle 1|` observable rather than the :math:`Z` Pauli + operator. + + See below for more information regarding how to translate more complex circuits from Qiskit to + PennyLane, including handling parameterized Qiskit circuits, mid-circuit measurements, and + classical control flows. .. details:: - :title: Usage Details + :title: Parameterized Quantum Circuits + + A Qiskit ``QuantumCircuit`` is parameterized if it contains + `Parameter `__ or + `ParameterVector `__ + references that need to be given defined values to evaluate the circuit. These can be passed + to the generated quantum function as keyword or positional arguments. If we define a + parameterized circuit: + + .. code-block:: python + + from qiskit.circuit import QuantumCircuit, Parameter + + angle0 = Parameter("x") + angle1 = Parameter("y") + + qc = QuantumCircuit(2, 2) + qc.rx(angle0, 0) + qc.ry(angle1, 1) + qc.cx(1, 0) - The ``measurement`` keyword allows one to add a list of PennyLane measurements - that will override the terminal measurements present in their ``QuantumCircuit``. + Then this circuit can be converted into a differentiable circuit in PennyLane and + executed: + + .. code-block:: python + + import pennylane as qml + from pennylane import numpy as np + + dev = qml.device("default.qubit") + + qfunc = qml.from_qiskit(qc, measurements=qml.expval(qml.Z(0))) + circuit = qml.QNode(qfunc, dev) + + Now, ``circuit`` has a signature of ``(x, y)``. The parameters are ordered alphabetically. + + >>> x = np.pi / 4 + >>> y = 0 + >>> circuit(x, y) + tensor(0.70710678, requires_grad=True) + + >>> qml.grad(circuit, argnum=[0, 1])(np.pi/4, np.pi/6) + (array(-0.61237244), array(-0.35355339)) + + The ``QuantumCircuit`` may also be parameterized with a ``ParameterVector``. These can be + similarly converted: + + .. code-block:: python + + from qiskit.circuit import ParameterVector + + angles = ParameterVector("angles", 2) + + qc = QuantumCircuit(2, 2) + qc.rx(angles[0], 0) + qc.ry(angles[1], 1) + qc.cx(1, 0) + + @qml.qnode(dev) + def circuit(angles): + qml.from_qiskit(qc)(angles) + return qml.expval(qml.Z(0)) + + >>> angles = [3.1, 0.45] + >>> circuit(angles) + tensor(-0.89966835, requires_grad=True) + + + .. details:: + :title: Measurements and Classical Control Flows + + When ``measurement=None``, all of the measurements performed in the ``QuantumCircuit`` will + be returned by the quantum function in the form of a :ref:`mid-circuit measurement + `. For example, if we define a ``QuantumCircuit`` with + measurements: .. code-block:: python @@ -141,17 +283,77 @@ def from_qiskit(quantum_circuit, measurements=None): qc.cx(0, 1) qc.measure_all() - measurements = [qml.expval(qml.Z(0)), qml.vn_entropy([1])] - quantum_circuit = qml.from_qiskit(qc, measurements=measurements) + Then we can create a PennyLane circuit that uses this as a sub-circuit, and performs + additional operations conditional on the results. We can also calculate standard mid-circuit + measurement statistics, like expectation value, on the returned measurements: + + .. code-block:: python @qml.qnode(qml.device("default.qubit")) - def circuit_loaded_qiskit_circuit(): - return quantum_circuit() + def circuit(): + # apply the QuantumCircuit and retrieve the measurements + mid_measure0, m0, m1 = qml.from_qiskit(qc)() - >>> print(qml.draw(circuit_loaded_qiskit_circuit)()) - 0: ──H──┤↗├──RZ(0.24)─╭●─┤ - 1: ───────────────────╰X─┤ vnentropy + # conditionally apply an additional operation based on the results + qml.cond(mid_measure0==0, qml.RX)(np.pi/2, 0) + # return the expectation value of one of the mid-circuit measurements, and a terminal measurement + return qml.expval(mid_measure0), qml.expval(m1) + + >>> circuit() + (tensor(0.5, requires_grad=True), tensor(0.5, requires_grad=True)) + + .. note:: + + The order of mid-circuit measurements returned by `qml.from_qiskit()` in the example + above is determined by the order in which measurements appear in the input Qiskit + ``QuantumCircuit``. + + Furthermore, the Qiskit `IfElseOp `__, + `SwitchCaseOp `__ and + `c_if `__ + conditional workflows are automatically translated into their PennyLane counterparts during + conversion. For example, if we construct a ``QuantumCircuit`` with these workflows: + + .. code-block:: python + + qc = QuantumCircuit(4, 1) + qc.h(0) + qc.measure(0, 0) + + # Use an `IfElseOp` operation. + noop = QuantumCircuit(1) + flip_x = QuantumCircuit(1) + flip_x.x(0) + qc.if_else((qc.clbits[0], True), flip_x, noop, [1], []) + + # Use a `SwitchCaseOp` operation. + with qc.switch(qc.clbits[0]) as case: + with case(0): + qc.y(2) + + # Use the `c_if()` function. + qc.z(3).c_if(qc.clbits[0], True) + + qc.measure_all() + + We can convert the ``QuantumCircuit`` into a PennyLane quantum function using: + + .. code-block:: python + + dev = qml.device("default.qubit") + + measurements = [qml.expval(qml.Z(i)) for i in range(qc.num_qubits)] + cond_circuit = qml.QNode(qml.from_qiskit(qc, measurements=measurements), dev) + + The result is: + + >>> print(qml.draw(cond_circuit)()) + 0: ──H──┤↗├──────────╭||─┤ + 1: ──────║───X───────├||─┤ + 2: ──────║───║──Y────├||─┤ + 3: ──────║───║──║──Z─╰||─┤ + ╚═══╩══╩══╝ """ try: return load(quantum_circuit, format="qiskit", measurements=measurements) @@ -162,11 +364,18 @@ def circuit_loaded_qiskit_circuit(): def from_qiskit_op(qiskit_op, params=None, wires=None): - """Loads Qiskit `SparsePauliOp `_ - objects by using the converter in the PennyLane-Qiskit plugin. + """Converts a Qiskit `SparsePauliOp `__ + into a PennyLane :class:`Operator `. + + .. note:: + + This function depends upon the PennyLane-Qiskit plugin. Follow the + `installation instructions `__ + to get up and running. You may need to restart your kernel if you are running in a notebook + environment. Args: - qiskit_op (qiskit.quantum_info.SparsePauliOp): the ``SparsePauliOp`` to be converted + qiskit_op (qiskit.quantum_info.SparsePauliOp): a ``SparsePauliOp`` created in Qiskit params (Any): optional assignment of coefficient values for the ``SparsePauliOp``; see the `Qiskit documentation `_ to learn more about the expected format of these parameters @@ -175,7 +384,8 @@ def from_qiskit_op(qiskit_op, params=None, wires=None): sequence of length :math:`N` Returns: - Operator: The equivalent PennyLane operator. + Operator: The PennyLane operator, created based on the input Qiskit + ``SparsePauliOp`` object. .. note:: @@ -202,7 +412,7 @@ def from_qiskit_op(qiskit_op, params=None, wires=None): SparsePauliOp(['II', 'XY'], coeffs=[1.+0.j, 1.+0.j]) - To convert the ``SparsePauliOp`` into a PennyLane :class:`Operator`, use: + To convert the ``SparsePauliOp`` into a PennyLane :class:`pennylane.operation.Operator`, use: >>> import pennylane as qml >>> qml.from_qiskit_op(qiskit_op)