mirror of https://github.com/Qiskit/qiskit.git
Add flatten option to the NLocal family (#10269)
* Add flatten option to the NLocal family This commit adds a new flag `flatten` to the circuit library elements in the NLocal family. This flag is used to avoid the nested wrapping of the output that is done by default in the output of these gates. This is done primarily in the interest of performance. The runtime performance of bind_parameters() is vastly improved with a flattened output compared to the nested object. For example, running: ``` qc = EfficientSU2(100, entanglement="linear", reps=100) qc.measure_all() qc.bind_parameters({x: math.pi / 2 for x in qc.parameters}) ``` the runtime of `bind_parameters()` went from ~390 seconds with the wrapped output to ~0.5 seconds with the flattened output. I think given these results longer term we might want to flip the default behavior to only wrap on user request (likely for visualization as that's the only use case I can think of for the wrapped output). The default value is set to `None` in this PR to facilitate this change. In 0.26/0.45 we can emit a warning if `flatten` is `None` to warn that the default will change in a future release. * Add missing nlocal subclasses * Add setter * Fix lint * Fix flatten for EvolvedOperatorAnsatz
This commit is contained in:
parent
7c1b8ee4fe
commit
fcd7766fec
|
@ -14,6 +14,7 @@
|
|||
|
||||
from __future__ import annotations
|
||||
from collections.abc import Sequence
|
||||
from typing import Optional
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
@ -40,6 +41,7 @@ class EvolvedOperatorAnsatz(NLocal):
|
|||
name: str = "EvolvedOps",
|
||||
parameter_prefix: str | Sequence[str] = "t",
|
||||
initial_state: QuantumCircuit | None = None,
|
||||
flatten: Optional[bool] = None,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
|
@ -59,6 +61,13 @@ class EvolvedOperatorAnsatz(NLocal):
|
|||
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,
|
||||
|
@ -66,6 +75,7 @@ class EvolvedOperatorAnsatz(NLocal):
|
|||
reps=reps,
|
||||
insert_barriers=insert_barriers,
|
||||
name=name,
|
||||
flatten=flatten,
|
||||
)
|
||||
self._operators = None
|
||||
|
||||
|
@ -187,7 +197,10 @@ class EvolvedOperatorAnsatz(NLocal):
|
|||
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):
|
||||
|
|
|
@ -94,6 +94,7 @@ class EfficientSU2(TwoLocal):
|
|||
insert_barriers: bool = False,
|
||||
initial_state: Optional[Any] = None,
|
||||
name: str = "EfficientSU2",
|
||||
flatten: Optional[bool] = None,
|
||||
) -> None:
|
||||
"""Create a new EfficientSU2 2-local circuit.
|
||||
|
||||
|
@ -124,7 +125,13 @@ class EfficientSU2(TwoLocal):
|
|||
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]
|
||||
|
@ -140,6 +147,7 @@ class EfficientSU2(TwoLocal):
|
|||
insert_barriers=insert_barriers,
|
||||
initial_state=initial_state,
|
||||
name=name,
|
||||
flatten=flatten,
|
||||
)
|
||||
|
||||
@property
|
||||
|
|
|
@ -100,6 +100,7 @@ class ExcitationPreserving(TwoLocal):
|
|||
insert_barriers: bool = False,
|
||||
initial_state: Optional[Any] = None,
|
||||
name: str = "ExcitationPreserving",
|
||||
flatten: Optional[bool] = None,
|
||||
) -> None:
|
||||
"""Create a new ExcitationPreserving 2-local circuit.
|
||||
|
||||
|
@ -127,6 +128,13 @@ class ExcitationPreserving(TwoLocal):
|
|||
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.
|
||||
|
@ -155,6 +163,7 @@ class ExcitationPreserving(TwoLocal):
|
|||
insert_barriers=insert_barriers,
|
||||
initial_state=initial_state,
|
||||
name=name,
|
||||
flatten=flatten,
|
||||
)
|
||||
|
||||
@property
|
||||
|
|
|
@ -90,6 +90,7 @@ class NLocal(BlueprintCircuit):
|
|||
skip_unentangled_qubits: bool = False,
|
||||
initial_state: QuantumCircuit | None = None,
|
||||
name: str | None = "nlocal",
|
||||
flatten: Optional[bool] = None,
|
||||
) -> None:
|
||||
"""Create a new n-local circuit.
|
||||
|
||||
|
@ -114,6 +115,13 @@ class NLocal(BlueprintCircuit):
|
|||
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
|
||||
|
@ -144,6 +152,7 @@ class NLocal(BlueprintCircuit):
|
|||
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")
|
||||
|
@ -188,6 +197,16 @@ class NLocal(BlueprintCircuit):
|
|||
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.
|
||||
|
||||
|
@ -900,7 +919,10 @@ class NLocal(BlueprintCircuit):
|
|||
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:
|
||||
|
@ -944,12 +966,13 @@ class NLocal(BlueprintCircuit):
|
|||
# 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)
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def _parameter_generator(self, rep: int, block: int, indices: list[int]) -> Parameter | None:
|
||||
|
|
|
@ -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
|
||||
|
@ -39,6 +41,7 @@ class QAOAAnsatz(EvolvedOperatorAnsatz):
|
|||
initial_state: QuantumCircuit | None = None,
|
||||
mixer_operator=None,
|
||||
name: str = "QAOA",
|
||||
flatten: Optional[bool] = None,
|
||||
):
|
||||
r"""
|
||||
Args:
|
||||
|
@ -55,8 +58,15 @@ class QAOAAnsatz(EvolvedOperatorAnsatz):
|
|||
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
|
||||
|
|
|
@ -124,6 +124,7 @@ class RealAmplitudes(TwoLocal):
|
|||
insert_barriers: bool = False,
|
||||
initial_state: Optional[Any] = None,
|
||||
name: str = "RealAmplitudes",
|
||||
flatten: Optional[bool] = None,
|
||||
) -> None:
|
||||
"""Create a new RealAmplitudes 2-local circuit.
|
||||
|
||||
|
@ -153,7 +154,13 @@ class RealAmplitudes(TwoLocal):
|
|||
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,
|
||||
|
@ -167,6 +174,7 @@ class RealAmplitudes(TwoLocal):
|
|||
parameter_prefix=parameter_prefix,
|
||||
insert_barriers=insert_barriers,
|
||||
name=name,
|
||||
flatten=flatten,
|
||||
)
|
||||
|
||||
@property
|
||||
|
|
|
@ -173,6 +173,7 @@ class TwoLocal(NLocal):
|
|||
insert_barriers: bool = False,
|
||||
initial_state: Optional[Any] = None,
|
||||
name: str = "TwoLocal",
|
||||
flatten: Optional[bool] = None,
|
||||
) -> None:
|
||||
"""Construct a new two-local circuit.
|
||||
|
||||
|
@ -208,6 +209,13 @@ class TwoLocal(NLocal):
|
|||
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__(
|
||||
|
@ -222,6 +230,7 @@ class TwoLocal(NLocal):
|
|||
initial_state=initial_state,
|
||||
parameter_prefix=parameter_prefix,
|
||||
name=name,
|
||||
flatten=flatten,
|
||||
)
|
||||
|
||||
def _convert_to_block(self, layer: Union[str, type, Gate, QuantumCircuit]) -> QuantumCircuit:
|
||||
|
|
|
@ -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.
|
|
@ -119,6 +119,15 @@ class TestEvolvedOperatorAnsatz(QiskitTestCase):
|
|||
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."""
|
||||
|
|
Loading…
Reference in New Issue