-
Notifications
You must be signed in to change notification settings - Fork 0
/
qsim.py
179 lines (141 loc) · 5.79 KB
/
qsim.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
'''
Simulations from https://docs.q-ctrl.com/boulder-opal/application-notes/q-ctrl-qchack-challenge
'''
import matplotlib.pyplot as plt
import numpy as np
from qctrlopencontrols import new_corpse_control, new_primitive_control
from qctrlvisualizer import plot_controls
def simulate_ideal_qubit(
duration=1, values=np.array([np.pi]), shots=1024, repetitions=1
):
b = np.array([[0, 1], [0, 0]]) # Lowering operator
initial_state = np.array([[1], [0]]) # Initial state of qubit in |0>
with qctrl.create_graph() as graph:
# Create time dependent \Omega(t)
drive = qctrl.operations.pwc_signal(duration=duration, values=values)
# Construct Hamiltonian (\Omega(t) b + \Omega^*(t) b^\dagger)/2
hamiltonian = qctrl.operations.pwc_operator_hermitian_part(
qctrl.operations.pwc_operator(signal=drive, operator=b)
)
# Solve Schrodinger's equation and get total unitary at the end
unitary = qctrl.operations.time_evolution_operators_pwc(
hamiltonian=hamiltonian,
sample_times=np.array([duration]),
)[-1]
unitary.name = "unitary"
# Repeat final unitary
repeated_unitary = np.eye(2)
for _ in range(repetitions):
repeated_unitary = repeated_unitary @ unitary
repeated_unitary.name = "repeated_unitary"
# Calculate final state.
state = repeated_unitary @ initial_state
# Calculate final populations.
populations = qctrl.operations.abs(state[:, 0]) ** 2
# Normalize populations because of numerical precision
norm = qctrl.operations.sum(populations)
populations = populations / norm
populations.name = "populations"
# Evaluate graph.
result = qctrl.functions.calculate_graph(
graph=graph,
output_node_names=["unitary", "repeated_unitary", "populations"],
)
# Extract outputs.
unitary = result.output["unitary"]["value"]
repeated_unitary = result.output["repeated_unitary"]["value"]
populations = result.output["populations"]["value"]
# Sample projective measurements.
measurements = np.random.choice(2, size=shots, p=populations)
results = {"unitary": unitary, "measurements": measurements}
return results
def simulate_more_realistic_qubit(
duration=1, values=np.array([np.pi]), shots=1024, repetitions=1
):
# 1. Limits for drive amplitudes
assert np.amax(values) <= 1.0
assert np.amin(values) >= -1.0
max_drive_amplitude = 2 * np.pi * 20 # MHz
# 2. Dephasing error
dephasing_error = -2 * 2 * np.pi # MHz
# 3. Amplitude error
amplitude_i_error = 0.98
amplitude_q_error = 1.03
# 4. Control line bandwidth limit
cut_off_frequency = 2 * np.pi * 10 # MHz
resample_segment_count = 1000
# 5. SPAM error confusion matrix
confusion_matrix = np.array([[0.99, 0.01], [0.02, 0.98]])
# Lowering operator
b = np.array([[0, 1], [0, 0]])
# Number operator
n = np.diag([0, 1])
# Initial state
initial_state = np.array([[1], [0]])
with qctrl.create_graph() as graph:
# Apply 1. max Rabi rate.
values = values * max_drive_amplitude
# Apply 3. amplitude errors.
values_i = np.real(values) * amplitude_i_error
values_q = np.imag(values) * amplitude_q_error
values = values_i + 1j * values_q
# Apply 4. bandwidth limits
drive_unfiltered = qctrl.operations.pwc_signal(duration=duration, values=values)
drive_filtered = qctrl.operations.convolve_pwc(
pwc=drive_unfiltered,
kernel_integral=qctrl.operations.sinc_integral_function(cut_off_frequency),
)
drive = qctrl.operations.discretize_stf(
drive_filtered, duration=duration, segments_count=resample_segment_count
)
# Construct microwave drive
drive_term = qctrl.operations.pwc_operator_hermitian_part(
qctrl.operations.pwc_operator(signal=drive, operator=b)
)
# Construct 2. dephasing term.
dephasing_term = qctrl.operations.constant_pwc_operator(
operator=dephasing_error * n,
duration=duration,
)
# Construct Hamiltonian.
hamiltonian = qctrl.operations.pwc_sum(
[
drive_term,
dephasing_term,
]
)
# Solve Schrodinger's equation and get total unitary at the end
unitary = qctrl.operations.time_evolution_operators_pwc(
hamiltonian=hamiltonian,
sample_times=np.array([duration]),
)[-1]
unitary.name = "unitary"
# Repeat final unitary
repeated_unitary = np.eye(2)
for _ in range(repetitions):
repeated_unitary = repeated_unitary @ unitary
repeated_unitary.name = "repeated_unitary"
# Calculate final state.
state = repeated_unitary @ initial_state
# Calculate final populations.
populations = qctrl.operations.abs(state[:, 0]) ** 2
# Normalize populations
norm = qctrl.operations.sum(populations)
populations = populations / norm
populations.name = "populations"
# Evaluate graph.
result = qctrl.functions.calculate_graph(
graph=graph,
output_node_names=["unitary", "repeated_unitary", "populations"],
)
# Extract outputs.
unitary = result.output["unitary"]["value"]
repeated_unitary = result.output["repeated_unitary"]["value"]
populations = result.output["populations"]["value"]
# Sample projective measurements.
true_measurements = np.random.choice(2, size=shots, p=populations)
measurements = np.array(
[np.random.choice(2, p=confusion_matrix[m]) for m in true_measurements]
)
results = {"unitary": unitary, "measurements": measurements}
return results