Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/remove-consider-using-dict-items…
Browse files Browse the repository at this point in the history
…-lint-rule' into remove-consider-using-dict-items-lint-rule
  • Loading branch information
joesho112358 committed Apr 26, 2024
2 parents 3857fe8 + 3d293be commit 69b91f7
Show file tree
Hide file tree
Showing 55 changed files with 782 additions and 610 deletions.
1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,6 @@ disable = [
"consider-iterating-dictionary",
"consider-using-enumerate",
"consider-using-f-string",
"modified-iterating-list",
"nested-min-max",
"no-member",
"no-value-for-parameter",
Expand Down
18 changes: 18 additions & 0 deletions qiskit/circuit/annotated_operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,24 @@ def inverse(self, annotated: bool = True):
extended_modifiers.append(InverseModifier())
return AnnotatedOperation(self.base_op, extended_modifiers)

def power(self, exponent: float, annotated: bool = False):
"""
Raise this gate to the power of ``exponent``.
Implemented as an annotated operation, see :class:`.AnnotatedOperation`.
Args:
exponent: the power to raise the gate to
annotated: ignored (used for consistency with other power methods)
Returns:
An operation implementing ``gate^exponent``
"""
# pylint: disable=unused-argument
extended_modifiers = self.modifiers.copy()
extended_modifiers.append(PowerModifier(exponent))
return AnnotatedOperation(self.base_op, extended_modifiers)


def _canonicalize_modifiers(modifiers):
"""
Expand Down
27 changes: 20 additions & 7 deletions qiskit/circuit/gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

from qiskit.circuit.parameterexpression import ParameterExpression
from qiskit.circuit.exceptions import CircuitError
from .annotated_operation import AnnotatedOperation, ControlModifier
from .annotated_operation import AnnotatedOperation, ControlModifier, PowerModifier
from .instruction import Instruction


Expand Down Expand Up @@ -62,23 +62,36 @@ def to_matrix(self) -> np.ndarray:
return self.__array__(dtype=complex)
raise CircuitError(f"to_matrix not defined for this {type(self)}")

def power(self, exponent: float):
"""Creates a unitary gate as `gate^exponent`.
def power(self, exponent: float, annotated: bool = False):
"""Raise this gate to the power of ``exponent``.
Implemented either as a unitary gate (ref. :class:`~.library.UnitaryGate`)
or as an annotated operation (ref. :class:`.AnnotatedOperation`). In the case of several standard
gates, such as :class:`.RXGate`, when the power of a gate can be expressed in terms of another
standard gate that is returned directly.
Args:
exponent (float): Gate^exponent
exponent (float): the power to raise the gate to
annotated (bool): indicates whether the power gate can be implemented
as an annotated operation. In the case of several standard
gates, such as :class:`.RXGate`, this argument is ignored when
the power of a gate can be expressed in terms of another
standard gate.
Returns:
.library.UnitaryGate: To which `to_matrix` is self.to_matrix^exponent.
An operation implementing ``gate^exponent``
Raises:
CircuitError: If Gate is not unitary
CircuitError: If gate is not unitary
"""
# pylint: disable=cyclic-import
from qiskit.quantum_info.operators import Operator
from qiskit.circuit.library.generalized_gates.unitary import UnitaryGate

return UnitaryGate(Operator(self).power(exponent), label=f"{self.name}^{exponent}")
if not annotated:
return UnitaryGate(Operator(self).power(exponent), label=f"{self.name}^{exponent}")
else:
return AnnotatedOperation(self, PowerModifier(exponent))

def __pow__(self, exponent: float) -> "Gate":
return self.power(exponent)
Expand Down
149 changes: 100 additions & 49 deletions qiskit/circuit/library/n_local/n_local.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,24 @@
"""The n-local circuit class."""

from __future__ import annotations

import collections
import itertools
import typing
from collections.abc import Callable, Mapping, Sequence

from itertools import combinations

import numpy
from qiskit.circuit.quantumcircuit import QuantumCircuit
from qiskit.circuit.quantumregister import QuantumRegister
from qiskit.circuit import Instruction, Parameter, ParameterVector, ParameterExpression
from qiskit.circuit import (
Instruction,
Parameter,
ParameterVector,
ParameterExpression,
CircuitInstruction,
)
from qiskit.exceptions import QiskitError
from qiskit.circuit.library.standard_gates import get_standard_gate_name_mapping

from ..blueprintcircuit import BlueprintCircuit

Expand Down Expand Up @@ -154,6 +162,17 @@ def __init__(
self._bounds: list[tuple[float | None, float | None]] | None = None
self._flatten = flatten

# During the build, if a subclass hasn't overridden our parametrisation methods, we can use
# a newer fast-path method to parametrise the rotation and entanglement blocks if internally
# those are just simple stdlib gates that have been promoted to circuits. We don't
# precalculate the fast-path layers themselves because there's far too much that can be
# overridden between object construction and build, and far too many subclasses of `NLocal`
# that override bits and bobs of the internal private methods, so it'd be too hard to keep
# everything in sync.
self._allow_fast_path_parametrization = (
getattr(self._parameter_generator, "__func__", None) is NLocal._parameter_generator
)

if int(reps) != reps:
raise TypeError("The value of reps should be int")

Expand Down Expand Up @@ -779,13 +798,10 @@ def add_layer(
else:
entangler_map = entanglement

layer = QuantumCircuit(self.num_qubits)
for i in entangler_map:
params = self.ordered_parameters[-len(get_parameters(block)) :]
parameterized_block = self._parameterize_block(block, params=params)
layer.compose(parameterized_block, i, inplace=True)

self.compose(layer, inplace=True)
self.compose(parameterized_block, i, inplace=True, copy=False)
else:
# cannot prepend a block currently, just rebuild
self._invalidate()
Expand Down Expand Up @@ -843,52 +859,65 @@ def _build_rotation_layer(self, circuit, param_iter, i):
"""Build a rotation layer."""
# if the unentangled qubits are skipped, compute the set of qubits that are not entangled
if self._skip_unentangled_qubits:
unentangled_qubits = self.get_unentangled_qubits()
skipped_qubits = self.get_unentangled_qubits()
else:
skipped_qubits = set()

target_qubits = circuit.qubits

# iterate over all rotation blocks
for j, block in enumerate(self.rotation_blocks):
# create a new layer
layer = QuantumCircuit(*self.qregs)

# we apply the rotation gates stacked on top of each other, i.e.
# if we have 4 qubits and a rotation block of width 2, we apply two instances
block_indices = [
list(range(k * block.num_qubits, (k + 1) * block.num_qubits))
for k in range(self.num_qubits // block.num_qubits)
]

# if unentangled qubits should not be acted on, remove all operations that
# touch an unentangled qubit
if self._skip_unentangled_qubits:
skipped_blocks = {qubit // block.num_qubits for qubit in skipped_qubits}
if (
self._allow_fast_path_parametrization
and (simple_block := _stdlib_gate_from_simple_block(block)) is not None
):
all_qubits = (
tuple(target_qubits[k * block.num_qubits : (k + 1) * block.num_qubits])
for k in range(self.num_qubits // block.num_qubits)
if k not in skipped_blocks
)
for qubits in all_qubits:
instr = CircuitInstruction(
simple_block.gate(*itertools.islice(param_iter, simple_block.num_params)),
qubits,
)
circuit._append(instr)
else:
block_indices = [
indices
for indices in block_indices
if set(indices).isdisjoint(unentangled_qubits)
list(range(k * block.num_qubits, (k + 1) * block.num_qubits))
for k in range(self.num_qubits // block.num_qubits)
if k not in skipped_blocks
]

# apply the operations in the layer
for indices in block_indices:
parameterized_block = self._parameterize_block(block, param_iter, i, j, indices)
layer.compose(parameterized_block, indices, inplace=True)

# add the layer to the circuit
circuit.compose(layer, inplace=True)
# apply the operations in the layer
for indices in block_indices:
parameterized_block = self._parameterize_block(block, param_iter, i, j, indices)
circuit.compose(parameterized_block, indices, inplace=True, copy=False)

def _build_entanglement_layer(self, circuit, param_iter, i):
"""Build an entanglement layer."""
# iterate over all entanglement blocks
target_qubits = circuit.qubits
for j, block in enumerate(self.entanglement_blocks):
# create a new layer and get the entangler map for this block
layer = QuantumCircuit(*self.qregs)
entangler_map = self.get_entangler_map(i, j, block.num_qubits)

# apply the operations in the layer
for indices in entangler_map:
parameterized_block = self._parameterize_block(block, param_iter, i, j, indices)
layer.compose(parameterized_block, indices, inplace=True)

# add the layer to the circuit
circuit.compose(layer, inplace=True)
if (
self._allow_fast_path_parametrization
and (simple_block := _stdlib_gate_from_simple_block(block)) is not None
):
for indices in entangler_map:
# It's actually nontrivially faster to use a listcomp and pass that to `tuple`
# than to pass a generator expression directly.
# pylint: disable=consider-using-generator
instr = CircuitInstruction(
simple_block.gate(*itertools.islice(param_iter, simple_block.num_params)),
tuple([target_qubits[i] for i in indices]),
)
circuit._append(instr)
else:
# apply the operations in the layer
for indices in entangler_map:
parameterized_block = self._parameterize_block(block, param_iter, i, j, indices)
circuit.compose(parameterized_block, indices, inplace=True, copy=False)

def _build_additional_layers(self, circuit, which):
if which == "appended":
Expand All @@ -901,13 +930,10 @@ def _build_additional_layers(self, circuit, which):
raise ValueError("`which` must be either `appended` or `prepended`.")

for block, ent in zip(blocks, entanglements):
layer = QuantumCircuit(*self.qregs)
if isinstance(ent, str):
ent = get_entangler_map(block.num_qubits, self.num_qubits, ent)
for indices in ent:
layer.compose(block, indices, inplace=True)

circuit.compose(layer, inplace=True)
circuit.compose(block, indices, inplace=True, copy=False)

def _build(self) -> None:
"""If not already built, build the circuit."""
Expand All @@ -926,7 +952,7 @@ def _build(self) -> None:

# use the initial state as starting circuit, if it is set
if self.initial_state:
circuit.compose(self.initial_state.copy(), inplace=True)
circuit.compose(self.initial_state.copy(), inplace=True, copy=False)

param_iter = iter(self.ordered_parameters)

Expand Down Expand Up @@ -972,7 +998,7 @@ def _build(self) -> None:
except QiskitError:
block = circuit.to_instruction()

self.append(block, self.qubits)
self.append(block, self.qubits, copy=False)

# pylint: disable=unused-argument
def _parameter_generator(self, rep: int, block: int, indices: list[int]) -> Parameter | None:
Expand Down Expand Up @@ -1023,7 +1049,7 @@ def get_entangler_map(
raise ValueError("Pairwise entanglement is not defined for blocks with more than 2 qubits.")

if entanglement == "full":
return list(combinations(list(range(n)), m))
return list(itertools.combinations(list(range(n)), m))
elif entanglement == "reverse_linear":
# reverse linear connectivity. In the case of m=2 and the entanglement_block='cx'
# then it's equivalent to 'full' entanglement
Expand Down Expand Up @@ -1057,3 +1083,28 @@ def get_entangler_map(

else:
raise ValueError(f"Unsupported entanglement type: {entanglement}")


_StdlibGateResult = collections.namedtuple("_StdlibGateResult", ("gate", "num_params"))
_STANDARD_GATE_MAPPING = get_standard_gate_name_mapping()


def _stdlib_gate_from_simple_block(block: QuantumCircuit) -> _StdlibGateResult | None:
if block.global_phase != 0.0 or len(block) != 1:
return None
instruction = block.data[0]
# If the single instruction isn't a standard-library gate that spans the full width of the block
# in the correct order, we're not simple. If the gate isn't fully parametrised with pure,
# unique `Parameter` instances (expressions are too complex) that are in order, we're not
# simple.
if (
instruction.clbits
or tuple(instruction.qubits) != tuple(block.qubits)
or (
getattr(_STANDARD_GATE_MAPPING.get(instruction.operation.name), "base_class", None)
is not instruction.operation.base_class
)
or tuple(instruction.operation.params) != tuple(block.parameters)
):
return None
return _StdlibGateResult(instruction.operation.base_class, len(instruction.operation.params))
3 changes: 1 addition & 2 deletions qiskit/circuit/library/standard_gates/i.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,7 @@ def inverse(self, annotated: bool = False):
."""
return IGate() # self-inverse

def power(self, exponent: float):
"""Raise gate to a power."""
def power(self, exponent: float, annotated: bool = False):
return IGate()

def __eq__(self, other):
Expand Down
3 changes: 1 addition & 2 deletions qiskit/circuit/library/standard_gates/iswap.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,7 @@ def _define(self):

self.definition = qc

def power(self, exponent: float):
"""Raise gate to a power."""
def power(self, exponent: float, annotated: bool = False):
return XXPlusYYGate(-np.pi * exponent)

def __eq__(self, other):
Expand Down
6 changes: 2 additions & 4 deletions qiskit/circuit/library/standard_gates/p.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,7 @@ def __array__(self, dtype=None):
lam = float(self.params[0])
return numpy.array([[1, 0], [0, exp(1j * lam)]], dtype=dtype)

def power(self, exponent: float):
"""Raise gate to a power."""
def power(self, exponent: float, annotated: bool = False):
(theta,) = self.params
return PhaseGate(exponent * theta)

Expand Down Expand Up @@ -289,8 +288,7 @@ def __array__(self, dtype=None):
)
return numpy.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, eith, 0], [0, 0, 0, 1]], dtype=dtype)

def power(self, exponent: float):
"""Raise gate to a power."""
def power(self, exponent: float, annotated: bool = False):
(theta,) = self.params
return CPhaseGate(exponent * theta)

Expand Down
3 changes: 1 addition & 2 deletions qiskit/circuit/library/standard_gates/r.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,7 @@ def __array__(self, dtype=None):
exp_p = exp(1j * phi)
return numpy.array([[cos, -1j * exp_m * sin], [-1j * exp_p * sin, cos]], dtype=dtype)

def power(self, exponent: float):
"""Raise gate to a power."""
def power(self, exponent: float, annotated: bool = False):
theta, phi = self.params
return RGate(exponent * theta, phi)

Expand Down
3 changes: 1 addition & 2 deletions qiskit/circuit/library/standard_gates/rx.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,7 @@ def __array__(self, dtype=None):
sin = math.sin(self.params[0] / 2)
return numpy.array([[cos, -1j * sin], [-1j * sin, cos]], dtype=dtype)

def power(self, exponent: float):
"""Raise gate to a power."""
def power(self, exponent: float, annotated: bool = False):
(theta,) = self.params
return RXGate(exponent * theta)

Expand Down
3 changes: 1 addition & 2 deletions qiskit/circuit/library/standard_gates/rxx.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,7 @@ def __array__(self, dtype=None):
dtype=dtype,
)

def power(self, exponent: float):
"""Raise gate to a power."""
def power(self, exponent: float, annotated: bool = False):
(theta,) = self.params
return RXXGate(exponent * theta)

Expand Down
3 changes: 1 addition & 2 deletions qiskit/circuit/library/standard_gates/ry.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,7 @@ def __array__(self, dtype=None):
sin = math.sin(self.params[0] / 2)
return numpy.array([[cos, -sin], [sin, cos]], dtype=dtype)

def power(self, exponent: float):
"""Raise gate to a power."""
def power(self, exponent: float, annotated: bool = False):
(theta,) = self.params
return RYGate(exponent * theta)

Expand Down
Loading

0 comments on commit 69b91f7

Please sign in to comment.