From 2eea43e057674953e039036cf9e441941ec11885 Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Thu, 17 Oct 2024 11:37:14 +0200 Subject: [PATCH] update docstrings --- .../circuit/library/n_local/efficient_su2.py | 53 +++++----- .../library/n_local/excitation_preserving.py | 44 +++------ qiskit/circuit/library/n_local/n_local.py | 96 ++++++++++++++++++- .../library/n_local/pauli_two_design.py | 8 +- .../library/n_local/real_amplitudes.py | 71 ++++---------- 5 files changed, 156 insertions(+), 116 deletions(-) diff --git a/qiskit/circuit/library/n_local/efficient_su2.py b/qiskit/circuit/library/n_local/efficient_su2.py index d04d75996923..a49d62803e33 100644 --- a/qiskit/circuit/library/n_local/efficient_su2.py +++ b/qiskit/circuit/library/n_local/efficient_su2.py @@ -43,17 +43,17 @@ def efficient_su2( insert_barriers: bool = False, name: str = "EfficientSU2", ): - r"""The hardware efficient SU(2) 2-local circuit. + r"""The hardware-efficient :math:`SU(2)` 2-local circuit. - The ``efficient_su2`` circuit consists of layers of single qubit operations spanned by SU(2) - and CX entanglements. This is a heuristic pattern that can be used to prepare trial wave - functions for variational quantum algorithms or classification circuit for machine learning. + The ``efficient_su2`` circuit consists of layers of single qubit operations spanned by + :math:`SU(2)` and CX entanglements. This is a heuristic pattern that can be used to prepare trial + wave functions for variational quantum algorithms or classification circuit for machine learning. - SU(2) stands for special unitary group of degree 2, its elements are :math:`2 \times 2` + :math:`SU(2)` is the special unitary group of degree 2, its elements are :math:`2 \times 2` unitary matrices with determinant 1, such as the Pauli rotation gates. - On 3 qubits and using the Pauli :math:`Y` and :math:`Z` su2_gates as single qubit gates, the - hardware efficient SU(2) circuit is represented by: + On 3 qubits and using the Pauli :math:`Y` and :math:`Z` rotations as single qubit gates, the + this circuit is represented by: .. parsed-literal:: @@ -67,29 +67,24 @@ def efficient_su2( Examples: - >>> circuit = efficient_su2(3, reps=1) - >>> print(circuit) - ┌──────────┐┌──────────┐ ┌──────────┐┌──────────┐ - q_0: ┤ RY(θ[0]) ├┤ RZ(θ[3]) ├──■────■──┤ RY(θ[6]) ├┤ RZ(θ[9]) ├───────────── - ├──────────┤├──────────┤┌─┴─┐ │ └──────────┘├──────────┤┌───────────┐ - q_1: ┤ RY(θ[1]) ├┤ RZ(θ[4]) ├┤ X ├──┼───────■──────┤ RY(θ[7]) ├┤ RZ(θ[10]) ├ - ├──────────┤├──────────┤└───┘┌─┴─┐ ┌─┴─┐ ├──────────┤├───────────┤ - q_2: ┤ RY(θ[2]) ├┤ RZ(θ[5]) ├─────┤ X ├───┤ X ├────┤ RY(θ[8]) ├┤ RZ(θ[11]) ├ - └──────────┘└──────────┘ └───┘ └───┘ └──────────┘└───────────┘ + Per default, the ``"reverse_linear"`` entanglement is used, which, in the case of + CX gates, is equivalent to an all-to-all entanglement: - >>> ansatz = efficient_su2(4, su2_gates=['rx', 'y'], entanglement='circular', reps=1) - >>> qc = QuantumCircuit(4) # create a circuit and append the RY variational form - >>> qc.compose(ansatz, inplace=True) - >>> qc.draw() - ┌──────────┐┌───┐┌───┐ ┌──────────┐ ┌───┐ - q_0: ┤ RX(θ[0]) ├┤ Y ├┤ X ├──■──┤ RX(θ[4]) ├───┤ Y ├───────────────────── - ├──────────┤├───┤└─┬─┘┌─┴─┐└──────────┘┌──┴───┴───┐ ┌───┐ - q_1: ┤ RX(θ[1]) ├┤ Y ├──┼──┤ X ├─────■──────┤ RX(θ[5]) ├───┤ Y ├───────── - ├──────────┤├───┤ │ └───┘ ┌─┴─┐ └──────────┘┌──┴───┴───┐┌───┐ - q_2: ┤ RX(θ[2]) ├┤ Y ├──┼──────────┤ X ├─────────■──────┤ RX(θ[6]) ├┤ Y ├ - ├──────────┤├───┤ │ └───┘ ┌─┴─┐ ├──────────┤├───┤ - q_3: ┤ RX(θ[3]) ├┤ Y ├──■──────────────────────┤ X ├────┤ RX(θ[7]) ├┤ Y ├ - └──────────┘└───┘ └───┘ └──────────┘└───┘ + .. plot:: + :include-source: + + circuit = efficient_su2(3, reps=1) + circuit.draw("mpl") + + To specify which SU(2) gates should be used in the rotation layer, we can set the + ``su2_gates`` argument. In addition, we can change the entanglement structure. + For example:: + + .. plot:: + :include-source: + + circuit = efficient_su2(4, su2_gates=["rx", "y"], entanglement="circular", reps=1) + circuit.draw("mpl") Args: num_qubits: The number of qubits. diff --git a/qiskit/circuit/library/n_local/excitation_preserving.py b/qiskit/circuit/library/n_local/excitation_preserving.py index 3c0570a29f5c..b185e923b1ca 100644 --- a/qiskit/circuit/library/n_local/excitation_preserving.py +++ b/qiskit/circuit/library/n_local/excitation_preserving.py @@ -67,37 +67,23 @@ def excitation_preserving( Examples: - >>> ansatz = excitation_preserving(3, reps=1, insert_barriers=True, entanglement='linear') - >>> print(ansatz) # show the circuit - ┌──────────┐ ░ ┌────────────┐┌────────────┐ ░ ┌──────────┐ - q_0: ┤ RZ(θ[0]) ├─░─┤0 ├┤0 ├─────────────────────────────░─┤ RZ(θ[5]) ├ - ├──────────┤ ░ │ RXX(θ[3]) ││ RYY(θ[3]) │┌────────────┐┌────────────┐ ░ ├──────────┤ - q_1: ┤ RZ(θ[1]) ├─░─┤1 ├┤1 ├┤0 ├┤0 ├─░─┤ RZ(θ[6]) ├ - ├──────────┤ ░ └────────────┘└────────────┘│ RXX(θ[4]) ││ RYY(θ[4]) │ ░ ├──────────┤ - q_2: ┤ RZ(θ[2]) ├─░─────────────────────────────┤1 ├┤1 ├─░─┤ RZ(θ[7]) ├ - └──────────┘ ░ └────────────┘└────────────┘ ░ └──────────┘ + With linear entanglement, this circuit is given by: - >>> ansatz = excitation_preserving(2, reps=1) - >>> qc = QuantumCircuit(2) # create a circuit and append the RY variational form - >>> qc.cry(0.2, 0, 1) # do some previous operation - >>> qc.compose(ansatz, inplace=True) - >>> qc.draw() - ┌──────────┐┌────────────┐┌────────────┐┌──────────┐ - q_0: ─────■─────┤ RZ(θ[0]) ├┤0 ├┤0 ├┤ RZ(θ[3]) ├ - ┌────┴────┐├──────────┤│ RXX(θ[2]) ││ RYY(θ[2]) │├──────────┤ - q_1: ┤ RY(0.2) ├┤ RZ(θ[1]) ├┤1 ├┤1 ├┤ RZ(θ[4]) ├ - └─────────┘└──────────┘└────────────┘└────────────┘└──────────┘ + .. plot:: + :include-source: - >>> ansatz = excitation_preserving(3, reps=1, mode='fsim', entanglement=[[0,2]], - ... insert_barriers=True) - >>> print(ansatz) - ┌──────────┐ ░ ┌────────────┐┌────────────┐ ░ ┌──────────┐ - q_0: ┤ RZ(θ[0]) ├─░─┤0 ├┤0 ├─■──────░─┤ RZ(θ[5]) ├ - ├──────────┤ ░ │ ││ │ │ ░ ├──────────┤ - q_1: ┤ RZ(θ[1]) ├─░─┤ RXX(θ[3]) ├┤ RYY(θ[3]) ├─┼──────░─┤ RZ(θ[6]) ├ - ├──────────┤ ░ │ ││ │ │θ[4] ░ ├──────────┤ - q_2: ┤ RZ(θ[2]) ├─░─┤1 ├┤1 ├─■──────░─┤ RZ(θ[7]) ├ - └──────────┘ ░ └────────────┘└────────────┘ ░ └──────────┘ + ansatz = excitation_preserving(3, reps=1, insert_barriers=True, entanglement="linear") + ansatz.draw("mpl") + + The entanglement structure can be explicitly specified with the ``entanglement`` + argument. The ``"fsim"`` mode includes an additional parameterized :class:`.CPhaseGate` + in each block: + + .. plot:: + :include-source: + + ansatz = excitation_preserving(3, reps=1, mode="fsim", entanglement=[[0, 2]]) + ansatz.draw("mpl") Args: num_qubits: The number of qubits. diff --git a/qiskit/circuit/library/n_local/n_local.py b/qiskit/circuit/library/n_local/n_local.py index e2aa669bd2e0..bb9291e78985 100644 --- a/qiskit/circuit/library/n_local/n_local.py +++ b/qiskit/circuit/library/n_local/n_local.py @@ -66,7 +66,7 @@ def n_local( skip_unentangled_qubits: bool = False, name: str | None = "nlocal", ) -> QuantumCircuit: - """Construct an n-local variational circuit. + r"""Construct an n-local variational circuit. The structure of the n-local circuit are alternating rotation and entanglement layers. In both layers, parameterized circuit-blocks act on the circuit in a defined way. @@ -99,7 +99,99 @@ def n_local( +---------------------------------+ repeated reps times - If specified, barriers can be inserted in between every layer. + Entanglement: + + The entanglement describes the connections of the gates in the entanglement layer. + For a two-qubit gate for example, the entanglement contains pairs of qubits on which the + gate should acts, e.g. ``[[ctrl0, target0], [ctrl1, target1], ...]``. + A set of default entanglement strategies is provided and can be selected by name: + + * ``"full"`` entanglement is each qubit is entangled with all the others. + * ``"linear"`` entanglement is qubit :math:`i` entangled with qubit :math:`i + 1`, + for all :math:`i \in \{0, 1, ... , n - 2\}`, where :math:`n` is the total number of qubits. + * ``"reverse_linear"`` entanglement is qubit :math:`i` entangled with qubit :math:`i + 1`, + for all :math:`i \in \{n-2, n-3, ... , 1, 0\}`, where :math:`n` is the total number of qubits. + Note that if ``entanglement_blocks=="cx"`` then this option provides the same unitary as + ``"full"`` with fewer entangling gates. + * ``"pairwise"`` entanglement is one layer where qubit :math:`i` is entangled with qubit + :math:`i + 1`, for all even values of :math:`i`, and then a second layer where qubit :math:`i` + is entangled with qubit :math:`i + 1`, for all odd values of :math:`i`. + * ``"circular"`` entanglement is linear entanglement but with an additional entanglement of the + first and last qubit before the linear part. + * ``"sca"`` (shifted-circular-alternating) entanglement is a generalized and modified version + of the proposed circuit 14 in `Sim et al. `__. + It consists of circular entanglement where the "long" entanglement connecting the first with + the last qubit is shifted by one each block. Furthermore the role of control and target + qubits are swapped every block (therefore alternating). + + If an entanglement layer contains multiple blocks, then the entanglement should be + given as list of entanglements for each block. For example:: + + entanglement_blocks = ["rxx", "ryy"] + entanglement = ["full", "linear"] # full for rxx and linear for ryy + + or:: + + structure_rxx = [[0, 1], [2, 3]] + structure_ryy = [[0, 2]] + entanglement = [structure_rxx, structure_ryy] + + Finally, the entanglement can vary in each repetition of the circuit. For this, we + support passing a callable that takes as input the layer index and returns the entanglement + for the layer in the above format. See the examples below for a concrete example. + + Examples: + + The rotation and entanglement gates can be specified via single strings, if they + are made up of a single block per layer: + + .. plot:: + :include-source: + + circuit = n_local(3, "ry", "cx", "linear", reps=2, insert_barriers=True) + circuit.draw("mpl") + + Multiple gates per layer can be set by passing a list. Here, for example, we use + Pauli-Y and Pauli-Z rotations in the rotation layer: + + .. plot:: + :include-source: + + circuit = n_local(3, ["ry", "rz"], "cz", 'full', reps=1, insert_barriers=True) + circuit.draw("mpl") + + To omit rotation or entanglement layers, the block can be set to an empty list: + + .. plot:: + :include-source: + + circuit = n_local(4, [], "cry", reps=2) + circuit.draw("mpl") + + The entanglement can be set explicitly via the ``entanglement`` argument: + + .. plot:: + :include-source: + + entangler_map = [[0, 1], [2, 0]] + circuit = n_local(3, "x", "crx", entangler_map, reps=2) + circuit.draw("mpl") + + We can set different entanglements per layer, by specifing a callable that takes + as input the current layer index, and returns the entanglement structure. For example, + the following uses different entanglements for odd and even layers: + + .. plot: + :include-source: + + def entanglement(layer_index: int) -> list[list[int]]: + if layer_index % 2 == 0: + return [[0, 1], [0, 2]] + return [[1, 2]] + + circuit = n_local(3, "x", "cx", entanglement, reps=3, insert_barriers=True) + circuit.draw("mpl") + Args: num_qubits: The number of qubits of the circuit. diff --git a/qiskit/circuit/library/n_local/pauli_two_design.py b/qiskit/circuit/library/n_local/pauli_two_design.py index 5ac43de435e8..d21593eb70fa 100644 --- a/qiskit/circuit/library/n_local/pauli_two_design.py +++ b/qiskit/circuit/library/n_local/pauli_two_design.py @@ -59,11 +59,11 @@ def pauli_two_design( Examples: .. plot:: - :include-source: + :include-source: - from qiskit.circuit.library import pauli_two_design - circuit = pauli_two_design(4, reps=2, seed=5, insert_barriers=True) - circuit.draw("mpl") + from qiskit.circuit.library import pauli_two_design + circuit = pauli_two_design(4, reps=2, seed=5, insert_barriers=True) + circuit.draw("mpl") Args: num_qubits: The number of qubits of the Pauli Two-Design circuit. diff --git a/qiskit/circuit/library/n_local/real_amplitudes.py b/qiskit/circuit/library/n_local/real_amplitudes.py index 4a66cdbdaf36..770f7565129f 100644 --- a/qiskit/circuit/library/n_local/real_amplitudes.py +++ b/qiskit/circuit/library/n_local/real_amplitudes.py @@ -67,62 +67,29 @@ def real_amplitudes( Examples: - >>> ansatz = real_amplitudes(3, reps=2) # create the circuit on 3 qubits - >>> print(ansatz) - ┌──────────┐ ┌──────────┐ ┌──────────┐ - q_0: ┤ Ry(θ[0]) ├──────────■──────┤ Ry(θ[3]) ├──────────■──────┤ Ry(θ[6]) ├ - ├──────────┤ ┌─┴─┐ ├──────────┤ ┌─┴─┐ ├──────────┤ - q_1: ┤ Ry(θ[1]) ├──■─────┤ X ├────┤ Ry(θ[4]) ├──■─────┤ X ├────┤ Ry(θ[7]) ├ - ├──────────┤┌─┴─┐┌──┴───┴───┐└──────────┘┌─┴─┐┌──┴───┴───┐└──────────┘ - q_2: ┤ Ry(θ[2]) ├┤ X ├┤ Ry(θ[5]) ├────────────┤ X ├┤ Ry(θ[8]) ├──────────── - └──────────┘└───┘└──────────┘ └───┘└──────────┘ + .. plot:: + :include-source: - >>> ansatz = real_amplitudes(3, entanglement='full', reps=2) # it is the same unitary as above - >>> print(ansatz) - ┌──────────┐ ┌──────────┐ ┌──────────┐ - q_0: ┤ RY(θ[0]) ├──■────■──┤ RY(θ[3]) ├──────────────■────■──┤ RY(θ[6]) ├──────────── - ├──────────┤┌─┴─┐ │ └──────────┘┌──────────┐┌─┴─┐ │ └──────────┘┌──────────┐ - q_1: ┤ RY(θ[1]) ├┤ X ├──┼───────■──────┤ RY(θ[4]) ├┤ X ├──┼───────■──────┤ RY(θ[7]) ├ - ├──────────┤└───┘┌─┴─┐ ┌─┴─┐ ├──────────┤└───┘┌─┴─┐ ┌─┴─┐ ├──────────┤ - q_2: ┤ RY(θ[2]) ├─────┤ X ├───┤ X ├────┤ RY(θ[5]) ├─────┤ X ├───┤ X ├────┤ RY(θ[8]) ├ - └──────────┘ └───┘ └───┘ └──────────┘ └───┘ └───┘ └──────────┘ + ansatz = real_amplitudes(3, reps=2) # create the circuit on 3 qubits + ansatz.draw("mpl") - >>> ansatz = real_amplitudes(3, entanglement='linear', reps=2, insert_barriers=True) - >>> qc = QuantumCircuit(3) # create a circuit and append the RY variational form - >>> qc.compose(ansatz, inplace=True) - >>> qc.draw() - ┌──────────┐ ░ ░ ┌──────────┐ ░ ░ ┌──────────┐ - q_0: ┤ RY(θ[0]) ├─░───■────────░─┤ RY(θ[3]) ├─░───■────────░─┤ RY(θ[6]) ├ - ├──────────┤ ░ ┌─┴─┐ ░ ├──────────┤ ░ ┌─┴─┐ ░ ├──────────┤ - q_1: ┤ RY(θ[1]) ├─░─┤ X ├──■───░─┤ RY(θ[4]) ├─░─┤ X ├──■───░─┤ RY(θ[7]) ├ - ├──────────┤ ░ └───┘┌─┴─┐ ░ ├──────────┤ ░ └───┘┌─┴─┐ ░ ├──────────┤ - q_2: ┤ RY(θ[2]) ├─░──────┤ X ├─░─┤ RY(θ[5]) ├─░──────┤ X ├─░─┤ RY(θ[8]) ├ - └──────────┘ ░ └───┘ ░ └──────────┘ ░ └───┘ ░ └──────────┘ + .. plot:: + :include-source: - >>> ansatz = real_amplitudes(4, reps=1, entanglement='circular', insert_barriers=True) - >>> print(ansatz) - ┌──────────┐ ░ ┌───┐ ░ ┌──────────┐ - q_0: ┤ RY(θ[0]) ├─░─┤ X ├──■─────────────░─┤ RY(θ[4]) ├ - ├──────────┤ ░ └─┬─┘┌─┴─┐ ░ ├──────────┤ - q_1: ┤ RY(θ[1]) ├─░───┼──┤ X ├──■────────░─┤ RY(θ[5]) ├ - ├──────────┤ ░ │ └───┘┌─┴─┐ ░ ├──────────┤ - q_2: ┤ RY(θ[2]) ├─░───┼───────┤ X ├──■───░─┤ RY(θ[6]) ├ - ├──────────┤ ░ │ └───┘┌─┴─┐ ░ ├──────────┤ - q_3: ┤ RY(θ[3]) ├─░───■────────────┤ X ├─░─┤ RY(θ[7]) ├ - └──────────┘ ░ └───┘ ░ └──────────┘ + ansatz = real_amplitudes(3, entanglement="full", reps=2) # it is the same unitary as above + ansatz.draw("mpl") - >>> ansatz = real_amplitudes(4, reps=2, entanglement=[[0,3], [0,2]], - ... skip_unentangled_qubits=True) - >>> print(ansatz) - ┌──────────┐ ┌──────────┐ ┌──────────┐ - q_0: ┤ RY(θ[0]) ├──■───────■──────┤ RY(θ[3]) ├──■───────■──────┤ RY(θ[6]) ├ - └──────────┘ │ │ └──────────┘ │ │ └──────────┘ - q_1: ──────────────┼───────┼────────────────────┼───────┼────────────────── - ┌──────────┐ │ ┌─┴─┐ ┌──────────┐ │ ┌─┴─┐ ┌──────────┐ - q_2: ┤ RY(θ[1]) ├──┼─────┤ X ├────┤ RY(θ[4]) ├──┼─────┤ X ├────┤ RY(θ[7]) ├ - ├──────────┤┌─┴─┐┌──┴───┴───┐└──────────┘┌─┴─┐┌──┴───┴───┐└──────────┘ - q_3: ┤ RY(θ[2]) ├┤ X ├┤ RY(θ[5]) ├────────────┤ X ├┤ RY(θ[8]) ├──────────── - └──────────┘└───┘└──────────┘ └───┘└──────────┘ + .. plot:: + :include-source: + + ansatz = real_amplitudes(3, entanglement="linear", reps=2, insert_barriers=True) + ansatz.draw("mpl") + + .. plot:: + :include-source: + + ansatz = real_amplitudes(4, reps=2, entanglement=[[0,3], [0,2]], skip_unentangled_qubits=True) + ansatz.draw("mpl") Args: num_qubits: The number of qubits of the RealAmplitudes circuit.