Skip to content

Commit 200e799

Browse files
authored
Merge pull request #236 from ermalrrapaj/power_gate
Power gate
2 parents 4483593 + 1c20cb2 commit 200e799

File tree

13 files changed

+317
-14
lines changed

13 files changed

+317
-14
lines changed

.pre-commit-config.yaml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ ci:
22
skip: [mypy]
33
repos:
44
- repo: https://github.com/pre-commit/pre-commit-hooks
5-
rev: v4.4.0
5+
rev: v4.6.0
66
hooks:
77
- id: trailing-whitespace
88
- id: end-of-file-fixer
@@ -30,7 +30,7 @@ repos:
3030
- --wrap-summaries=80
3131
- --wrap-descriptions=80
3232
- repo: https://github.com/pre-commit/mirrors-autopep8
33-
rev: v2.0.2
33+
rev: v2.0.4
3434
hooks:
3535
- id: autopep8
3636
args:
@@ -39,13 +39,13 @@ repos:
3939
- --ignore=E731
4040
exclude: 'tests/ext.*'
4141
- repo: https://github.com/asottile/pyupgrade
42-
rev: v3.10.1
42+
rev: v3.17.0
4343
hooks:
4444
- id: pyupgrade
4545
args:
4646
- --py38-plus
4747
- repo: https://github.com/asottile/reorder_python_imports
48-
rev: v3.10.0
48+
rev: v3.13.0
4949
hooks:
5050
- id: reorder-python-imports
5151
args:
@@ -54,25 +54,25 @@ repos:
5454
- --py37-plus
5555
exclude: 'tests/ext.*'
5656
- repo: https://github.com/asottile/add-trailing-comma
57-
rev: v3.0.1
57+
rev: v3.1.0
5858
hooks:
5959
- id: add-trailing-comma
6060
args:
6161
- --py36-plus
6262
- repo: https://github.com/PyCQA/autoflake
63-
rev: v2.2.0
63+
rev: v2.3.1
6464
hooks:
6565
- id: autoflake
6666
args:
6767
- --in-place
6868
- repo: https://github.com/pre-commit/mirrors-mypy
69-
rev: v1.5.0
69+
rev: v1.11.1
7070
hooks:
7171
- id: mypy
7272
exclude: tests/qis/test_pauli.py
7373
additional_dependencies: ["numpy>=1.21"]
7474
- repo: https://github.com/PyCQA/flake8
75-
rev: 6.1.0
75+
rev: 7.1.1
7676
hooks:
7777
- id: flake8
7878
args:

bqskit/ext/qiskit/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def model_from_backend(backend: BackendV1) -> MachineModel:
2323
num_qudits = config.n_qubits
2424
gate_set = _basis_gate_str_to_bqskit_gate(config.basis_gates)
2525
coupling_map = list({tuple(sorted(e)) for e in config.coupling_map})
26-
return MachineModel(num_qudits, coupling_map, gate_set) # type: ignore
26+
return MachineModel(num_qudits, coupling_map, gate_set)
2727

2828

2929
def _basis_gate_str_to_bqskit_gate(basis_gates: list[str]) -> set[Gate]:

bqskit/ir/gates/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@
100100
:template: autosummary/gate.rst
101101
102102
ControlledGate
103+
PowerGate
103104
DaggerGate
104105
EmbeddedGate
105106
FrozenParameterGate

bqskit/ir/gates/composed/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
from bqskit.ir.gates.composed.daggergate import DaggerGate
66
from bqskit.ir.gates.composed.embedded import EmbeddedGate
77
from bqskit.ir.gates.composed.frozenparam import FrozenParameterGate
8+
from bqskit.ir.gates.composed.powergate import PowerGate
89
from bqskit.ir.gates.composed.tagged import TaggedGate
910
from bqskit.ir.gates.composed.vlg import VariableLocationGate
1011

1112
__all__ = [
1213
'ControlledGate',
14+
'PowerGate',
1315
'DaggerGate',
1416
'EmbeddedGate',
1517
'FrozenParameterGate',

bqskit/ir/gates/composed/powergate.py

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
"""This module implements the DaggerGate Class."""
2+
from __future__ import annotations
3+
4+
import re
5+
6+
import numpy as np
7+
import numpy.typing as npt
8+
9+
from bqskit.ir.gate import Gate
10+
from bqskit.ir.gates.composed.daggergate import DaggerGate
11+
from bqskit.ir.gates.composedgate import ComposedGate
12+
from bqskit.qis.unitary.differentiable import DifferentiableUnitary
13+
from bqskit.qis.unitary.unitary import RealVector
14+
from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix
15+
from bqskit.utils.docs import building_docs
16+
from bqskit.utils.typing import is_integer
17+
18+
19+
class PowerGate(
20+
ComposedGate,
21+
DifferentiableUnitary,
22+
):
23+
"""
24+
An arbitrary inverted gate.
25+
26+
The PowerGate is a composed gate that equivalent to the
27+
integer power of the input gate.
28+
29+
Examples:
30+
>>> from bqskit.ir.gates import TGate, TdgGate
31+
>>> PowerGate(TGate(),2).get_unitary() ==
32+
TdgGate().get_unitary()*TdgGate().get_unitary()
33+
True
34+
"""
35+
36+
def __init__(self, gate: Gate, power: int = 1) -> None:
37+
"""
38+
Create a gate which is the integer power of the input gate.
39+
40+
Args:
41+
gate (Gate): The Gate to conjugate transpose.
42+
power (int): The power index for the PowerGate.
43+
"""
44+
if not isinstance(gate, Gate):
45+
raise TypeError('Expected gate object, got %s' % type(gate))
46+
47+
if not is_integer(power):
48+
raise TypeError(f'Expected integer power, got {type(power)}.')
49+
50+
self.gate = gate
51+
self.power = power
52+
self._name = f'[{gate.name}^{power}]'
53+
self._num_params = gate.num_params
54+
self._num_qudits = gate.num_qudits
55+
self._radixes = gate.radixes
56+
57+
# If input is a constant gate, we can cache the unitary.
58+
if self.num_params == 0 and not building_docs():
59+
self.utry = self.gate.get_unitary([]).ipower(power)
60+
61+
def get_unitary(self, params: RealVector = []) -> UnitaryMatrix:
62+
"""Return the unitary for this gate, see :class:`Unitary` for more."""
63+
if hasattr(self, 'utry'):
64+
return self.utry
65+
66+
return self.gate.get_unitary(params).ipower(self.power)
67+
68+
def get_grad(self, params: RealVector = []) -> npt.NDArray[np.complex128]:
69+
"""
70+
Return the gradient for this gate.
71+
72+
See :class:`DifferentiableUnitary` for more info.
73+
74+
Notes:
75+
The derivative of the integer power of matrix is equal
76+
to the derivative of the matrix multiplied by
77+
the integer-1 power of the matrix
78+
and by the integer power.
79+
"""
80+
if hasattr(self, 'utry'):
81+
return np.array([])
82+
83+
_, grad = self.get_unitary_and_grad(params)
84+
return grad
85+
86+
def get_unitary_and_grad(
87+
self,
88+
params: RealVector = [],
89+
) -> tuple[UnitaryMatrix, npt.NDArray[np.complex128]]:
90+
"""
91+
Return the unitary and gradient for this gate.
92+
93+
See :class:`DifferentiableUnitary` for more info.
94+
"""
95+
# Constant gate case
96+
if hasattr(self, 'utry'):
97+
return self.utry, np.array([])
98+
99+
grad_shape = (self.num_params, self.dim, self.dim)
100+
101+
# Identity gate case
102+
if self.power == 0:
103+
utry = UnitaryMatrix.identity(self.dim)
104+
grad = np.zeros(grad_shape, dtype=np.complex128)
105+
return utry, grad
106+
107+
# Invert the gate if the power is negative
108+
gate = self.gate if self.power > 0 else DaggerGate(self.gate)
109+
power = abs(self.power)
110+
111+
# Parallel Dicts for unitary and gradient powers
112+
utrys = {} # utrys[i] = gate^(2^i)
113+
grads = {} # grads[i] = d(gate^(2^i))/d(params)
114+
115+
# decompose the power as sum of powers of 2
116+
power_bin = bin(abs(power))[2:]
117+
binary_decomp = [
118+
len(power_bin) - 1 - xb.start()
119+
for xb in re.finditer('1', power_bin)
120+
][::-1]
121+
max_power_of_2 = max(binary_decomp)
122+
123+
# Base Case: 2^0
124+
utrys[0], grads[0] = gate.get_unitary_and_grad(params) # type: ignore
125+
126+
# Loop over powers of 2
127+
for i in range(1, max_power_of_2 + 1):
128+
# u^(2^i) = u^(2^(i-1)) @ u^(2^(i-1))
129+
utrys[i] = utrys[i - 1] @ utrys[i - 1]
130+
131+
# d[u^(2^i)] = d[u^(2^(i-1)) @ u^(2^(i-1))] =
132+
grads[i] = grads[i - 1] @ utrys[i - 1] + utrys[i - 1] @ grads[i - 1]
133+
134+
# Calculate binary composition of the unitary and gradient
135+
utry = utrys[binary_decomp[0]]
136+
grad = grads[binary_decomp[0]]
137+
for i in sorted(binary_decomp[1:]):
138+
grad = grad @ utrys[i] + utry @ grads[i]
139+
utry = utry @ utrys[i]
140+
141+
return utry, grad
142+
143+
def __eq__(self, other: object) -> bool:
144+
return (
145+
isinstance(other, PowerGate)
146+
and self.gate == other.gate
147+
and self.power == other.power
148+
)
149+
150+
def __hash__(self) -> int:
151+
return hash((self.power, self.gate))
152+
153+
def get_inverse(self) -> Gate:
154+
"""Return the gate's inverse as a gate."""
155+
return PowerGate(self.gate, -self.power)

bqskit/ir/interval.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ def __new__(
8989
'Expected positive integers, got {lower} and {upper}.',
9090
)
9191

92-
return super().__new__(cls, (lower, upper)) # type: ignore
92+
return super().__new__(cls, (lower, upper))
9393

9494
@property
9595
def lower(self) -> int:

bqskit/ir/point.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ def __new__(
6666
else:
6767
raise TypeError('Expected two integer arguments.')
6868

69-
return super().__new__(cls, (cycle, qudit)) # type: ignore
69+
return super().__new__(cls, (cycle, qudit))
7070

7171
@property
7272
def cycle(self) -> int:

bqskit/qis/state/state.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -433,4 +433,8 @@ def __repr__(self) -> str:
433433
return repr(self._vec)
434434

435435

436-
StateLike = Union[StateVector, np.ndarray, Sequence[Union[int, float, complex]]]
436+
StateLike = Union[
437+
StateVector,
438+
npt.NDArray[np.complex128],
439+
Sequence[Union[int, float, complex]],
440+
]

bqskit/qis/unitary/unitary.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from typing import Union
88

99
import numpy as np
10+
import numpy.typing as npt
1011

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

153154

154-
RealVector = Union[Sequence[float], np.ndarray]
155+
RealVector = Union[
156+
Sequence[float],
157+
npt.NDArray[np.float64],
158+
npt.NDArray[np.float32],
159+
]

bqskit/qis/unitary/unitarymatrix.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,22 @@ def otimes(self, *utrys: UnitaryLike) -> UnitaryMatrix:
199199

200200
return UnitaryMatrix(utry_acm, radixes_acm)
201201

202+
def ipower(self, power: int) -> UnitaryMatrix:
203+
"""
204+
Calculate the integer power of this unitary.
205+
206+
Args:
207+
power (int): The integer power to raise the unitary to.
208+
209+
Returns:
210+
UnitaryMatrix: The resulting unitary matrix.
211+
"""
212+
if power < 0:
213+
mat = np.linalg.matrix_power(self.dagger, -power)
214+
else:
215+
mat = np.linalg.matrix_power(self, power)
216+
return UnitaryMatrix(mat, self.radixes)
217+
202218
def get_unitary(self, params: RealVector = []) -> UnitaryMatrix:
203219
"""Return the same object, satisfies the :class:`Unitary` API."""
204220
return self
@@ -232,6 +248,23 @@ def get_distance_from(self, other: UnitaryLike, degree: int = 2) -> float:
232248
dist = np.power(1 - (frac ** degree), 1.0 / degree)
233249
return dist if dist > 0.0 else 0.0
234250

251+
def isclose(self, other: UnitaryLike, tol: float = 1e-6) -> bool:
252+
"""
253+
Check if `self` is approximately equal to `other` upto global phase.
254+
255+
Args:
256+
other (UnitaryLike): The unitary to compare to.
257+
258+
tol (float): The numerical precision of the check.
259+
260+
Returns:
261+
bool: True if `self` is close to `other`.
262+
263+
See Also:
264+
- :func:`get_distance_from` for the error function used.
265+
"""
266+
return self.get_distance_from(other) < tol
267+
235268
def get_statevector(self, in_state: StateLike) -> StateVector:
236269
"""
237270
Calculate the output state after applying this unitary to `in_state`.
@@ -507,6 +540,11 @@ def __hash__(self) -> int:
507540

508541
UnitaryLike = Union[
509542
UnitaryMatrix,
510-
np.ndarray,
543+
npt.NDArray[np.complex128],
544+
npt.NDArray[np.complex64],
545+
npt.NDArray[np.int64],
546+
npt.NDArray[np.int32],
547+
npt.NDArray[np.float64],
548+
npt.NDArray[np.float32],
511549
Sequence[Sequence[Union[int, float, complex]]],
512550
]

0 commit comments

Comments
 (0)