Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add flatten option to the NLocal family #10269

Merged
merged 7 commits into from
Jul 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion qiskit/circuit/library/evolved_operator_ansatz.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

from __future__ import annotations
from collections.abc import Sequence
from typing import Optional

import numpy as np

Expand All @@ -40,6 +41,7 @@ def __init__(
name: str = "EvolvedOps",
parameter_prefix: str | Sequence[str] = "t",
initial_state: QuantumCircuit | None = None,
flatten: Optional[bool] = None,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency with the rest of the signature (also the Optional import would need to be removed 🙂)

Suggested change
flatten: Optional[bool] = None,
flatten: bool | None = None,

):
"""
Args:
Expand All @@ -59,13 +61,21 @@ def __init__(
will be used for each parameters. Can also be a list to specify a prefix per
operator.
initial_state: A :class:`.QuantumCircuit` object to prepend to the circuit.
flatten: Set this to ``True`` to output a flat circuit instead of nesting it inside multiple
layers of gate objects. By default currently the contents of
the output circuit will be wrapped in nested objects for
cleaner visualization. However, if you're using this circuit
for anything besides visualization its **strongly** recommended
to set this flag to ``True`` to avoid a large performance
overhead for parameter binding.
"""
super().__init__(
initial_state=initial_state,
parameter_prefix=parameter_prefix,
reps=reps,
insert_barriers=insert_barriers,
name=name,
flatten=flatten,
)
self._operators = None

Expand Down Expand Up @@ -187,7 +197,10 @@ def _evolve_operator(self, operator, time):
gate = PauliEvolutionGate(operator, time, synthesis=evolution)

evolved = QuantumCircuit(operator.num_qubits)
evolved.append(gate, evolved.qubits)
if not self.flatten:
evolved.append(gate, evolved.qubits)
else:
evolved.compose(gate.definition, evolved.qubits, inplace=True)
return evolved

def _build(self):
Expand Down
10 changes: 9 additions & 1 deletion qiskit/circuit/library/n_local/efficient_su2.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ def __init__(
insert_barriers: bool = False,
initial_state: Optional[Any] = None,
name: str = "EfficientSU2",
flatten: Optional[bool] = None,
) -> None:
"""Create a new EfficientSU2 2-local circuit.

Expand Down Expand Up @@ -124,7 +125,13 @@ def __init__(
we use :class:`~qiskit.circuit.ParameterVector`.
insert_barriers: If True, barriers are inserted in between each layer. If False,
no barriers are inserted.

flatten: Set this to ``True`` to output a flat circuit instead of nesting it inside multiple
layers of gate objects. By default currently the contents of
the output circuit will be wrapped in nested objects for
cleaner visualization. However, if you're using this circuit
for anything besides visualization its **strongly** recommended
to set this flag to ``True`` to avoid a large performance
overhead for parameter binding.
"""
if su2_gates is None:
su2_gates = [RYGate, RZGate]
Expand All @@ -140,6 +147,7 @@ def __init__(
insert_barriers=insert_barriers,
initial_state=initial_state,
name=name,
flatten=flatten,
)

@property
Expand Down
9 changes: 9 additions & 0 deletions qiskit/circuit/library/n_local/excitation_preserving.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ def __init__(
insert_barriers: bool = False,
initial_state: Optional[Any] = None,
name: str = "ExcitationPreserving",
flatten: Optional[bool] = None,
) -> None:
"""Create a new ExcitationPreserving 2-local circuit.

Expand Down Expand Up @@ -127,6 +128,13 @@ def __init__(
we use :class:`~qiskit.circuit.ParameterVector`.
insert_barriers: If True, barriers are inserted in between each layer. If False,
no barriers are inserted.
flatten: Set this to ``True`` to output a flat circuit instead of nesting it inside multiple
layers of gate objects. By default currently the contents of
the output circuit will be wrapped in nested objects for
cleaner visualization. However, if you're using this circuit
for anything besides visualization its **strongly** recommended
to set this flag to ``True`` to avoid a large performance
overhead for parameter binding.

Raises:
ValueError: If the selected mode is not supported.
Expand Down Expand Up @@ -155,6 +163,7 @@ def __init__(
insert_barriers=insert_barriers,
initial_state=initial_state,
name=name,
flatten=flatten,
)

@property
Expand Down
35 changes: 29 additions & 6 deletions qiskit/circuit/library/n_local/n_local.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ def __init__(
skip_unentangled_qubits: bool = False,
initial_state: QuantumCircuit | None = None,
name: str | None = "nlocal",
flatten: Optional[bool] = None,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
flatten: Optional[bool] = None,
flatten: bool | None = None,

) -> None:
"""Create a new n-local circuit.

Expand All @@ -114,6 +115,13 @@ def __init__(
initial_state: A :class:`.QuantumCircuit` object which can be used to describe an initial
state prepended to the NLocal circuit.
name: The name of the circuit.
flatten: Set this to ``True`` to output a flat circuit instead of nesting it inside multiple
layers of gate objects. By default currently the contents of
the output circuit will be wrapped in nested objects for
cleaner visualization. However, if you're using this circuit
for anything besides visualization its **strongly** recommended
to set this flag to ``True`` to avoid a large performance
overhead for parameter binding.

Examples:
TODO
Expand Down Expand Up @@ -144,6 +152,7 @@ def __init__(
self._initial_state: QuantumCircuit | None = None
self._initial_state_circuit: QuantumCircuit | None = None
self._bounds: list[tuple[float | None, float | None]] | None = None
self._flatten = flatten

if int(reps) != reps:
raise TypeError("The value of reps should be int")
Expand Down Expand Up @@ -188,6 +197,16 @@ def num_qubits(self, num_qubits: int) -> None:
self._num_qubits = num_qubits
self.qregs = [QuantumRegister(num_qubits, name="q")]

@property
def flatten(self) -> bool:
"""Returns whether the circuit is wrapped in nested gates/instructions or flattened."""
return bool(self._flatten)

@flatten.setter
def flatten(self, flatten: bool) -> None:
self._invalidate()
self._flatten = flatten

def _convert_to_block(self, layer: Any) -> QuantumCircuit:
"""Try to convert ``layer`` to a QuantumCircuit.

Expand Down Expand Up @@ -899,7 +918,10 @@ def _build(self) -> None:
if self.num_qubits == 0:
return

circuit = QuantumCircuit(*self.qregs, name=self.name)
if not self._flatten:
circuit = QuantumCircuit(*self.qregs, name=self.name)
else:
circuit = self

# use the initial state as starting circuit, if it is set
if self.initial_state:
Expand Down Expand Up @@ -943,12 +965,13 @@ def _build(self) -> None:
# expression contains free parameters
pass

try:
block = circuit.to_gate()
except QiskitError:
block = circuit.to_instruction()
if not self._flatten:
try:
block = circuit.to_gate()
except QiskitError:
block = circuit.to_instruction()

self.append(block, self.qubits)
self.append(block, self.qubits)
jakelishman marked this conversation as resolved.
Show resolved Hide resolved

# pylint: disable=unused-argument
def _parameter_generator(self, rep: int, block: int, indices: list[int]) -> Parameter | None:
Expand Down
12 changes: 11 additions & 1 deletion qiskit/circuit/library/n_local/qaoa_ansatz.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

# pylint: disable=cyclic-import
from __future__ import annotations
from typing import Optional

import numpy as np

from qiskit.circuit.library.evolved_operator_ansatz import EvolvedOperatorAnsatz, _is_pauli_identity
Expand All @@ -39,6 +41,7 @@ def __init__(
initial_state: QuantumCircuit | None = None,
mixer_operator=None,
name: str = "QAOA",
flatten: Optional[bool] = None,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
flatten: Optional[bool] = None,
flatten: bool | None = None,

):
r"""
Args:
Expand All @@ -55,8 +58,15 @@ def __init__(
in the original paper. Can be an operator or an optionally parameterized quantum
circuit.
name (str): A name of the circuit, default 'qaoa'
flatten: Set this to ``True`` to output a flat circuit instead of nesting it inside multiple
layers of gate objects. By default currently the contents of
the output circuit will be wrapped in nested objects for
cleaner visualization. However, if you're using this circuit
for anything besides visualization its **strongly** recommended
to set this flag to ``True`` to avoid a large performance
overhead for parameter binding.
"""
super().__init__(reps=reps, name=name)
super().__init__(reps=reps, name=name, flatten=flatten)

self._cost_operator = None
self._reps = reps
Expand Down
10 changes: 9 additions & 1 deletion qiskit/circuit/library/n_local/real_amplitudes.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ def __init__(
insert_barriers: bool = False,
initial_state: Optional[Any] = None,
name: str = "RealAmplitudes",
flatten: Optional[bool] = None,
) -> None:
"""Create a new RealAmplitudes 2-local circuit.

Expand Down Expand Up @@ -153,7 +154,13 @@ def __init__(
we use :class:`~qiskit.circuit.ParameterVector`.
insert_barriers: If True, barriers are inserted in between each layer. If False,
no barriers are inserted.

flatten: Set this to ``True`` to output a flat circuit instead of nesting it inside multiple
layers of gate objects. By default currently the contents of
the output circuit will be wrapped in nested objects for
cleaner visualization. However, if you're using this circuit
for anything besides visualization its **strongly** recommended
to set this flag to ``True`` to avoid a large performance
overhead for parameter binding.
"""
super().__init__(
num_qubits=num_qubits,
Expand All @@ -167,6 +174,7 @@ def __init__(
parameter_prefix=parameter_prefix,
insert_barriers=insert_barriers,
name=name,
flatten=flatten,
)

@property
Expand Down
9 changes: 9 additions & 0 deletions qiskit/circuit/library/n_local/two_local.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ def __init__(
insert_barriers: bool = False,
initial_state: Optional[Any] = None,
name: str = "TwoLocal",
flatten: Optional[bool] = None,
) -> None:
"""Construct a new two-local circuit.

Expand Down Expand Up @@ -208,6 +209,13 @@ def __init__(
insert_barriers: If ``True``, barriers are inserted in between each layer. If ``False``,
no barriers are inserted. Defaults to ``False``.
initial_state: A :class:`.QuantumCircuit` object to prepend to the circuit.
flatten: Set this to ``True`` to output a flat circuit instead of nesting it inside multiple
layers of gate objects. By default currently the contents of
the output circuit will be wrapped in nested objects for
cleaner visualization. However, if you're using this circuit
for anything besides visualization its **strongly** recommended
to set this flag to ``True`` to avoid a large performance
overhead for parameter binding.

"""
super().__init__(
Expand All @@ -222,6 +230,7 @@ def __init__(
initial_state=initial_state,
parameter_prefix=parameter_prefix,
name=name,
flatten=flatten,
)

def _convert_to_block(self, layer: Union[str, type, Gate, QuantumCircuit]) -> QuantumCircuit:
Expand Down
22 changes: 22 additions & 0 deletions releasenotes/notes/flatten-nlocal-family-292b23b99947f3c9.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
features:
- |
Added a new keyword argument ``flatten`` to the constructor for the
following classes:

* :class:`~.EfficientSU2`
* :class:`~.ExcitationPreserving`
* :class:`~.NLocal`
* :class:`~.RealAmplitudes`
* :class:`~.TwoLocal`
* :class:`~.EvolvedOperatorAnsatz`
* :class:`~.QAOAAnsatz`

If this argument is set to ``True`` the :class:`~.QuantumCircuit` subclass
generated will not wrap the implementation into :class:`~.Gate` or
:class:`~.circuit.Instruction` objects. While this isn't optimal for visualization
it typically results in much better runtime performance, especially with
:meth:`.QuantumCircuit.bind_parameters` and
:meth:`.QuantumCircuit.assign_parameters` which can see a substatial
runtime improvement with a flattened output compared to the nested
wrapped default output.
9 changes: 9 additions & 0 deletions test/python/circuit/library/test_evolved_op_ansatz.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,15 @@ def test_matrix_operator(self):
evo = EvolvedOperatorAnsatz(unitary, reps=3).decompose()
self.assertEqual(evo.count_ops()["hamiltonian"], 3)

def test_flattened(self):
"""Test flatten option is actually flattened."""
num_qubits = 3
ops = [Pauli("Z" * num_qubits), Pauli("Y" * num_qubits), Pauli("X" * num_qubits)]
evo = EvolvedOperatorAnsatz(ops, reps=3, flatten=True)
self.assertNotIn("hamiltonian", evo.count_ops())
self.assertNotIn("EvolvedOps", evo.count_ops())
self.assertNotIn("PauliEvolution", evo.count_ops())


def evolve(pauli_string, time):
"""Get the reference evolution circuit for a single Pauli string."""
Expand Down