spin-pulse 1.0__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.
- spin_pulse/__init__.py +41 -0
- spin_pulse/characterization/__init__.py +16 -0
- spin_pulse/characterization/average_superop.py +219 -0
- spin_pulse/characterization/ramsey.py +120 -0
- spin_pulse/environment/__init__.py +23 -0
- spin_pulse/environment/experimental_environment.py +158 -0
- spin_pulse/environment/noise/__init__.py +32 -0
- spin_pulse/environment/noise/noise_time_trace.py +115 -0
- spin_pulse/environment/noise/pink.py +197 -0
- spin_pulse/environment/noise/quasistatic.py +94 -0
- spin_pulse/environment/noise/white.py +91 -0
- spin_pulse/transpilation/__init__.py +39 -0
- spin_pulse/transpilation/dynamical_decoupling.py +35 -0
- spin_pulse/transpilation/hardware_specs.py +196 -0
- spin_pulse/transpilation/instructions/__init__.py +30 -0
- spin_pulse/transpilation/instructions/idle.py +166 -0
- spin_pulse/transpilation/instructions/pulse_instruction.py +49 -0
- spin_pulse/transpilation/instructions/rotations.py +583 -0
- spin_pulse/transpilation/passes/__init__.py +14 -0
- spin_pulse/transpilation/passes/rzz_echo.py +74 -0
- spin_pulse/transpilation/pulse_circuit.py +693 -0
- spin_pulse/transpilation/pulse_layer.py +256 -0
- spin_pulse/transpilation/pulse_sequence.py +280 -0
- spin_pulse/transpilation/utils.py +254 -0
- spin_pulse/version.py +38 -0
- spin_pulse-1.0.dist-info/METADATA +78 -0
- spin_pulse-1.0.dist-info/RECORD +30 -0
- spin_pulse-1.0.dist-info/WHEEL +5 -0
- spin_pulse-1.0.dist-info/licenses/LICENCE.txt +177 -0
- spin_pulse-1.0.dist-info/top_level.txt +1 -0
spin_pulse/__init__.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# --------------------------------------------------------------------------------------
|
|
2
|
+
# This code is part of SpinPulse.
|
|
3
|
+
#
|
|
4
|
+
# (C) Copyright Quobly 2025.
|
|
5
|
+
#
|
|
6
|
+
# This code is licensed under the Apache License, Version 2.0. You may
|
|
7
|
+
# obtain a copy of this license in the LICENSE.txt file in the root directory
|
|
8
|
+
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
|
|
9
|
+
#
|
|
10
|
+
# Any modifications or derivative works of this code must retain this
|
|
11
|
+
# copyright notice, and modified files need to carry a notice indicating
|
|
12
|
+
# that they have been altered from the originals.
|
|
13
|
+
# --------------------------------------------------------------------------------------
|
|
14
|
+
"""
|
|
15
|
+
:mod:`SpinPulse` is an open-source python package for simulating silicon based spin qubits at the pulse-level.
|
|
16
|
+
|
|
17
|
+
Modules
|
|
18
|
+
----------------
|
|
19
|
+
:mod:`spin_pulse.transpilation`
|
|
20
|
+
The `transpilation` module provides a set of classes that enable the simulation of quantum circuits defined in `Qiskit` on silicon-based spin-qubit hardware models.
|
|
21
|
+
|
|
22
|
+
:mod:`spin_pulse.environment`
|
|
23
|
+
The `environment` module provides a set of classes for defining and configuring a quantum experimental environment tailored to spin-qubit systems.
|
|
24
|
+
|
|
25
|
+
:mod:`spin_pulse.characterization`
|
|
26
|
+
The `characterization` module provides a set of functions for characterizing spin-qubit control operations and quantifying noise strength.
|
|
27
|
+
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from .environment.experimental_environment import ExperimentalEnvironment
|
|
31
|
+
from .transpilation.dynamical_decoupling import DynamicalDecoupling
|
|
32
|
+
from .transpilation.hardware_specs import HardwareSpecs, Shape
|
|
33
|
+
from .transpilation.pulse_circuit import PulseCircuit
|
|
34
|
+
|
|
35
|
+
__all__ = [
|
|
36
|
+
"DynamicalDecoupling",
|
|
37
|
+
"ExperimentalEnvironment",
|
|
38
|
+
"HardwareSpecs",
|
|
39
|
+
"PulseCircuit",
|
|
40
|
+
"Shape",
|
|
41
|
+
]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# --------------------------------------------------------------------------------------
|
|
2
|
+
# This code is part of SpinPulse.
|
|
3
|
+
#
|
|
4
|
+
# (C) Copyright Quobly 2025.
|
|
5
|
+
#
|
|
6
|
+
# This code is licensed under the Apache License, Version 2.0. You may
|
|
7
|
+
# obtain a copy of this license in the LICENSE.txt file in the root directory
|
|
8
|
+
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
|
|
9
|
+
#
|
|
10
|
+
# Any modifications or derivative works of this code must retain this
|
|
11
|
+
# copyright notice, and modified files need to carry a notice indicating
|
|
12
|
+
# that they have been altered from the originals.
|
|
13
|
+
# --------------------------------------------------------------------------------------
|
|
14
|
+
"""This module provides a set of functions for characterizing spin-qubit control operations and quantifying noise intensity.
|
|
15
|
+
It includes methods for performing Ramsey experiments, comparing quantum circuits, and computing average quantum channels.
|
|
16
|
+
"""
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# --------------------------------------------------------------------------------------
|
|
2
|
+
# This code is part of SpinPulse.
|
|
3
|
+
#
|
|
4
|
+
# (C) Copyright Quobly 2025.
|
|
5
|
+
#
|
|
6
|
+
# This code is licensed under the Apache License, Version 2.0. You may
|
|
7
|
+
# obtain a copy of this license in the LICENSE.txt file in the root directory
|
|
8
|
+
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
|
|
9
|
+
#
|
|
10
|
+
# Any modifications or derivative works of this code must retain this
|
|
11
|
+
# copyright notice, and modified files need to carry a notice indicating
|
|
12
|
+
# that they have been altered from the originals.
|
|
13
|
+
# --------------------------------------------------------------------------------------
|
|
14
|
+
"""
|
|
15
|
+
Utilities to analyze and visualize quantum super-Operators.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import itertools
|
|
19
|
+
|
|
20
|
+
import matplotlib as mpl
|
|
21
|
+
import matplotlib.pyplot as plt
|
|
22
|
+
import numpy as np
|
|
23
|
+
import qiskit as qi
|
|
24
|
+
from qiskit.quantum_info import (
|
|
25
|
+
Chi,
|
|
26
|
+
Operator,
|
|
27
|
+
Pauli,
|
|
28
|
+
SuperOp,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def compare_circuits(circ1: qi.QuantumCircuit, circ2: qi.QuantumCircuit):
|
|
33
|
+
"""
|
|
34
|
+
Compare two quantum circuits by plotting the matrix elements of their
|
|
35
|
+
corresponding unitary operators.
|
|
36
|
+
|
|
37
|
+
This function converts both circuits into unitary matrices using
|
|
38
|
+
qiskit.quantum_info.Operator.
|
|
39
|
+
The global phase is removed before comparison. Three scatter plots are
|
|
40
|
+
generated: real parts, imaginary parts, and absolute values of the matrix
|
|
41
|
+
elements. A diagonal reference line is shown, and the squared distance
|
|
42
|
+
between the two matrices is displayed.
|
|
43
|
+
|
|
44
|
+
Parameters:
|
|
45
|
+
circ1 (qiskit.QuantumCircuit): First circuit to compare.
|
|
46
|
+
circ2 (qiskit.QuantumCircuit): Second circuit to compare.
|
|
47
|
+
|
|
48
|
+
Notes:
|
|
49
|
+
The global phase is aligned using the matrix element with maximum magnitude.
|
|
50
|
+
This function is useful to visually validate the equivalence of two circuits
|
|
51
|
+
after transformations such as transpilation or pulse compilation.
|
|
52
|
+
|
|
53
|
+
"""
|
|
54
|
+
data1 = (Operator.from_circuit(circ1).data).flatten()
|
|
55
|
+
data2 = (Operator.from_circuit(circ2).data).flatten()
|
|
56
|
+
i_phase = np.argmax(abs(data1))
|
|
57
|
+
phase1 = np.angle(data1[i_phase])
|
|
58
|
+
data1 *= np.exp(-1j * phase1)
|
|
59
|
+
phase2 = np.angle(data2[i_phase])
|
|
60
|
+
data2 *= np.exp(-1j * phase2)
|
|
61
|
+
plt.plot(np.real(data1), np.real(data2), "o", label="real")
|
|
62
|
+
plt.plot(np.imag(data1), np.imag(data2), "x", label="imag")
|
|
63
|
+
plt.plot(np.abs(data1), np.abs(data2), "*", label="abs")
|
|
64
|
+
|
|
65
|
+
a = np.max(abs(data1))
|
|
66
|
+
plt.plot([-a, a], [-a, a], "--k")
|
|
67
|
+
plt.text(-a, a, f"distance {np.sum(np.abs(data1 - data2) ** 2)}")
|
|
68
|
+
plt.xlabel("circ 1 matrix elements")
|
|
69
|
+
plt.ylabel("circ 2 matrix elements")
|
|
70
|
+
plt.legend(loc=0)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def plot_chi_matrix(superop: dict[str, SuperOp], threshold=None) -> plt.Figure:
|
|
74
|
+
"""Plot the chi-matrix elements for one or multiple quantum superop.
|
|
75
|
+
|
|
76
|
+
The chi-matrix is computed for each channel and plotted as bar plots
|
|
77
|
+
(real and imaginary parts). If a threshold is provided, only elements
|
|
78
|
+
with absolute value greater than the threshold (from the first channel)
|
|
79
|
+
are shown.
|
|
80
|
+
|
|
81
|
+
Channels whose key contains the substring ``"analytical"`` are plotted
|
|
82
|
+
with transparent bars and line styles, while the others are plotted as
|
|
83
|
+
semi-transparent filled bars.
|
|
84
|
+
|
|
85
|
+
Parameters:
|
|
86
|
+
superop (dict[str, qiskit.quantum_info.SuperOp or qiskit.quantum_info.Channel]):
|
|
87
|
+
Dictionary mapping labels to quantum super-Operator. Each value must
|
|
88
|
+
be compatible with ``qiskit.quantum_info.Choi``/``Chi`` so that
|
|
89
|
+
``Chi(superop[key]).data`` returns the chi-matrix.
|
|
90
|
+
threshold (float | None): If not ``None``, only chi-matrix elements with
|
|
91
|
+
absolute value greater than ``threshold`` (as determined from the
|
|
92
|
+
first channel in ``superop``) are included in the plot.
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
matplotlib.figure.Figure: The figure object containing the chi-matrix plot.
|
|
96
|
+
|
|
97
|
+
"""
|
|
98
|
+
n_qb = int(np.log2(next(iter(superop.values())).data.shape[0]) / 2)
|
|
99
|
+
mpl.rcParams["font.size"] = 22
|
|
100
|
+
fig = plt.figure(figsize=(10, 5))
|
|
101
|
+
# Generate Pauli basis labels
|
|
102
|
+
paulis = ["I", "X", "Y", "Z"]
|
|
103
|
+
pauli_labels = ["".join(p) for p in itertools.product(paulis, repeat=n_qb)]
|
|
104
|
+
full_labels = [f"${p1}.{p2}$" for p1 in pauli_labels for p2 in pauli_labels]
|
|
105
|
+
|
|
106
|
+
counter = 0
|
|
107
|
+
lines_style = ["-", "--", ":", "-."]
|
|
108
|
+
|
|
109
|
+
x = np.arange(len(full_labels))
|
|
110
|
+
|
|
111
|
+
for index, key in enumerate(superop.keys()):
|
|
112
|
+
chi_matrix = Chi(superop[key]).data
|
|
113
|
+
|
|
114
|
+
# Flatten matrix into list of values and labels
|
|
115
|
+
values = chi_matrix.flatten()
|
|
116
|
+
|
|
117
|
+
if threshold is not None and index == 0:
|
|
118
|
+
indices = [i for i in range(len(values)) if np.abs(values[i]) > threshold]
|
|
119
|
+
x = np.array(range(len(indices)))
|
|
120
|
+
full_labels = [full_labels[i] for i in indices]
|
|
121
|
+
if threshold is not None:
|
|
122
|
+
values = values[indices]
|
|
123
|
+
|
|
124
|
+
if "analytical" in key:
|
|
125
|
+
plt.bar(
|
|
126
|
+
x,
|
|
127
|
+
np.real(values),
|
|
128
|
+
tick_label=full_labels,
|
|
129
|
+
label=f"{key}" + " (real)",
|
|
130
|
+
facecolor="none",
|
|
131
|
+
edgecolor="tab:blue",
|
|
132
|
+
linestyle=lines_style[counter % len(lines_style)],
|
|
133
|
+
linewidth=1.0,
|
|
134
|
+
)
|
|
135
|
+
plt.bar(
|
|
136
|
+
x,
|
|
137
|
+
np.imag(values),
|
|
138
|
+
tick_label=full_labels,
|
|
139
|
+
label=f"{key}" + " (imag)",
|
|
140
|
+
facecolor="none",
|
|
141
|
+
edgecolor="tab:orange",
|
|
142
|
+
linestyle=lines_style[counter % len(lines_style)],
|
|
143
|
+
linewidth=1.0,
|
|
144
|
+
)
|
|
145
|
+
counter += 1
|
|
146
|
+
|
|
147
|
+
else:
|
|
148
|
+
plt.bar(
|
|
149
|
+
x,
|
|
150
|
+
np.real(values),
|
|
151
|
+
alpha=0.3,
|
|
152
|
+
tick_label=full_labels,
|
|
153
|
+
label=f"{key}" + " (real)",
|
|
154
|
+
)
|
|
155
|
+
plt.bar(
|
|
156
|
+
x,
|
|
157
|
+
np.imag(values),
|
|
158
|
+
alpha=0.3,
|
|
159
|
+
tick_label=full_labels,
|
|
160
|
+
label=f"{key}" + " (imag)",
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
plt.xticks(rotation=90)
|
|
164
|
+
plt.ylabel(r"$\chi$")
|
|
165
|
+
plt.legend()
|
|
166
|
+
plt.grid(True, axis="y", linestyle="--", alpha=0.5)
|
|
167
|
+
plt.tight_layout()
|
|
168
|
+
return fig
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def get_superop_from_paulidict(pauli_dict: dict[str, complex]) -> SuperOp:
|
|
172
|
+
r"""Return the SuperOp corresponding to a Pauli decomposition given as
|
|
173
|
+
a dictionary.
|
|
174
|
+
|
|
175
|
+
The input is a mapping from tensor-product Pauli labels (e.g., "IXZ",
|
|
176
|
+
"ZZ", "I") to complex coefficients. For an n-qubit system, each label
|
|
177
|
+
must be a string of length n, with characters drawn from
|
|
178
|
+
``{"I", "X", "Y", "Z"}``.
|
|
179
|
+
|
|
180
|
+
The function builds the operator
|
|
181
|
+
|
|
182
|
+
.. math::
|
|
183
|
+
|
|
184
|
+
O = \sum_{P} c_P P,
|
|
185
|
+
|
|
186
|
+
where :math:`P` runs over Pauli strings and :math:`c_P` are the provided
|
|
187
|
+
coefficients, and then wraps it as a ``qiskit.quantum_info.SuperOp``.
|
|
188
|
+
|
|
189
|
+
Parameters:
|
|
190
|
+
pauli_dict (dict[str, complex]): Dictionary mapping Pauli labels
|
|
191
|
+
(e.g., "IX", "ZZI") to complex coefficients.
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
qiskit.quantum_info.SuperOp: The resulting quantum channel represented
|
|
195
|
+
as a ``SuperOp`` acting on the corresponding Hilbert space dimension.
|
|
196
|
+
|
|
197
|
+
"""
|
|
198
|
+
keys = list(pauli_dict.keys())
|
|
199
|
+
|
|
200
|
+
# Validate that all keys are even in Pauli matrices
|
|
201
|
+
for i in range(len(keys)):
|
|
202
|
+
if len(keys[i]) % 2 != 0:
|
|
203
|
+
raise ValueError(
|
|
204
|
+
"All Pauli keys must have an even number of single-qubit Pauli matrices "
|
|
205
|
+
f"(got {len(keys[i])})."
|
|
206
|
+
)
|
|
207
|
+
# Validate that all keys have the same length
|
|
208
|
+
for i in range(1, len(keys)):
|
|
209
|
+
if len(keys[i - 1]) != len(keys[i]):
|
|
210
|
+
raise ValueError(
|
|
211
|
+
"All keys must have the same length "
|
|
212
|
+
f"(got {len(keys[i - 1])} and {len(keys[i])})."
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
super_ops = []
|
|
216
|
+
for label, coeff in pauli_dict.items():
|
|
217
|
+
P = Pauli(label)
|
|
218
|
+
super_ops.append(coeff * SuperOp(P.to_matrix()))
|
|
219
|
+
return sum(super_ops)
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# --------------------------------------------------------------------------------------
|
|
2
|
+
# This code is part of SpinPulse.
|
|
3
|
+
#
|
|
4
|
+
# (C) Copyright Quobly 2025.
|
|
5
|
+
#
|
|
6
|
+
# This code is licensed under the Apache License, Version 2.0. You may
|
|
7
|
+
# obtain a copy of this license in the LICENSE.txt file in the root directory
|
|
8
|
+
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
|
|
9
|
+
#
|
|
10
|
+
# Any modifications or derivative works of this code must retain this
|
|
11
|
+
# copyright notice, and modified files need to carry a notice indicating
|
|
12
|
+
# that they have been altered from the originals.
|
|
13
|
+
# --------------------------------------------------------------------------------------
|
|
14
|
+
"""Helper functions to carry out Ramsey experiments."""
|
|
15
|
+
|
|
16
|
+
import numpy as np
|
|
17
|
+
import qiskit as qi
|
|
18
|
+
from qiskit.quantum_info import Operator
|
|
19
|
+
from tqdm import tqdm
|
|
20
|
+
|
|
21
|
+
from ..environment.experimental_environment import ExperimentalEnvironment
|
|
22
|
+
from ..transpilation.hardware_specs import HardwareSpecs
|
|
23
|
+
from ..transpilation.pulse_circuit import PulseCircuit
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_ramsey_circuit(
|
|
27
|
+
duration: int,
|
|
28
|
+
hardware_specs: HardwareSpecs,
|
|
29
|
+
exp_env: ExperimentalEnvironment | None = None,
|
|
30
|
+
):
|
|
31
|
+
"""Construct a pulse-level Ramsey experiment.
|
|
32
|
+
|
|
33
|
+
This function builds a single-qubit Ramsey sequence consisting of a
|
|
34
|
+
Hadamard gate, an idle period of a specified duration, and a second
|
|
35
|
+
Hadamard gate. The circuit is transpiled into the hardware native gate
|
|
36
|
+
set and converted into a PulseCircuit. If an experimental environment is
|
|
37
|
+
provided, noise time traces are attached to the resulting pulse-level
|
|
38
|
+
circuit.
|
|
39
|
+
|
|
40
|
+
Parameters:
|
|
41
|
+
duration (int): Duration of the free-evolution period (in the discrete
|
|
42
|
+
time unit used by the hardware model).
|
|
43
|
+
hardware_specs (HardwareSpecs): Hardware configuration used to
|
|
44
|
+
transpile the logical Ramsey sequence into native instructions, and
|
|
45
|
+
to generate the pulse sequences of the PulseCircuit.
|
|
46
|
+
exp_env (ExperimentalEnvironment | None): Optional noise environment
|
|
47
|
+
from which time traces are created and attached to the PulseCircuit.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
PulseCircuit: Pulse-level representation of the Ramsey experiment,
|
|
51
|
+
optionally including noise time traces.
|
|
52
|
+
|
|
53
|
+
"""
|
|
54
|
+
qreg = qi.QuantumRegister(1)
|
|
55
|
+
circ = qi.QuantumCircuit(qreg)
|
|
56
|
+
circ.h(0)
|
|
57
|
+
circ.delay(int(duration))
|
|
58
|
+
|
|
59
|
+
circ.h(0)
|
|
60
|
+
isa_circ = hardware_specs.gate_transpile(circ)
|
|
61
|
+
pulse_circuit = PulseCircuit.from_circuit(isa_circ, hardware_specs, exp_env=exp_env)
|
|
62
|
+
return pulse_circuit
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def get_contrast(pulse_circuit: PulseCircuit) -> float:
|
|
66
|
+
"""Compute the population contrast of a Ramsey experiment.
|
|
67
|
+
|
|
68
|
+
The pulse-level circuit is converted back to a unitary qiskit.QuantumCircuit,
|
|
69
|
+
and the resulting unitary matrix is applied to the input state 0.
|
|
70
|
+
The contrast is defined as the population difference between state 0
|
|
71
|
+
and state 1, that is: C = P0 - P1
|
|
72
|
+
|
|
73
|
+
Parameters:
|
|
74
|
+
pulse_circuit (PulseCircuit): Pulse-level Ramsey circuit.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
float: Population contrast.
|
|
78
|
+
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
circ = pulse_circuit.to_circuit()
|
|
82
|
+
unitary = Operator(circ).to_matrix()
|
|
83
|
+
statevector = unitary[:, 0]
|
|
84
|
+
return np.abs(statevector[0]) ** 2 - np.abs(statevector[1]) ** 2
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def get_average_ramsey_contrast(
|
|
88
|
+
hardware_specs: HardwareSpecs,
|
|
89
|
+
exp_env: ExperimentalEnvironment,
|
|
90
|
+
durations: list[int],
|
|
91
|
+
):
|
|
92
|
+
"""Compute the average Ramsey contrast over multiple noise realizations.
|
|
93
|
+
|
|
94
|
+
For each free-evolution duration given, a Ramsey pulse circuit is constructed
|
|
95
|
+
and its population contrast is computed by averaging over multiple noise
|
|
96
|
+
realizations drawn from the experimental environment. After each duration,
|
|
97
|
+
the environment time traces are regenerated to ensure independence
|
|
98
|
+
between samples.
|
|
99
|
+
|
|
100
|
+
Parameters:
|
|
101
|
+
hardware_specs (HardwareSpecs): Hardware configuration used to
|
|
102
|
+
construct pulse-level Ramsey circuits.
|
|
103
|
+
exp_env (ExperimentalEnvironment): Noise environment providing time
|
|
104
|
+
traces for each sample.
|
|
105
|
+
durations (list[int]): List of free-evolution durations for which the
|
|
106
|
+
Ramsey contrast is evaluated.
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
np.ndarray: Array containing the average Ramsey contrast for each
|
|
110
|
+
duration in ``durations``.
|
|
111
|
+
|
|
112
|
+
"""
|
|
113
|
+
nbp = len(durations)
|
|
114
|
+
average_contrast = np.zeros(nbp)
|
|
115
|
+
for j in tqdm(range(nbp)):
|
|
116
|
+
ramsey_circ = get_ramsey_circuit(durations[j], hardware_specs)
|
|
117
|
+
average_contrast[j] = ramsey_circ.averaging_over_samples(get_contrast, exp_env)
|
|
118
|
+
# regenerate new time traces for the next duration
|
|
119
|
+
exp_env.generate_time_traces()
|
|
120
|
+
return average_contrast
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# --------------------------------------------------------------------------------------
|
|
2
|
+
# This code is part of SpinPulse.
|
|
3
|
+
#
|
|
4
|
+
# (C) Copyright Quobly 2025.
|
|
5
|
+
#
|
|
6
|
+
# This code is licensed under the Apache License, Version 2.0. You may
|
|
7
|
+
# obtain a copy of this license in the LICENSE.txt file in the root directory
|
|
8
|
+
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
|
|
9
|
+
#
|
|
10
|
+
# Any modifications or derivative works of this code must retain this
|
|
11
|
+
# copyright notice, and modified files need to carry a notice indicating
|
|
12
|
+
# that they have been altered from the originals.
|
|
13
|
+
# --------------------------------------------------------------------------------------
|
|
14
|
+
"""
|
|
15
|
+
The `environment` module provides a set of classes for defining and configuring a quantum experimental environment tailored to spin-qubit systems.
|
|
16
|
+
|
|
17
|
+
- :mod:`spin_pulse.environment.experimental_environment`, defines the `Environment` class that contains a quantum experimental environment with configurable noise models.
|
|
18
|
+
|
|
19
|
+
The submodule :mod:`spin_pulse.environment.noise` provides a set of noise classes for simulating different noise processes:
|
|
20
|
+
- QUASISTATIC: Quasistatic noise generated by `QuasistaticNoiseTimeTrace`.
|
|
21
|
+
- PINK: Pink (1/f) noise generated by `PinkNoiseTimeTrace`.
|
|
22
|
+
- WHITE: White Gaussian noise generated by `WhiteNoiseTimeTrace`.
|
|
23
|
+
"""
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# --------------------------------------------------------------------------------------
|
|
2
|
+
# This code is part of SpinPulse.
|
|
3
|
+
#
|
|
4
|
+
# (C) Copyright Quobly 2025.
|
|
5
|
+
#
|
|
6
|
+
# This code is licensed under the Apache License, Version 2.0. You may
|
|
7
|
+
# obtain a copy of this license in the LICENSE.txt file in the root directory
|
|
8
|
+
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
|
|
9
|
+
#
|
|
10
|
+
# Any modifications or derivative works of this code must retain this
|
|
11
|
+
# copyright notice, and modified files need to carry a notice indicating
|
|
12
|
+
# that they have been altered from the originals.
|
|
13
|
+
# --------------------------------------------------------------------------------------
|
|
14
|
+
"""Description of the noisy environment associated to a hardware."""
|
|
15
|
+
|
|
16
|
+
from ..transpilation.hardware_specs import HardwareSpecs
|
|
17
|
+
from .noise import (
|
|
18
|
+
NoiseType,
|
|
19
|
+
PinkNoiseTimeTrace,
|
|
20
|
+
QuasistaticNoiseTimeTrace,
|
|
21
|
+
WhiteNoiseTimeTrace,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ExperimentalEnvironment:
|
|
26
|
+
"""
|
|
27
|
+
Contain a quantum experimental environment with configurable noise models.
|
|
28
|
+
|
|
29
|
+
Attributes:
|
|
30
|
+
- hardware_specs (class): HardwareSpecs class, that defines the hardware settings.
|
|
31
|
+
- noise_type (NoiseType): Type of noise to simulate. Must be "pink", "white", or "quasistatic".
|
|
32
|
+
- T2S (float): Characteristic time of individual qubits.
|
|
33
|
+
- TJS (float or None): Characteristic time of coupled two-qubit system at maximal J coupling without noise on the qubit's frequency.
|
|
34
|
+
- duration (int): Total duration of the simulation.
|
|
35
|
+
- segment_duration (int): Duration of each noise segment; used to partition the time trace.
|
|
36
|
+
- only_idle (bool): Flag to apply noise only to idle qubits.
|
|
37
|
+
- time_traces (list[float]): List of time traces for each qubit.
|
|
38
|
+
- time_traces_coupling (list[float]): List of time traces for coupling noise for each pair of qubits (if TJS is set).
|
|
39
|
+
- seed (int or None): seed integer for random number generation. If not specified, no seed used.
|
|
40
|
+
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
# noise_generator (class): Noise generator class based on noise_type. :no-index:
|
|
44
|
+
|
|
45
|
+
noise_generator: (
|
|
46
|
+
type[WhiteNoiseTimeTrace]
|
|
47
|
+
| type[QuasistaticNoiseTimeTrace]
|
|
48
|
+
| type[PinkNoiseTimeTrace]
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
def __init__(
|
|
52
|
+
self,
|
|
53
|
+
hardware_specs: HardwareSpecs,
|
|
54
|
+
noise_type: NoiseType = NoiseType.PINK,
|
|
55
|
+
T2S: float = 100.0,
|
|
56
|
+
TJS: float | None = None,
|
|
57
|
+
duration: int = 2**10,
|
|
58
|
+
only_idle: bool = False,
|
|
59
|
+
segment_duration: int = 2**10,
|
|
60
|
+
seed: int | None = None,
|
|
61
|
+
):
|
|
62
|
+
"""
|
|
63
|
+
Initialize the ExperimentalEnvironment with specified noise characteristics and simulation parameters.
|
|
64
|
+
|
|
65
|
+
Parameters:
|
|
66
|
+
hardware_specs (class): HardwareSpecs class, that defines the hardware settings.
|
|
67
|
+
noise_type (NoiseType): Type of noise to simulate. Must be "pink", "white", or "quasistatic".
|
|
68
|
+
T2S (float): Characteristic time of individual qubits.
|
|
69
|
+
TJS (float or None): Characteristic time of coupled two-qubit system at maximal J coupling with no noise on the qubit's frequency.
|
|
70
|
+
duration (int): Total duration of the simulation.
|
|
71
|
+
only_idle (bool): Flag to apply noise only to idle qubits.
|
|
72
|
+
segment_duration (int): Duration of each noise segment; used to partition the time trace.
|
|
73
|
+
seed (int or None): seed integer for random number generation. If not specified, no seed used.
|
|
74
|
+
Raises:
|
|
75
|
+
ValueError: If an invalid noise_type is provided.
|
|
76
|
+
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
self.hardware_specs: HardwareSpecs = hardware_specs
|
|
80
|
+
self.noise_type: NoiseType = noise_type
|
|
81
|
+
self.T2S: float = T2S
|
|
82
|
+
self.TJS: float | None = TJS
|
|
83
|
+
self.duration: int = duration
|
|
84
|
+
self.segment_duration: int = segment_duration
|
|
85
|
+
self.seed: int | None = seed
|
|
86
|
+
match noise_type:
|
|
87
|
+
case NoiseType.PINK:
|
|
88
|
+
self.noise_generator = PinkNoiseTimeTrace
|
|
89
|
+
case NoiseType.WHITE:
|
|
90
|
+
self.noise_generator = WhiteNoiseTimeTrace
|
|
91
|
+
case NoiseType.QUASISTATIC:
|
|
92
|
+
self.noise_generator = QuasistaticNoiseTimeTrace
|
|
93
|
+
case _:
|
|
94
|
+
raise ValueError("unknown noise type")
|
|
95
|
+
|
|
96
|
+
self.only_idle = only_idle
|
|
97
|
+
self.generate_time_traces()
|
|
98
|
+
|
|
99
|
+
def generate_time_traces(self):
|
|
100
|
+
"""
|
|
101
|
+
Generate noise time traces for each qubit's frequency and J coupling to each pair of qubits if TJS is defined.
|
|
102
|
+
|
|
103
|
+
Behavior:
|
|
104
|
+
For each qubit, instantiate a noise generator using the selected noise_type.
|
|
105
|
+
The generator uses T2S, duration, and segment_duration to produce a time trace.
|
|
106
|
+
If TJS is provided, generate additional time traces for J coupling noise for each pair of qubits.
|
|
107
|
+
|
|
108
|
+
Effects:
|
|
109
|
+
Populate self.time_traces with one noise trace per qubit.
|
|
110
|
+
If TJS is set, populate self.time_traces_coupling with one trace per pair of qubits (n-1 traces for n qubits).
|
|
111
|
+
"""
|
|
112
|
+
self.time_traces = []
|
|
113
|
+
for _ in range(self.hardware_specs.num_qubits):
|
|
114
|
+
time_trace = self.noise_generator(
|
|
115
|
+
self.T2S, self.duration, self.segment_duration, seed=self.seed
|
|
116
|
+
)
|
|
117
|
+
self.time_traces.append(time_trace)
|
|
118
|
+
|
|
119
|
+
if self.TJS is not None:
|
|
120
|
+
self.time_traces_coupling = []
|
|
121
|
+
for _ in range(self.hardware_specs.num_qubits - 1):
|
|
122
|
+
time_trace = self.noise_generator(
|
|
123
|
+
self.TJS, self.duration, self.segment_duration, seed=self.seed
|
|
124
|
+
)
|
|
125
|
+
self.time_traces_coupling.append(time_trace)
|
|
126
|
+
|
|
127
|
+
def __str__(self):
|
|
128
|
+
"""
|
|
129
|
+
Return a string representation of the ExperimentalEnvironment instance.
|
|
130
|
+
|
|
131
|
+
Includes:
|
|
132
|
+
Number of qubits
|
|
133
|
+
Noise type
|
|
134
|
+
T2S and TJS values
|
|
135
|
+
Duration and segment duration
|
|
136
|
+
Whether noise is only appplied to idle qubits
|
|
137
|
+
The J coupling value set in HardwareSpecs
|
|
138
|
+
The total number of generated time traces
|
|
139
|
+
The total number of generated coupling time traces if there exists some
|
|
140
|
+
"""
|
|
141
|
+
summary = [
|
|
142
|
+
"ExperimentalEnvironment:",
|
|
143
|
+
f" Qubits: {self.hardware_specs.num_qubits}",
|
|
144
|
+
f" Noise Type: {self.noise_type}",
|
|
145
|
+
f" T2S (qubit dephasing): {self.T2S}",
|
|
146
|
+
f" TJS (coupling dephasing): {self.TJS if self.TJS is not None else 'None'}",
|
|
147
|
+
f" Duration: {self.duration}",
|
|
148
|
+
f" Segment Duration: {self.segment_duration}",
|
|
149
|
+
f" Only Idle: {self.only_idle}",
|
|
150
|
+
f" J Coupling: {self.hardware_specs.J_coupling if self.hardware_specs.J_coupling is not None else 'None'}",
|
|
151
|
+
f" Time Traces Generated: {len(self.time_traces)}",
|
|
152
|
+
f" Seed: {self.seed}",
|
|
153
|
+
]
|
|
154
|
+
if hasattr(self, "time_traces_coupling"):
|
|
155
|
+
summary.append(
|
|
156
|
+
f" Coupling Time Traces Generated: {len(self.time_traces_coupling)}"
|
|
157
|
+
)
|
|
158
|
+
return "\n".join(summary)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# --------------------------------------------------------------------------------------
|
|
2
|
+
# This code is part of SpinPulse.
|
|
3
|
+
#
|
|
4
|
+
# (C) Copyright Quobly 2025.
|
|
5
|
+
#
|
|
6
|
+
# This code is licensed under the Apache License, Version 2.0. You may
|
|
7
|
+
# obtain a copy of this license in the LICENSE.txt file in the root directory
|
|
8
|
+
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
|
|
9
|
+
#
|
|
10
|
+
# Any modifications or derivative works of this code must retain this
|
|
11
|
+
# copyright notice, and modified files need to carry a notice indicating
|
|
12
|
+
# that they have been altered from the originals.
|
|
13
|
+
# --------------------------------------------------------------------------------------
|
|
14
|
+
"""
|
|
15
|
+
This modules provides a set of noise classes for simulating different noise processes:
|
|
16
|
+
- QUASISTATIC: Quasistatic noise generated by `QuasistaticNoiseTimeTrace` from :mod:`spin_pulse.environment.noise.quasistatic`.
|
|
17
|
+
- PINK: Pink (1/f) noise generated by `PinkNoiseTimeTrace` from :mod:`spin_pulse.environment.noise.pink`.
|
|
18
|
+
- WHITE: White Gaussian noise generated by `WhiteNoiseTimeTrace` from :mod:`spin_pulse.environment.noise.white`.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from .noise_time_trace import NoiseTimeTrace, NoiseType
|
|
22
|
+
from .pink import PinkNoiseTimeTrace
|
|
23
|
+
from .quasistatic import QuasistaticNoiseTimeTrace
|
|
24
|
+
from .white import WhiteNoiseTimeTrace
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"NoiseTimeTrace",
|
|
28
|
+
"NoiseType",
|
|
29
|
+
"PinkNoiseTimeTrace",
|
|
30
|
+
"QuasistaticNoiseTimeTrace",
|
|
31
|
+
"WhiteNoiseTimeTrace",
|
|
32
|
+
]
|