qilisdk 0.1.5__py3-none-any.whl → 0.1.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- qilisdk/analog/__init__.py +1 -2
- qilisdk/analog/hamiltonian.py +4 -71
- qilisdk/analog/schedule.py +291 -313
- qilisdk/backends/backend.py +5 -1
- qilisdk/backends/cuda_backend.py +10 -6
- qilisdk/backends/qutip_backend.py +24 -32
- qilisdk/{common → core}/__init__.py +4 -0
- qilisdk/core/interpolator.py +406 -0
- qilisdk/{common → core}/model.py +7 -7
- qilisdk/core/parameterizable.py +131 -0
- qilisdk/{common → core}/qtensor.py +1 -1
- qilisdk/{common → core}/variables.py +192 -11
- qilisdk/cost_functions/cost_function.py +1 -1
- qilisdk/cost_functions/model_cost_function.py +5 -5
- qilisdk/cost_functions/observable_cost_function.py +2 -2
- qilisdk/digital/ansatz.py +0 -3
- qilisdk/digital/circuit.py +3 -2
- qilisdk/digital/circuit_transpiler.py +46 -0
- qilisdk/digital/circuit_transpiler_passes/__init__.py +18 -0
- qilisdk/digital/circuit_transpiler_passes/circuit_transpiler_pass.py +36 -0
- qilisdk/digital/circuit_transpiler_passes/decompose_multi_controlled_gates_pass.py +216 -0
- qilisdk/digital/circuit_transpiler_passes/numeric_helpers.py +82 -0
- qilisdk/digital/gates.py +15 -5
- qilisdk/{speqtrum/experiments → experiments}/__init__.py +13 -2
- qilisdk/{speqtrum/experiments → experiments}/experiment_functional.py +90 -2
- qilisdk/{speqtrum/experiments → experiments}/experiment_result.py +16 -0
- qilisdk/functionals/functional.py +2 -2
- qilisdk/functionals/functional_result.py +1 -1
- qilisdk/functionals/sampling.py +8 -1
- qilisdk/functionals/time_evolution.py +8 -4
- qilisdk/functionals/time_evolution_result.py +2 -2
- qilisdk/functionals/variational_program.py +58 -0
- qilisdk/optimizers/optimizer_result.py +1 -1
- qilisdk/speqtrum/__init__.py +2 -0
- qilisdk/speqtrum/speqtrum.py +537 -152
- qilisdk/speqtrum/speqtrum_models.py +258 -2
- qilisdk/utils/openfermion/__init__.py +38 -0
- qilisdk/{common/algorithm.py → utils/openfermion/__init__.pyi} +2 -3
- qilisdk/utils/openfermion/openfermion.py +45 -0
- qilisdk/utils/visualization/schedule_renderers.py +22 -9
- {qilisdk-0.1.5.dist-info → qilisdk-0.1.7.dist-info}/METADATA +89 -39
- qilisdk-0.1.7.dist-info/RECORD +76 -0
- {qilisdk-0.1.5.dist-info → qilisdk-0.1.7.dist-info}/WHEEL +1 -1
- qilisdk/analog/linear_schedule.py +0 -118
- qilisdk/common/parameterizable.py +0 -75
- qilisdk-0.1.5.dist-info/RECORD +0 -69
- /qilisdk/{common → core}/exceptions.py +0 -0
- /qilisdk/{common → core}/result.py +0 -0
- {qilisdk-0.1.5.dist-info → qilisdk-0.1.7.dist-info}/licenses/LICENCE +0 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Copyright 2025 Qilimanjaro Quantum Tech
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from abc import ABC, abstractmethod
|
|
16
|
+
|
|
17
|
+
from qilisdk.digital import Circuit
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class CircuitTranspilerPass(ABC):
|
|
21
|
+
"""Base class for non-mutating circuit transpiler passes.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
CircuitTranspilerPass: Instances expose the `run` API required by the transpiler.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
@abstractmethod
|
|
28
|
+
def run(self, circuit: Circuit) -> Circuit:
|
|
29
|
+
"""Create a new circuit built from `circuit` without mutating the input.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
circuit (Circuit): Circuit to be transpiled.
|
|
33
|
+
Returns:
|
|
34
|
+
Circuit: Newly transpiled circuit.
|
|
35
|
+
"""
|
|
36
|
+
...
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# Copyright 2025 Qilimanjaro Quantum Tech
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
import math
|
|
16
|
+
from typing import List
|
|
17
|
+
|
|
18
|
+
from qilisdk.digital import RX, RY, RZ, U1, U2, U3, Circuit, Gate, H, I, S, T, X, Y, Z
|
|
19
|
+
from qilisdk.digital.gates import BasicGate, Controlled
|
|
20
|
+
|
|
21
|
+
from .circuit_transpiler_pass import CircuitTranspilerPass
|
|
22
|
+
from .numeric_helpers import (
|
|
23
|
+
_unitary_sqrt_2x2,
|
|
24
|
+
_zyz_from_unitary,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class DecomposeMultiControlledGatesPass(CircuitTranspilerPass):
|
|
29
|
+
"""Decompose multi-controlled (k >= 2) single-qubit gates.
|
|
30
|
+
|
|
31
|
+
The construction follows Lemma 7.5 from Barenco et al., *Elementary Gates for Quantum Computation*,
|
|
32
|
+
recursively replacing a k-controlled unitary with five layers of (k-1)-controlled operations built
|
|
33
|
+
from sqrt(U), its adjoint, and multi-controlled Pauli-X gates.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def run(self, circuit: Circuit) -> Circuit:
|
|
37
|
+
"""Rewrite the circuit while decomposing multi-controlled gates.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
circuit (Circuit): Circuit whose gates should be rewritten.
|
|
41
|
+
Returns:
|
|
42
|
+
Circuit: Newly built circuit containing only supported primitives.
|
|
43
|
+
"""
|
|
44
|
+
out = Circuit(circuit.nqubits)
|
|
45
|
+
for g in circuit.gates:
|
|
46
|
+
for h in self._rewrite_gate(g):
|
|
47
|
+
out.add(h)
|
|
48
|
+
|
|
49
|
+
return out
|
|
50
|
+
|
|
51
|
+
def _rewrite_gate(self, gate: Gate) -> List[Gate]: # noqa: PLR6301
|
|
52
|
+
"""Expand unsupported gates into equivalent elementary gates.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
gate (Gate): Candidate gate potentially containing multiple controls.
|
|
56
|
+
Returns:
|
|
57
|
+
list[Gate]: Sequence of equivalent gates that rely on supported primitives.
|
|
58
|
+
"""
|
|
59
|
+
# --- Multi-controlled gates ---
|
|
60
|
+
if isinstance(gate, Controlled):
|
|
61
|
+
base: BasicGate = gate.basic_gate
|
|
62
|
+
if base.nqubits != 1:
|
|
63
|
+
raise NotImplementedError("Controlled version of multi-qubit gates is not supported.")
|
|
64
|
+
|
|
65
|
+
return _decompose(gate)
|
|
66
|
+
|
|
67
|
+
# Everything else is untouched.
|
|
68
|
+
return [gate]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _decompose(gate: Controlled) -> List[Gate]:
|
|
72
|
+
"""Recursively decompose a multi-controlled single-qubit gate.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
gate (Controlled): Controlled gate whose target operation is single-qubit.
|
|
76
|
+
Returns:
|
|
77
|
+
list[Gate]: Gate sequence computing the same unitary as `gate`.
|
|
78
|
+
"""
|
|
79
|
+
if len(gate.control_qubits) == 1:
|
|
80
|
+
return [gate]
|
|
81
|
+
|
|
82
|
+
c_last = gate.control_qubits[-1]
|
|
83
|
+
rest = gate.control_qubits[:-1]
|
|
84
|
+
|
|
85
|
+
V = _sqrt_of(gate.basic_gate)
|
|
86
|
+
Vd = _adjoint_of(V)
|
|
87
|
+
|
|
88
|
+
seq: List[Gate] = []
|
|
89
|
+
seq += _decompose(Controlled(c_last, basic_gate=V))
|
|
90
|
+
seq += _decompose(X(c_last).controlled(*rest))
|
|
91
|
+
seq += _decompose(Controlled(c_last, basic_gate=Vd))
|
|
92
|
+
seq += _decompose(X(c_last).controlled(*rest))
|
|
93
|
+
seq += _decompose(Controlled(*rest, basic_gate=V))
|
|
94
|
+
|
|
95
|
+
return seq
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _sqrt_of(gate: BasicGate) -> BasicGate:
|
|
99
|
+
"""Return a gate V whose square equals the provided gate.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
gate (BasicGate): Single-qubit gate to compute the principal square root for.
|
|
103
|
+
Returns:
|
|
104
|
+
BasicGate: New primitive V that satisfies V · V ≡ gate.
|
|
105
|
+
"""
|
|
106
|
+
q = gate.qubits[0]
|
|
107
|
+
|
|
108
|
+
# Identity: sqrt(I) = I
|
|
109
|
+
if isinstance(gate, I):
|
|
110
|
+
return I(q)
|
|
111
|
+
|
|
112
|
+
# Direct parametric rotations.
|
|
113
|
+
if isinstance(gate, RZ):
|
|
114
|
+
return RZ(q, phi=gate.phi / 2.0)
|
|
115
|
+
if isinstance(gate, RX):
|
|
116
|
+
return RX(q, theta=gate.theta / 2.0)
|
|
117
|
+
if isinstance(gate, RY):
|
|
118
|
+
return RY(q, theta=gate.theta / 2.0)
|
|
119
|
+
|
|
120
|
+
# Pauli gates via half-angle rotations.
|
|
121
|
+
if isinstance(gate, Z):
|
|
122
|
+
return RZ(q, phi=math.pi / 2.0)
|
|
123
|
+
if isinstance(gate, X):
|
|
124
|
+
return RX(q, theta=math.pi / 2.0)
|
|
125
|
+
if isinstance(gate, Y):
|
|
126
|
+
return RY(q, theta=math.pi / 2.0)
|
|
127
|
+
|
|
128
|
+
# Phase gate U1(phi) = diag(1, e^{iphi}), sqrt is U1(phi/2).
|
|
129
|
+
if isinstance(gate, U1):
|
|
130
|
+
return RZ(q, phi=gate.phi / 2.0)
|
|
131
|
+
|
|
132
|
+
# S and T: phase gates with known relation to RZ
|
|
133
|
+
# S = RZ(pi/2) ⇒ sqrt(S) = RZ(pi/4) ≡ T
|
|
134
|
+
if isinstance(gate, S):
|
|
135
|
+
return T(q)
|
|
136
|
+
|
|
137
|
+
# T = RZ(pi/4) ⇒ sqrt(T) = RZ(pi/8)
|
|
138
|
+
if isinstance(gate, T):
|
|
139
|
+
return RZ(q, phi=math.pi / 8.0)
|
|
140
|
+
|
|
141
|
+
# Build the 2x2 unitary matrix for gate
|
|
142
|
+
if isinstance(gate, (U2, U3, H, BasicGate)):
|
|
143
|
+
U = gate.matrix
|
|
144
|
+
else:
|
|
145
|
+
raise NotImplementedError(f"_sqrt_1q_gate_as_basis only supports 1-qubit gates; got {type(gate).__name__}")
|
|
146
|
+
|
|
147
|
+
# Compute a matrix square root V such that V @ V ≈ U.
|
|
148
|
+
Vs = _unitary_sqrt_2x2(U)
|
|
149
|
+
|
|
150
|
+
# Express V as a U3 on the same qubit. This introduces a new gate in U3 form
|
|
151
|
+
# for the *square root*, but leaves the original g untouched.
|
|
152
|
+
th, ph, lam = _zyz_from_unitary(Vs)
|
|
153
|
+
return U3(q, theta=th, phi=ph, gamma=lam)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _adjoint_of(gate: BasicGate) -> BasicGate:
|
|
157
|
+
"""Return the single-qubit adjoint (inverse) of a gate.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
gate (BasicGate): Gate whose inverse should be produced.
|
|
161
|
+
Returns:
|
|
162
|
+
BasicGate: Gate that when composed with `gate` yields the identity.
|
|
163
|
+
"""
|
|
164
|
+
q = gate.qubits[0]
|
|
165
|
+
|
|
166
|
+
# Identity: self-adjoint.
|
|
167
|
+
if isinstance(gate, I):
|
|
168
|
+
return I(q)
|
|
169
|
+
|
|
170
|
+
# Pauli & Hadamard: self-adjoint.
|
|
171
|
+
if isinstance(gate, X):
|
|
172
|
+
return X(q)
|
|
173
|
+
if isinstance(gate, Y):
|
|
174
|
+
return Y(q)
|
|
175
|
+
if isinstance(gate, Z):
|
|
176
|
+
return Z(q)
|
|
177
|
+
if isinstance(gate, H):
|
|
178
|
+
return H(q)
|
|
179
|
+
|
|
180
|
+
if isinstance(gate, RX):
|
|
181
|
+
return RX(q, theta=-gate.theta)
|
|
182
|
+
if isinstance(gate, RY):
|
|
183
|
+
return RY(q, theta=-gate.theta)
|
|
184
|
+
if isinstance(gate, RZ):
|
|
185
|
+
return RZ(q, phi=-gate.phi)
|
|
186
|
+
|
|
187
|
+
if isinstance(gate, U1):
|
|
188
|
+
# U1(gamma)† = U1(-gamma)
|
|
189
|
+
return RZ(q, phi=-gate.phi)
|
|
190
|
+
if isinstance(gate, U2):
|
|
191
|
+
# U2(phi, gamma)† = U3(pi/2, phi, gamma)† = U3(-pi/2, -phi, -gamma)
|
|
192
|
+
return U3(q, theta=-math.pi / 2.0, phi=-gate.gamma, gamma=-gate.phi)
|
|
193
|
+
if isinstance(gate, U3):
|
|
194
|
+
# U3(theta, phi, gamma)† = U3(-theta, -gamma, -phi)
|
|
195
|
+
return U3(q, theta=-gate.theta, phi=-gate.gamma, gamma=-gate.phi)
|
|
196
|
+
|
|
197
|
+
# S, T: phase gates about Z.
|
|
198
|
+
# S = RZ(pi/2) ⇒ S† = RZ(-pi/2)
|
|
199
|
+
if isinstance(gate, S):
|
|
200
|
+
return RZ(q, phi=-math.pi / 2.0)
|
|
201
|
+
|
|
202
|
+
# T = RZ(pi/4) ⇒ T† = RZ(-pi/4)
|
|
203
|
+
if isinstance(gate, T):
|
|
204
|
+
return RZ(q, phi=-math.pi / 4.0)
|
|
205
|
+
|
|
206
|
+
# ---------- Generic 1-qubit unitary via matrix adjoint ----------
|
|
207
|
+
|
|
208
|
+
if isinstance(gate, BasicGate) and gate.nqubits == 1:
|
|
209
|
+
U = gate.matrix
|
|
210
|
+
else:
|
|
211
|
+
raise NotImplementedError(f"_adjoint_1q only supports 1-qubit gates; got {type(gate).__name__}")
|
|
212
|
+
|
|
213
|
+
# Take the matrix adjoint U† and convert to ZYZ → U3.
|
|
214
|
+
U_dag = U.conj().T
|
|
215
|
+
theta, phi, gamma = _zyz_from_unitary(U_dag)
|
|
216
|
+
return U3(q, theta=theta, phi=phi, gamma=gamma)
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Copyright 2025 Qilimanjaro Quantum Tech
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
import math
|
|
15
|
+
|
|
16
|
+
import numpy as np
|
|
17
|
+
|
|
18
|
+
# ======================= numeric helpers =======================
|
|
19
|
+
|
|
20
|
+
_EPS = 1e-12
|
|
21
|
+
_SIG_DECIMALS = 12
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _wrap_angle(a: float) -> float:
|
|
25
|
+
"""Wrap an angle to the (-pi, pi] range.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
a (float): Angle value in radians.
|
|
29
|
+
Returns:
|
|
30
|
+
float: Angle mapped into the open-closed interval (-pi, pi].
|
|
31
|
+
"""
|
|
32
|
+
a = (a + math.pi) % (2.0 * math.pi) - math.pi
|
|
33
|
+
if a <= -math.pi:
|
|
34
|
+
a = math.pi
|
|
35
|
+
return a
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _zyz_from_unitary(U: np.ndarray) -> tuple[float, float, float]:
|
|
39
|
+
"""Recover ZYZ Euler angles from a 2x2 unitary.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
U (np.ndarray): 2x2 unitary matrix.
|
|
43
|
+
Returns:
|
|
44
|
+
tuple[float, float, float]: Tuple containing theta, phi and gamma angles.
|
|
45
|
+
Raises:
|
|
46
|
+
ValueError: If matrix is not 2x2.
|
|
47
|
+
"""
|
|
48
|
+
if U.shape != (2, 2):
|
|
49
|
+
raise ValueError("Expected 2x2 unitary for ZYZ decomposition.")
|
|
50
|
+
det = np.linalg.det(U)
|
|
51
|
+
if abs(det) < _EPS:
|
|
52
|
+
raise ValueError("Matrix is singular.")
|
|
53
|
+
# remove phase to a U3 rotation
|
|
54
|
+
phase = np.angle(U[0, 0])
|
|
55
|
+
U /= np.exp(1j * phase, dtype=complex)
|
|
56
|
+
|
|
57
|
+
a00, a01 = U[0, 0], U[0, 1]
|
|
58
|
+
a10, a11 = U[1, 0], U[1, 1]
|
|
59
|
+
theta = 2.0 * math.atan2(np.abs(a01), np.abs(a00))
|
|
60
|
+
s = math.sin(theta / 2.0)
|
|
61
|
+
|
|
62
|
+
if s < _EPS:
|
|
63
|
+
lam = _wrap_angle(np.angle(a11))
|
|
64
|
+
return (0.0, 0.0, lam)
|
|
65
|
+
|
|
66
|
+
phi = _wrap_angle(np.angle(a10))
|
|
67
|
+
lam = _wrap_angle(np.angle(-a01))
|
|
68
|
+
return (theta, phi, lam)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _unitary_sqrt_2x2(U: np.ndarray) -> np.ndarray:
|
|
72
|
+
"""Compute the principal square root of a 2x2 unitary.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
U (np.ndarray): 2x2 unitary matrix.
|
|
76
|
+
Returns:
|
|
77
|
+
np.ndarray: Matrix V such that V · V equals U.
|
|
78
|
+
"""
|
|
79
|
+
w, V = np.linalg.eig(U)
|
|
80
|
+
ph = np.angle(w)
|
|
81
|
+
sqrt_w = np.exp(0.5j * ph)
|
|
82
|
+
return V @ np.diag(sqrt_w) @ np.linalg.inv(V)
|
qilisdk/digital/gates.py
CHANGED
|
@@ -20,8 +20,8 @@ import numpy as np
|
|
|
20
20
|
from scipy.linalg import expm
|
|
21
21
|
from typing_extensions import Self
|
|
22
22
|
|
|
23
|
-
from qilisdk.
|
|
24
|
-
from qilisdk.
|
|
23
|
+
from qilisdk.core.parameterizable import Parameterizable
|
|
24
|
+
from qilisdk.core.variables import Parameter
|
|
25
25
|
from qilisdk.yaml import yaml
|
|
26
26
|
|
|
27
27
|
from .exceptions import (
|
|
@@ -212,13 +212,23 @@ class BasicGate(Gate):
|
|
|
212
212
|
Represents a quantum gate that can be used in quantum circuits.
|
|
213
213
|
"""
|
|
214
214
|
|
|
215
|
-
def __init__(self, target_qubits: tuple[int, ...], parameters: dict[str, Parameter] =
|
|
215
|
+
def __init__(self, target_qubits: tuple[int, ...], parameters: dict[str, Parameter] | None = None) -> None:
|
|
216
|
+
"""Build a basic gate.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
target_qubits (tuple[int, ...]): Qubit indices the gate acts on. Duplicate indices are rejected.
|
|
220
|
+
parameters (dict[str, Parameter] | None): Optional parameter objects keyed by label for parameterized gates.
|
|
221
|
+
|
|
222
|
+
Raises:
|
|
223
|
+
ValueError: if duplicate target qubits are found.
|
|
224
|
+
"""
|
|
216
225
|
# Check for duplicate integers in target_qubits.
|
|
226
|
+
super(BasicGate, self).__init__()
|
|
217
227
|
if len(target_qubits) != len(set(target_qubits)):
|
|
218
228
|
raise ValueError("Duplicate target qubits found.")
|
|
219
229
|
|
|
220
230
|
self._target_qubits: tuple[int, ...] = target_qubits
|
|
221
|
-
self._parameters: dict[str, Parameter] = parameters
|
|
231
|
+
self._parameters: dict[str, Parameter] = parameters or {}
|
|
222
232
|
self._matrix: np.ndarray = self._generate_matrix()
|
|
223
233
|
|
|
224
234
|
@property
|
|
@@ -924,7 +934,7 @@ class U2(BasicGate):
|
|
|
924
934
|
``U2(phi, gamma) = U2_qiskit/pennylane(phi, gamma) = exp(i*(phi+gamma)/2) U2_qibo(phi, gamma)``
|
|
925
935
|
|
|
926
936
|
Other unitaries you can get from this one are:
|
|
927
|
-
- ``U2(phi=0, gamma=
|
|
937
|
+
- ``U2(phi=0, gamma=pi) = H``
|
|
928
938
|
- ``U2(phi=0, gamma=0) = RY(theta=pi/2)``
|
|
929
939
|
- ``U2(phi=-pi/2, gamma=pi/2) = RX(theta=pi/2)``
|
|
930
940
|
"""
|
|
@@ -11,8 +11,15 @@
|
|
|
11
11
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
|
-
from .experiment_functional import ExperimentFunctional, RabiExperiment, T1Experiment
|
|
15
|
-
from .experiment_result import
|
|
14
|
+
from .experiment_functional import ExperimentFunctional, RabiExperiment, T1Experiment, T2Experiment, TwoTonesExperiment
|
|
15
|
+
from .experiment_result import (
|
|
16
|
+
Dimension,
|
|
17
|
+
ExperimentResult,
|
|
18
|
+
RabiExperimentResult,
|
|
19
|
+
T1ExperimentResult,
|
|
20
|
+
T2ExperimentResult,
|
|
21
|
+
TwoTonesExperimentResult,
|
|
22
|
+
)
|
|
16
23
|
|
|
17
24
|
__all__ = [
|
|
18
25
|
"Dimension",
|
|
@@ -22,4 +29,8 @@ __all__ = [
|
|
|
22
29
|
"RabiExperimentResult",
|
|
23
30
|
"T1Experiment",
|
|
24
31
|
"T1ExperimentResult",
|
|
32
|
+
"T2Experiment",
|
|
33
|
+
"T2ExperimentResult",
|
|
34
|
+
"TwoTonesExperiment",
|
|
35
|
+
"TwoTonesExperimentResult",
|
|
25
36
|
]
|
|
@@ -16,12 +16,14 @@ from __future__ import annotations
|
|
|
16
16
|
from abc import ABC
|
|
17
17
|
from typing import TYPE_CHECKING, ClassVar, Generic, TypeVar
|
|
18
18
|
|
|
19
|
-
from qilisdk.
|
|
20
|
-
from qilisdk.speqtrum.experiments.experiment_result import (
|
|
19
|
+
from qilisdk.experiments.experiment_result import (
|
|
21
20
|
ExperimentResult,
|
|
22
21
|
RabiExperimentResult,
|
|
23
22
|
T1ExperimentResult,
|
|
23
|
+
T2ExperimentResult,
|
|
24
|
+
TwoTonesExperimentResult,
|
|
24
25
|
)
|
|
26
|
+
from qilisdk.functionals.functional import Functional
|
|
25
27
|
from qilisdk.yaml import yaml
|
|
26
28
|
|
|
27
29
|
if TYPE_CHECKING:
|
|
@@ -122,3 +124,89 @@ class T1Experiment(ExperimentFunctional[T1ExperimentResult]):
|
|
|
122
124
|
np.ndarray: The set of delay durations (in nanoseconds) used in the T1 experiment.
|
|
123
125
|
"""
|
|
124
126
|
return self._wait_duration_values
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@yaml.register_class
|
|
130
|
+
class T2Experiment(ExperimentFunctional[T2ExperimentResult]):
|
|
131
|
+
"""T2 dephasing experiment functional for a single qubit.
|
|
132
|
+
|
|
133
|
+
This functional defines a Ramsey/spin-echo style T2 experiment, where
|
|
134
|
+
the free-evolution delay between phase-sensitive pulses is swept to
|
|
135
|
+
extract the qubit coherence time.
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
result_type: ClassVar[type[T2ExperimentResult]] = T2ExperimentResult
|
|
139
|
+
"""Result type returned by this functional."""
|
|
140
|
+
|
|
141
|
+
def __init__(self, qubit: int, wait_duration_values: np.ndarray) -> None:
|
|
142
|
+
"""Initialize a T2 dephasing experiment functional.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
qubit (int): The physical qubit index on which the experiment is performed.
|
|
146
|
+
wait_duration_values (np.ndarray): Array of free-evolution delays
|
|
147
|
+
(in nanoseconds) between the phase-sensitive pulses.
|
|
148
|
+
"""
|
|
149
|
+
super().__init__(qubit=qubit)
|
|
150
|
+
self._wait_duration_values: np.ndarray = wait_duration_values
|
|
151
|
+
|
|
152
|
+
@property
|
|
153
|
+
def wait_duration_values(self) -> np.ndarray:
|
|
154
|
+
"""Free-evolution delay sweep values.
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
np.ndarray: The set of delay durations (in nanoseconds) used to estimate T2.
|
|
158
|
+
"""
|
|
159
|
+
return self._wait_duration_values
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
@yaml.register_class
|
|
163
|
+
class TwoTonesExperiment(ExperimentFunctional[TwoTonesExperimentResult]):
|
|
164
|
+
"""Two-tone spectroscopy functional for a single qubit.
|
|
165
|
+
|
|
166
|
+
Sweeps a drive tone frequency while monitoring the readout tone to
|
|
167
|
+
identify the qubit transition frequency.
|
|
168
|
+
"""
|
|
169
|
+
|
|
170
|
+
result_type: ClassVar[type[TwoTonesExperimentResult]] = TwoTonesExperimentResult
|
|
171
|
+
"""Result type returned by this functional."""
|
|
172
|
+
|
|
173
|
+
def __init__(self, qubit: int, frequency_start: float, frequency_stop: float, frequency_step: float) -> None:
|
|
174
|
+
"""Initialize a two-tone spectroscopy functional.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
qubit (int): The physical qubit index on which the experiment is performed.
|
|
178
|
+
frequency_start (float): Starting frequency of the swept drive tone (in Hz).
|
|
179
|
+
frequency_stop (float): Ending frequency of the swept drive tone (in Hz).
|
|
180
|
+
frequency_step (float): Frequency increment between sweep points (in Hz).
|
|
181
|
+
"""
|
|
182
|
+
super().__init__(qubit=qubit)
|
|
183
|
+
self._frequency_start: float = frequency_start
|
|
184
|
+
self._frequency_stop: float = frequency_stop
|
|
185
|
+
self._frequency_step: float = frequency_step
|
|
186
|
+
|
|
187
|
+
@property
|
|
188
|
+
def frequency_start(self) -> float:
|
|
189
|
+
"""Start frequency for the drive tone sweep.
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
float: Starting frequency of the drive tone (in Hz).
|
|
193
|
+
"""
|
|
194
|
+
return self._frequency_start
|
|
195
|
+
|
|
196
|
+
@property
|
|
197
|
+
def frequency_stop(self) -> float:
|
|
198
|
+
"""Stop frequency for the drive tone sweep.
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
float: Ending frequency of the drive tone (in Hz).
|
|
202
|
+
"""
|
|
203
|
+
return self._frequency_stop
|
|
204
|
+
|
|
205
|
+
@property
|
|
206
|
+
def frequency_step(self) -> float:
|
|
207
|
+
"""Step size for the drive tone sweep.
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
float: Frequency increment between sweep points (in Hz).
|
|
211
|
+
"""
|
|
212
|
+
return self._frequency_step
|
|
@@ -229,3 +229,19 @@ class T1ExperimentResult(ExperimentResult):
|
|
|
229
229
|
|
|
230
230
|
plot_title: ClassVar[str] = "T1"
|
|
231
231
|
"""Default title for T1 experiment plots."""
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
@yaml.register_class
|
|
235
|
+
class T2ExperimentResult(ExperimentResult):
|
|
236
|
+
"""Result container for T2 dephasing experiments."""
|
|
237
|
+
|
|
238
|
+
plot_title: ClassVar[str] = "T2"
|
|
239
|
+
"""Default title for T2 experiment plots."""
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
@yaml.register_class
|
|
243
|
+
class TwoTonesExperimentResult(ExperimentResult):
|
|
244
|
+
"""Result container for TwoTones experiments."""
|
|
245
|
+
|
|
246
|
+
plot_title: ClassVar[str] = "TwoTones"
|
|
247
|
+
"""Default title for TwoTones experiment plots."""
|
|
@@ -16,7 +16,7 @@ from __future__ import annotations
|
|
|
16
16
|
from abc import ABC
|
|
17
17
|
from typing import ClassVar, Generic, TypeVar
|
|
18
18
|
|
|
19
|
-
from qilisdk.
|
|
19
|
+
from qilisdk.core.parameterizable import Parameterizable
|
|
20
20
|
from qilisdk.functionals.functional_result import FunctionalResult
|
|
21
21
|
|
|
22
22
|
TResult_co = TypeVar("TResult_co", bound=FunctionalResult, covariant=True)
|
|
@@ -35,5 +35,5 @@ class Functional(ABC):
|
|
|
35
35
|
|
|
36
36
|
class PrimitiveFunctional(Parameterizable, Functional, ABC, Generic[TResult_co]):
|
|
37
37
|
"""
|
|
38
|
-
Base class for functionals backed by a :class:`~qilisdk.
|
|
38
|
+
Base class for functionals backed by a :class:`~qilisdk.core.parameterizable.Parameterizable` object.
|
|
39
39
|
"""
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
|
-
from qilisdk.
|
|
14
|
+
from qilisdk.core.result import Result
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class FunctionalResult(Result):
|
qilisdk/functionals/sampling.py
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
from typing import ClassVar
|
|
15
15
|
|
|
16
|
-
from qilisdk.
|
|
16
|
+
from qilisdk.core.variables import ComparisonTerm, RealNumber
|
|
17
17
|
from qilisdk.digital.circuit import Circuit
|
|
18
18
|
from qilisdk.functionals.functional import PrimitiveFunctional
|
|
19
19
|
from qilisdk.functionals.sampling_result import SamplingResult
|
|
@@ -79,3 +79,10 @@ class Sampling(PrimitiveFunctional[SamplingResult]):
|
|
|
79
79
|
def set_parameter_bounds(self, ranges: dict[str, tuple[float, float]]) -> None:
|
|
80
80
|
"""Update the admissible range for selected circuit parameters."""
|
|
81
81
|
self.circuit.set_parameter_bounds(ranges)
|
|
82
|
+
|
|
83
|
+
def get_constraints(self) -> list[ComparisonTerm]:
|
|
84
|
+
"""Expose parameter constraints defined on the circuit.
|
|
85
|
+
Returns:
|
|
86
|
+
list[ComparisonTerm]: a list of constraints on the circuit parameters.
|
|
87
|
+
"""
|
|
88
|
+
return self.circuit.get_constraints()
|
|
@@ -15,8 +15,8 @@ from typing import ClassVar
|
|
|
15
15
|
|
|
16
16
|
from qilisdk.analog.hamiltonian import Hamiltonian, PauliOperator
|
|
17
17
|
from qilisdk.analog.schedule import Schedule
|
|
18
|
-
from qilisdk.
|
|
19
|
-
from qilisdk.
|
|
18
|
+
from qilisdk.core.qtensor import QTensor
|
|
19
|
+
from qilisdk.core.variables import ComparisonTerm, RealNumber
|
|
20
20
|
from qilisdk.functionals.functional import PrimitiveFunctional
|
|
21
21
|
from qilisdk.functionals.time_evolution_result import TimeEvolutionResult
|
|
22
22
|
from qilisdk.yaml import yaml
|
|
@@ -31,11 +31,11 @@ class TimeEvolution(PrimitiveFunctional[TimeEvolutionResult]):
|
|
|
31
31
|
.. code-block:: python
|
|
32
32
|
|
|
33
33
|
from qilisdk.analog import Schedule, Hamiltonian, Z
|
|
34
|
-
from qilisdk.
|
|
34
|
+
from qilisdk.core import ket
|
|
35
35
|
from qilisdk.functionals.time_evolution import TimeEvolution
|
|
36
36
|
|
|
37
37
|
h0 = Z(0)
|
|
38
|
-
schedule = Schedule(
|
|
38
|
+
schedule = Schedule(hamiltonians={"h0": h0}, total_time=10.0)
|
|
39
39
|
functional = TimeEvolution(schedule, observables=[Z(0), X(0)], initial_state=ket(0))
|
|
40
40
|
"""
|
|
41
41
|
|
|
@@ -96,3 +96,7 @@ class TimeEvolution(PrimitiveFunctional[TimeEvolutionResult]):
|
|
|
96
96
|
def set_parameter_bounds(self, ranges: dict[str, tuple[float, float]]) -> None:
|
|
97
97
|
"""Update bounds for selected schedule parameters."""
|
|
98
98
|
self.schedule.set_parameter_bounds(ranges)
|
|
99
|
+
|
|
100
|
+
def get_constraints(self) -> list[ComparisonTerm]:
|
|
101
|
+
"""Return the parameter constraints defined within the underlying schedule."""
|
|
102
|
+
return self.schedule.get_constraints()
|
|
@@ -15,8 +15,8 @@ from pprint import pformat
|
|
|
15
15
|
|
|
16
16
|
import numpy as np
|
|
17
17
|
|
|
18
|
-
from qilisdk.
|
|
19
|
-
from qilisdk.
|
|
18
|
+
from qilisdk.core.model import Model
|
|
19
|
+
from qilisdk.core.qtensor import QTensor
|
|
20
20
|
from qilisdk.functionals.functional_result import FunctionalResult
|
|
21
21
|
from qilisdk.yaml import yaml
|
|
22
22
|
|