Skip to content

Commit

Permalink
Merge pull request #236 from ermalrrapaj/power_gate
Browse files Browse the repository at this point in the history
Power gate
  • Loading branch information
edyounis authored Aug 5, 2024
2 parents 4483593 + 1c20cb2 commit 200e799
Show file tree
Hide file tree
Showing 13 changed files with 317 additions and 14 deletions.
16 changes: 8 additions & 8 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ ci:
skip: [mypy]
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
rev: v4.6.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
Expand Down Expand Up @@ -30,7 +30,7 @@ repos:
- --wrap-summaries=80
- --wrap-descriptions=80
- repo: https://github.com/pre-commit/mirrors-autopep8
rev: v2.0.2
rev: v2.0.4
hooks:
- id: autopep8
args:
Expand All @@ -39,13 +39,13 @@ repos:
- --ignore=E731
exclude: 'tests/ext.*'
- repo: https://github.com/asottile/pyupgrade
rev: v3.10.1
rev: v3.17.0
hooks:
- id: pyupgrade
args:
- --py38-plus
- repo: https://github.com/asottile/reorder_python_imports
rev: v3.10.0
rev: v3.13.0
hooks:
- id: reorder-python-imports
args:
Expand All @@ -54,25 +54,25 @@ repos:
- --py37-plus
exclude: 'tests/ext.*'
- repo: https://github.com/asottile/add-trailing-comma
rev: v3.0.1
rev: v3.1.0
hooks:
- id: add-trailing-comma
args:
- --py36-plus
- repo: https://github.com/PyCQA/autoflake
rev: v2.2.0
rev: v2.3.1
hooks:
- id: autoflake
args:
- --in-place
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.5.0
rev: v1.11.1
hooks:
- id: mypy
exclude: tests/qis/test_pauli.py
additional_dependencies: ["numpy>=1.21"]
- repo: https://github.com/PyCQA/flake8
rev: 6.1.0
rev: 7.1.1
hooks:
- id: flake8
args:
Expand Down
2 changes: 1 addition & 1 deletion bqskit/ext/qiskit/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def model_from_backend(backend: BackendV1) -> MachineModel:
num_qudits = config.n_qubits
gate_set = _basis_gate_str_to_bqskit_gate(config.basis_gates)
coupling_map = list({tuple(sorted(e)) for e in config.coupling_map})
return MachineModel(num_qudits, coupling_map, gate_set) # type: ignore
return MachineModel(num_qudits, coupling_map, gate_set)


def _basis_gate_str_to_bqskit_gate(basis_gates: list[str]) -> set[Gate]:
Expand Down
1 change: 1 addition & 0 deletions bqskit/ir/gates/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
:template: autosummary/gate.rst
ControlledGate
PowerGate
DaggerGate
EmbeddedGate
FrozenParameterGate
Expand Down
2 changes: 2 additions & 0 deletions bqskit/ir/gates/composed/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
from bqskit.ir.gates.composed.daggergate import DaggerGate
from bqskit.ir.gates.composed.embedded import EmbeddedGate
from bqskit.ir.gates.composed.frozenparam import FrozenParameterGate
from bqskit.ir.gates.composed.powergate import PowerGate
from bqskit.ir.gates.composed.tagged import TaggedGate
from bqskit.ir.gates.composed.vlg import VariableLocationGate

__all__ = [
'ControlledGate',
'PowerGate',
'DaggerGate',
'EmbeddedGate',
'FrozenParameterGate',
Expand Down
155 changes: 155 additions & 0 deletions bqskit/ir/gates/composed/powergate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
"""This module implements the DaggerGate Class."""
from __future__ import annotations

import re

import numpy as np
import numpy.typing as npt

from bqskit.ir.gate import Gate
from bqskit.ir.gates.composed.daggergate import DaggerGate
from bqskit.ir.gates.composedgate import ComposedGate
from bqskit.qis.unitary.differentiable import DifferentiableUnitary
from bqskit.qis.unitary.unitary import RealVector
from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix
from bqskit.utils.docs import building_docs
from bqskit.utils.typing import is_integer


class PowerGate(
ComposedGate,
DifferentiableUnitary,
):
"""
An arbitrary inverted gate.
The PowerGate is a composed gate that equivalent to the
integer power of the input gate.
Examples:
>>> from bqskit.ir.gates import TGate, TdgGate
>>> PowerGate(TGate(),2).get_unitary() ==
TdgGate().get_unitary()*TdgGate().get_unitary()
True
"""

def __init__(self, gate: Gate, power: int = 1) -> None:
"""
Create a gate which is the integer power of the input gate.
Args:
gate (Gate): The Gate to conjugate transpose.
power (int): The power index for the PowerGate.
"""
if not isinstance(gate, Gate):
raise TypeError('Expected gate object, got %s' % type(gate))

if not is_integer(power):
raise TypeError(f'Expected integer power, got {type(power)}.')

self.gate = gate
self.power = power
self._name = f'[{gate.name}^{power}]'
self._num_params = gate.num_params
self._num_qudits = gate.num_qudits
self._radixes = gate.radixes

# If input is a constant gate, we can cache the unitary.
if self.num_params == 0 and not building_docs():
self.utry = self.gate.get_unitary([]).ipower(power)

def get_unitary(self, params: RealVector = []) -> UnitaryMatrix:
"""Return the unitary for this gate, see :class:`Unitary` for more."""
if hasattr(self, 'utry'):
return self.utry

return self.gate.get_unitary(params).ipower(self.power)

def get_grad(self, params: RealVector = []) -> npt.NDArray[np.complex128]:
"""
Return the gradient for this gate.
See :class:`DifferentiableUnitary` for more info.
Notes:
The derivative of the integer power of matrix is equal
to the derivative of the matrix multiplied by
the integer-1 power of the matrix
and by the integer power.
"""
if hasattr(self, 'utry'):
return np.array([])

_, grad = self.get_unitary_and_grad(params)
return grad

def get_unitary_and_grad(
self,
params: RealVector = [],
) -> tuple[UnitaryMatrix, npt.NDArray[np.complex128]]:
"""
Return the unitary and gradient for this gate.
See :class:`DifferentiableUnitary` for more info.
"""
# Constant gate case
if hasattr(self, 'utry'):
return self.utry, np.array([])

grad_shape = (self.num_params, self.dim, self.dim)

# Identity gate case
if self.power == 0:
utry = UnitaryMatrix.identity(self.dim)
grad = np.zeros(grad_shape, dtype=np.complex128)
return utry, grad

# Invert the gate if the power is negative
gate = self.gate if self.power > 0 else DaggerGate(self.gate)
power = abs(self.power)

# Parallel Dicts for unitary and gradient powers
utrys = {} # utrys[i] = gate^(2^i)
grads = {} # grads[i] = d(gate^(2^i))/d(params)

# decompose the power as sum of powers of 2
power_bin = bin(abs(power))[2:]
binary_decomp = [
len(power_bin) - 1 - xb.start()
for xb in re.finditer('1', power_bin)
][::-1]
max_power_of_2 = max(binary_decomp)

# Base Case: 2^0
utrys[0], grads[0] = gate.get_unitary_and_grad(params) # type: ignore

# Loop over powers of 2
for i in range(1, max_power_of_2 + 1):
# u^(2^i) = u^(2^(i-1)) @ u^(2^(i-1))
utrys[i] = utrys[i - 1] @ utrys[i - 1]

# d[u^(2^i)] = d[u^(2^(i-1)) @ u^(2^(i-1))] =
grads[i] = grads[i - 1] @ utrys[i - 1] + utrys[i - 1] @ grads[i - 1]

# Calculate binary composition of the unitary and gradient
utry = utrys[binary_decomp[0]]
grad = grads[binary_decomp[0]]
for i in sorted(binary_decomp[1:]):
grad = grad @ utrys[i] + utry @ grads[i]
utry = utry @ utrys[i]

return utry, grad

def __eq__(self, other: object) -> bool:
return (
isinstance(other, PowerGate)
and self.gate == other.gate
and self.power == other.power
)

def __hash__(self) -> int:
return hash((self.power, self.gate))

def get_inverse(self) -> Gate:
"""Return the gate's inverse as a gate."""
return PowerGate(self.gate, -self.power)
2 changes: 1 addition & 1 deletion bqskit/ir/interval.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def __new__(
'Expected positive integers, got {lower} and {upper}.',
)

return super().__new__(cls, (lower, upper)) # type: ignore
return super().__new__(cls, (lower, upper))

@property
def lower(self) -> int:
Expand Down
2 changes: 1 addition & 1 deletion bqskit/ir/point.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def __new__(
else:
raise TypeError('Expected two integer arguments.')

return super().__new__(cls, (cycle, qudit)) # type: ignore
return super().__new__(cls, (cycle, qudit))

@property
def cycle(self) -> int:
Expand Down
6 changes: 5 additions & 1 deletion bqskit/qis/state/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -433,4 +433,8 @@ def __repr__(self) -> str:
return repr(self._vec)


StateLike = Union[StateVector, np.ndarray, Sequence[Union[int, float, complex]]]
StateLike = Union[
StateVector,
npt.NDArray[np.complex128],
Sequence[Union[int, float, complex]],
]
7 changes: 6 additions & 1 deletion bqskit/qis/unitary/unitary.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from typing import Union

import numpy as np
import numpy.typing as npt

from bqskit.qis.unitary.meta import UnitaryMeta
from bqskit.utils.typing import is_real_number
Expand Down Expand Up @@ -151,4 +152,8 @@ def is_self_inverse(self, params: RealVector = []) -> bool:
return np.allclose(unitary_matrix, hermitian_conjugate)


RealVector = Union[Sequence[float], np.ndarray]
RealVector = Union[
Sequence[float],
npt.NDArray[np.float64],
npt.NDArray[np.float32],
]
40 changes: 39 additions & 1 deletion bqskit/qis/unitary/unitarymatrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,22 @@ def otimes(self, *utrys: UnitaryLike) -> UnitaryMatrix:

return UnitaryMatrix(utry_acm, radixes_acm)

def ipower(self, power: int) -> UnitaryMatrix:
"""
Calculate the integer power of this unitary.
Args:
power (int): The integer power to raise the unitary to.
Returns:
UnitaryMatrix: The resulting unitary matrix.
"""
if power < 0:
mat = np.linalg.matrix_power(self.dagger, -power)
else:
mat = np.linalg.matrix_power(self, power)
return UnitaryMatrix(mat, self.radixes)

def get_unitary(self, params: RealVector = []) -> UnitaryMatrix:
"""Return the same object, satisfies the :class:`Unitary` API."""
return self
Expand Down Expand Up @@ -232,6 +248,23 @@ def get_distance_from(self, other: UnitaryLike, degree: int = 2) -> float:
dist = np.power(1 - (frac ** degree), 1.0 / degree)
return dist if dist > 0.0 else 0.0

def isclose(self, other: UnitaryLike, tol: float = 1e-6) -> bool:
"""
Check if `self` is approximately equal to `other` upto global phase.
Args:
other (UnitaryLike): The unitary to compare to.
tol (float): The numerical precision of the check.
Returns:
bool: True if `self` is close to `other`.
See Also:
- :func:`get_distance_from` for the error function used.
"""
return self.get_distance_from(other) < tol

def get_statevector(self, in_state: StateLike) -> StateVector:
"""
Calculate the output state after applying this unitary to `in_state`.
Expand Down Expand Up @@ -507,6 +540,11 @@ def __hash__(self) -> int:

UnitaryLike = Union[
UnitaryMatrix,
np.ndarray,
npt.NDArray[np.complex128],
npt.NDArray[np.complex64],
npt.NDArray[np.int64],
npt.NDArray[np.int32],
npt.NDArray[np.float64],
npt.NDArray[np.float32],
Sequence[Sequence[Union[int, float, complex]]],
]
19 changes: 19 additions & 0 deletions bqskit/utils/test/strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
from bqskit.ir.region import CircuitRegion
from bqskit.qis.state.state import StateLike
from bqskit.qis.state.state import StateVector
from bqskit.qis.unitary import RealVector
from bqskit.qis.unitary import UnitaryMatrix
from bqskit.qis.unitary.unitarymatrix import UnitaryLike
from bqskit.utils.typing import is_integer
Expand Down Expand Up @@ -258,6 +259,24 @@ def gates(
return gate


@composite
def gates_and_params(
draw: Any,
radixes: Sequence[int] | int | None = None,
constant: bool | None = None,
) -> tuple[Gate, RealVector]:
"""Hypothesis strategy for generating gates and parameters."""
gate = draw(gates(radixes, constant))
params = draw(
lists(
floats(allow_nan=False, allow_infinity=False, width=16),
min_size=gate.num_params,
max_size=gate.num_params,
),
)
return gate, params


@composite
def operations(
draw: Any,
Expand Down
Loading

0 comments on commit 200e799

Please sign in to comment.