simcats-datasets 2.5.0__py3-none-any.whl → 2.6.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.
- simcats_datasets/__init__.py +1 -1
- simcats_datasets/generation/_create_dataset.py +50 -0
- simcats_datasets/generation/_create_simulated_dataset.py +168 -69
- simcats_datasets/loading/_load_dataset.py +24 -0
- simcats_datasets/loading/load_ground_truth.py +213 -2
- simcats_datasets/loading/pytorch.py +9 -2
- simcats_datasets/support_functions/clip_line_to_rectangle.py +15 -4
- simcats_datasets/support_functions/convert_lines.py +34 -0
- simcats_datasets/support_functions/data_preprocessing.py +112 -1
- simcats_datasets/support_functions/get_coulomb_oscillation_area_boundaries.py +471 -0
- simcats_datasets/support_functions/metadata_utils.py +62 -0
- simcats_datasets-2.6.0.dist-info/METADATA +163 -0
- simcats_datasets-2.6.0.dist-info/RECORD +22 -0
- {simcats_datasets-2.5.0.dist-info → simcats_datasets-2.6.0.dist-info}/WHEEL +1 -1
- simcats_datasets-2.5.0.dist-info/METADATA +0 -837
- simcats_datasets-2.5.0.dist-info/RECORD +0 -20
- {simcats_datasets-2.5.0.dist-info → simcats_datasets-2.6.0.dist-info/licenses}/LICENSE +0 -0
- {simcats_datasets-2.5.0.dist-info → simcats_datasets-2.6.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
"""Functions for calculating the Coulomb oscillation area boundaries in sensor scans.
|
|
2
|
+
|
|
3
|
+
@author: k.havemann
|
|
4
|
+
"""
|
|
5
|
+
from copy import deepcopy
|
|
6
|
+
import math
|
|
7
|
+
from typing import Callable, Dict, List, Tuple, Union, Optional
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
from simcats.sensor import SensorScanSensorGeneric
|
|
12
|
+
|
|
13
|
+
from simcats_datasets.support_functions.clip_line_to_rectangle import (clip_point_line_to_rectangle,
|
|
14
|
+
clip_slope_line_to_rectangle,
|
|
15
|
+
clip_infinite_slope_line_to_rectangle,
|
|
16
|
+
create_rectangle_corners,
|
|
17
|
+
is_point_inside_rectangle,
|
|
18
|
+
line_intersection)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_coulomb_oscillation_area_boundaries(metadata: Dict,
|
|
22
|
+
range_sensor_g1: Optional[np.ndarray] = None,
|
|
23
|
+
range_sensor_g2: Optional[np.ndarray] = None,
|
|
24
|
+
) -> Tuple[np.ndarray, List[str]]:
|
|
25
|
+
"""Function for calculating the boundaries of the Coulomb oscillation area in a sensor scan.
|
|
26
|
+
|
|
27
|
+
Calculates only the parts of the bounding box within the specified sensor gate 1 (g1) and gate 2 (g2) ranges
|
|
28
|
+
(range_sensor_g1, range_sensor_g2). The g1 and g2 pinch-off values are used for the left and lower boundaries and
|
|
29
|
+
the fully conductive values g1 and g2 are used for the right and upper boundaries.
|
|
30
|
+
In the 2D case, the boundaries are lines; in the 1D case, they are points.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
metadata: Dictionary with sensor metadata.
|
|
34
|
+
range_sensor_g1: Range of sensor g1 within which the coulomb oscillation area is calculated.
|
|
35
|
+
If None, sweep_range_sensor_g1 of the sensor metadata is used.
|
|
36
|
+
range_sensor_g2: Range of sensor g2 within which the coulomb oscillation area is calculated.
|
|
37
|
+
If None, sweep_range_sensor_g2 of the sensor metadata is used.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
np.ndarray, List[str]: Returns an array with the line coordinates (2D)/point coordinates (1D) and list
|
|
41
|
+
containing strings with corresponding labels. Every row of the array represents one line as
|
|
42
|
+
[x_start, y_start, x_stop, y_stop] in 2D case and a point as [x, y, x, y] in 1D case. In addition, a list
|
|
43
|
+
of labels of the calculated boundaries is returned. If both the array and the list are empty, there are no
|
|
44
|
+
boundaries of the oscillation area inside the given voltage ranges.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
line_points = []
|
|
48
|
+
labels = []
|
|
49
|
+
# list of labels for possible lines within the sensor g1 and g2 ranges
|
|
50
|
+
label_names = ["sensor_g1_pinch_off", "sensor_g2_fully_conductive",
|
|
51
|
+
"sensor_g1_fully_conductive", "sensor_g2_pinch_off"]
|
|
52
|
+
fixed_voltage = None # is None if neither of the two gate voltages is fixed during the simulation
|
|
53
|
+
fixed_g1 = False # specifies whether voltage at g1 is fixed
|
|
54
|
+
|
|
55
|
+
if range_sensor_g1 is None:
|
|
56
|
+
range_sensor_g1 = deepcopy(metadata["sweep_range_sensor_g1"])
|
|
57
|
+
if range_sensor_g2 is None:
|
|
58
|
+
range_sensor_g2 = deepcopy(metadata["sweep_range_sensor_g2"])
|
|
59
|
+
|
|
60
|
+
if type(metadata["resolution"]) is int: # 1D scan
|
|
61
|
+
# Setting up a two-dimensional voltage space for horizontal and vertical scans
|
|
62
|
+
if math.isclose(range_sensor_g1[0], range_sensor_g1[1], rel_tol=1e-3): # vertical scan
|
|
63
|
+
fixed_voltage = range_sensor_g1[0]
|
|
64
|
+
fixed_g1 = True
|
|
65
|
+
range_sensor_g1[0] -= 0.1
|
|
66
|
+
range_sensor_g1[1] += 0.1
|
|
67
|
+
elif math.isclose(range_sensor_g2[0], range_sensor_g2[1], rel_tol=1e-3): # horizontal scan
|
|
68
|
+
fixed_voltage = range_sensor_g2[0]
|
|
69
|
+
range_sensor_g2[0] -= 0.1
|
|
70
|
+
range_sensor_g2[1] += 0.1
|
|
71
|
+
|
|
72
|
+
# get rectangle corners of the gate 1 and gate 2 voltage ranges: [bottom_left, bottom_right, top_right, top_left]
|
|
73
|
+
corners = create_rectangle_corners(range_sensor_g1, range_sensor_g2)
|
|
74
|
+
|
|
75
|
+
# get pinch-off and fully conductive values from metadata
|
|
76
|
+
barrier_potentials = (metadata["sensor"].barrier_functions[0].pinch_off,
|
|
77
|
+
metadata["sensor"].barrier_functions[0].fully_conductive,
|
|
78
|
+
metadata["sensor"].barrier_functions[1].pinch_off,
|
|
79
|
+
metadata["sensor"].barrier_functions[1].fully_conductive)
|
|
80
|
+
|
|
81
|
+
# get lever arm from metadata
|
|
82
|
+
alpha_sensor_gate = metadata["sensor"].alpha_sensor_gate # 3x2 array
|
|
83
|
+
alpha_gate = metadata["sensor"].alpha_gate # 3x2 array
|
|
84
|
+
alpha_dot = metadata["sensor"].alpha_dot # 3x2 array
|
|
85
|
+
|
|
86
|
+
# get get_csd_data()-method from ideal_csd_config
|
|
87
|
+
generate_csd = None if metadata["ideal_csd_config"] is None else deepcopy(metadata["ideal_csd_config"].get_csd_data)
|
|
88
|
+
|
|
89
|
+
# calculate potential offset for both barrier gates
|
|
90
|
+
potential_offsets = calc_potential_offset(
|
|
91
|
+
volt_g1=metadata["volt_g1"],
|
|
92
|
+
volt_g2=metadata["volt_g2"],
|
|
93
|
+
alpha_gate=alpha_gate,
|
|
94
|
+
alpha_dot=alpha_dot,
|
|
95
|
+
generate_csd=generate_csd,
|
|
96
|
+
offset_mu_sens=metadata["sensor"].offset_mu_sens
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# calculate intersection points of oscillation area boundaries (order of points: [g1pinchoff_g2pinchoff,
|
|
100
|
+
# g1pinchoff_g2fullyconductive, g1fullyconductive_g2fullyconductive, g1fullyconductive_g2pinchoff] resp.
|
|
101
|
+
# [lower_left, upper_left, upper_right, lower_right]
|
|
102
|
+
intersections = calc_oscillation_area_boundaries_intersect_points(
|
|
103
|
+
gate_potentials=barrier_potentials,
|
|
104
|
+
alpha_sensor_gate=alpha_sensor_gate,
|
|
105
|
+
potential_offset=potential_offsets)
|
|
106
|
+
|
|
107
|
+
# save oscillation area boundaries and their labels if they are within the sensor g1 and g2 ranges
|
|
108
|
+
# oscillation area as a quadrilateral
|
|
109
|
+
for i in range(4):
|
|
110
|
+
start_point = intersections[i]
|
|
111
|
+
end_point = intersections[(i + 1) % 4]
|
|
112
|
+
|
|
113
|
+
# use intersection points to extend and clip pinch off lines to the edge of the scan range:
|
|
114
|
+
if i == 0:
|
|
115
|
+
# extend pinch-off line gate 1 to the upper edge of the voltage range of gate 2:
|
|
116
|
+
start_point, end_point = extend_pinch_off_line_to_edge(start_point,
|
|
117
|
+
end_point,
|
|
118
|
+
rect_corners=corners,
|
|
119
|
+
is_gate2=False)
|
|
120
|
+
elif i == 3:
|
|
121
|
+
# extend pinch-off line gate 2 to the upper edge of the voltage range of gate 1:
|
|
122
|
+
start_point, end_point = extend_pinch_off_line_to_edge(start_point,
|
|
123
|
+
end_point,
|
|
124
|
+
rect_corners=corners,
|
|
125
|
+
is_gate2=True)
|
|
126
|
+
else:
|
|
127
|
+
# use intersection points to clip each line to the rectangle defined by sensor g1 and g2 ranges
|
|
128
|
+
start_point, end_point = clip_point_line_to_rectangle(start=start_point,
|
|
129
|
+
end=end_point,
|
|
130
|
+
rect_corners=corners)
|
|
131
|
+
|
|
132
|
+
if start_point is not None and end_point is not None:
|
|
133
|
+
line_points.append(np.array([start_point[0], start_point[1], end_point[0], end_point[1]]))
|
|
134
|
+
labels.append(label_names[i])
|
|
135
|
+
|
|
136
|
+
# add fully conductive line for triangular oscillation area shape:
|
|
137
|
+
fully_conductive_point = intersections[2]
|
|
138
|
+
fully_conductive_slope = - alpha_sensor_gate[0, 0] / alpha_sensor_gate[0, 1]
|
|
139
|
+
start_point, end_point = clip_infinite_slope_line_to_rectangle(slope=fully_conductive_slope,
|
|
140
|
+
point=fully_conductive_point,
|
|
141
|
+
rect_corners=corners)
|
|
142
|
+
if start_point is not None and end_point is not None:
|
|
143
|
+
# the line always goes from the bottom right to the top left:
|
|
144
|
+
if start_point[0] < end_point[0]:
|
|
145
|
+
start_point, end_point = end_point, start_point
|
|
146
|
+
|
|
147
|
+
intersect_pinch_off_g1 = None
|
|
148
|
+
intersect_pinch_off_g2 = None
|
|
149
|
+
if "sensor_g1_pinch_off" in labels:
|
|
150
|
+
pinch_off_g1_start = line_points[labels.index("sensor_g1_pinch_off")][:2]
|
|
151
|
+
pinch_off_g1_end = line_points[labels.index("sensor_g1_pinch_off")][2:]
|
|
152
|
+
intersect_pinch_off_g1 = line_intersection(p1=start_point, p2=end_point,
|
|
153
|
+
q1=pinch_off_g1_start, q2=pinch_off_g1_end)
|
|
154
|
+
|
|
155
|
+
if "sensor_g2_pinch_off" in labels:
|
|
156
|
+
pinch_off_g2_start = line_points[labels.index("sensor_g2_pinch_off")][:2]
|
|
157
|
+
pinch_off_g2_end = line_points[labels.index("sensor_g2_pinch_off")][2:]
|
|
158
|
+
intersect_pinch_off_g2 = line_intersection(p1=start_point, p2=end_point,
|
|
159
|
+
q1=pinch_off_g2_start, q2=pinch_off_g2_end)
|
|
160
|
+
|
|
161
|
+
if intersect_pinch_off_g1 is not None and is_point_inside_rectangle(intersect_pinch_off_g1,
|
|
162
|
+
rect_corners=corners):
|
|
163
|
+
end_point = intersect_pinch_off_g1
|
|
164
|
+
if intersect_pinch_off_g2 is not None and is_point_inside_rectangle(intersect_pinch_off_g2,
|
|
165
|
+
rect_corners=corners):
|
|
166
|
+
start_point = intersect_pinch_off_g2
|
|
167
|
+
# else: no change in start and end points
|
|
168
|
+
|
|
169
|
+
line_points.append(np.array([start_point[0], start_point[1], end_point[0], end_point[1]]))
|
|
170
|
+
labels.append("sensor_potential_fully_conductive")
|
|
171
|
+
|
|
172
|
+
# 1D Scans: convert lines of the two-dimensional voltage space to points in the one-dimensional measurement
|
|
173
|
+
if type(metadata["resolution"]) is int:
|
|
174
|
+
if fixed_voltage is not None: # scan is either horizontal or vertical
|
|
175
|
+
if fixed_g1:
|
|
176
|
+
range_sensor_g1[0] = fixed_voltage
|
|
177
|
+
range_sensor_g1[1] = fixed_voltage
|
|
178
|
+
else:
|
|
179
|
+
range_sensor_g2[0] = fixed_voltage
|
|
180
|
+
range_sensor_g2[1] = fixed_voltage
|
|
181
|
+
|
|
182
|
+
line_points, labels = calc_track_boundaries_intersect_points(volt_range_g1=range_sensor_g1,
|
|
183
|
+
volt_range_g2=range_sensor_g2,
|
|
184
|
+
line_points=np.array(line_points),
|
|
185
|
+
labels=labels)
|
|
186
|
+
|
|
187
|
+
return np.array(line_points), labels
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def extend_pinch_off_line_to_edge(start: Tuple[float, float],
|
|
191
|
+
end: Tuple[float, float],
|
|
192
|
+
rect_corners: List[Tuple[float, float]],
|
|
193
|
+
is_gate2: bool) -> Tuple[Tuple[float, float], Tuple[float, float]]:
|
|
194
|
+
"""Extend and clip the pinch-off line segment to the edge of the voltage ranges of the sensor scan.
|
|
195
|
+
|
|
196
|
+
The pinch-off lines are calculated as segments between the lower left corner of the oscillation range and the fully
|
|
197
|
+
conductive values of the gates. However, the lines are required beyond the fully conductive value up to the upper
|
|
198
|
+
edge of the voltage ranges of the two gates and are simultaneously trimmed to these.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
start (Tuple[float, float]): The start point of the line segment ((x_start, y_start)).
|
|
202
|
+
end (Tuple[float, float]): The end point of the line segment ((x_end, y_end)).
|
|
203
|
+
rect_corners (np.ndarray): The corners of the boundary of the sensor scan.
|
|
204
|
+
is_gate2 (bool): True if the sensor scan line segment is the pinch-off line for gate 2. False if the sensor scan
|
|
205
|
+
line segment is the pinch-off line for gate 1.
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
Tuple[Tuple[float, float], Tuple[float, float]]: Extended pinch-off line ((x_start, y_start), (x_end, y_end)).
|
|
209
|
+
"""
|
|
210
|
+
# If necessary, swap the start and end points:
|
|
211
|
+
if (is_gate2 and start[0] > end[0]) or (not is_gate2 and start[1] > end[1]):
|
|
212
|
+
start, end = end, start
|
|
213
|
+
|
|
214
|
+
den = start[0] - end[0]
|
|
215
|
+
if math.isclose(den, 0.0): # vertical line with infinte slope
|
|
216
|
+
end = (end[0], np.max([corner[1] for corner in rect_corners]))
|
|
217
|
+
else: # all other cases
|
|
218
|
+
slope = (start[1] - end[1]) / den
|
|
219
|
+
start, end = clip_slope_line_to_rectangle(slope=slope,
|
|
220
|
+
point=start,
|
|
221
|
+
rect_corners=rect_corners,
|
|
222
|
+
is_start=is_gate2)
|
|
223
|
+
|
|
224
|
+
return start, end
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def calc_oscillation_area_boundaries_intersect_points(gate_potentials: List[float],
|
|
228
|
+
alpha_sensor_gate: np.ndarray, # 3x2 array
|
|
229
|
+
potential_offset: Tuple[float, float],
|
|
230
|
+
) -> List[Tuple[float, float]]:
|
|
231
|
+
"""Function for calculating the 4 intersection point of the boundaries of the coulomb oscillation area.
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
gate_potentials: List containing pinch-off and fully conductive potentials of sensor gate 1 and gate 2.
|
|
235
|
+
Assumes it contains exactly 4 values in the specified order:
|
|
236
|
+
[g1_pinch_off, g1_fully_conductive, g2_pinch_off, g2_fully_conductive]
|
|
237
|
+
alpha_sensor_gate: Lever arm from the sensor dot (barrier) gates to the sensor potential.
|
|
238
|
+
potential_offset: Potential offset of the sensor barrier gates.
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
List[Tuple[float, float]]: List of intersection points as Tuples (x_coord, y_coord).
|
|
242
|
+
"""
|
|
243
|
+
intersection_points = []
|
|
244
|
+
|
|
245
|
+
if math.isclose(alpha_sensor_gate[1, 1], 0.0): # case: slope of gate 1 line is infinity
|
|
246
|
+
v1_pinch_off = (gate_potentials[0] - potential_offset[0]) / alpha_sensor_gate[1, 0]
|
|
247
|
+
v1_fully_conductive = (gate_potentials[1] - potential_offset[0]) / alpha_sensor_gate[1, 0]
|
|
248
|
+
|
|
249
|
+
v2_pinch_off_left = ((-alpha_sensor_gate[2, 0] * v1_pinch_off + gate_potentials[2] -
|
|
250
|
+
potential_offset[1]) / alpha_sensor_gate[2, 1])
|
|
251
|
+
v2_pinch_off_right = ((-alpha_sensor_gate[2, 0] * v1_fully_conductive + gate_potentials[2] -
|
|
252
|
+
potential_offset[1]) / alpha_sensor_gate[2, 1])
|
|
253
|
+
v2_fully_conductive_left = ((-alpha_sensor_gate[2, 0] * v1_pinch_off + gate_potentials[3] -
|
|
254
|
+
potential_offset[1]) / alpha_sensor_gate[2, 1])
|
|
255
|
+
v2_fully_conductive_right = ((-alpha_sensor_gate[2, 0] * v1_fully_conductive + gate_potentials[3] -
|
|
256
|
+
potential_offset[1]) / alpha_sensor_gate[2, 1])
|
|
257
|
+
|
|
258
|
+
intersection_points = [(v1_pinch_off, v2_pinch_off_left), # g1pinchoff_g2pinchoff
|
|
259
|
+
(v1_pinch_off, v2_fully_conductive_left), # g1pinchoff_g2fullyconductive
|
|
260
|
+
(v1_fully_conductive, v2_fully_conductive_right), # g1fullyconductive_g2fullyconductive
|
|
261
|
+
(v1_fully_conductive, v2_pinch_off_right)] # g1fullyconductive_g2pinchoff
|
|
262
|
+
|
|
263
|
+
else:
|
|
264
|
+
gate1_potentials = [val for val in gate_potentials[:2] for _ in range(2)]
|
|
265
|
+
# gate1_potentials: [g1_pinch_off, g1_pinch_off, g1_fully_conductive, g1_fully_conductive]
|
|
266
|
+
gate2_potentials = gate_potentials[2:] + gate_potentials[:1:-1]
|
|
267
|
+
# gate2_potentials: [g2_pinch_off, g2_fully_conductive, g2_fully_conductive, g2_pinch_off]
|
|
268
|
+
for g1_pot, g2_pot in zip(gate1_potentials, gate2_potentials):
|
|
269
|
+
v1 = (((g2_pot - potential_offset[1]) * alpha_sensor_gate[1, 1]
|
|
270
|
+
- (g1_pot - potential_offset[0]) * alpha_sensor_gate[2, 1]) /
|
|
271
|
+
(alpha_sensor_gate[2, 0] * alpha_sensor_gate[1, 1]
|
|
272
|
+
- alpha_sensor_gate[1, 0] * alpha_sensor_gate[2, 1]))
|
|
273
|
+
v2 = (-alpha_sensor_gate[1, 0] * v1 + (g1_pot - potential_offset[0])) / alpha_sensor_gate[1, 1]
|
|
274
|
+
intersection_points.append((v1.item(), v2.item()))
|
|
275
|
+
# intersection points as Tuples [g1pinchoff_g2pinchoff, g1pinchoff_g2fullyconductive,
|
|
276
|
+
# g1fullyconductive_g2fullyconductive, g1fullyconductive_g2pinchoff] resp.
|
|
277
|
+
# [lower_left, upper_left, upper_right, lower_right]
|
|
278
|
+
return intersection_points
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def calc_potential_offset(volt_g1: float,
|
|
282
|
+
volt_g2: float,
|
|
283
|
+
alpha_gate: np.ndarray,
|
|
284
|
+
alpha_dot: np.ndarray,
|
|
285
|
+
generate_csd: Callable[
|
|
286
|
+
[np.ndarray, np.ndarray, Union[int, np.ndarray]], Tuple[np.ndarray, np.ndarray]],
|
|
287
|
+
offset_mu_sens: np.ndarray
|
|
288
|
+
) -> Tuple[float, float]:
|
|
289
|
+
"""Function for calculating the potential offset of the barrier gates of a sensor, which is given by the double dot
|
|
290
|
+
configurations.
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
volt_g1: Voltage of the double dot (plunger) gate 1.
|
|
294
|
+
volt_g2: Voltage of the double dot (plunger) gate 2.
|
|
295
|
+
alpha_gate: Lever arms from the double dot (plunger) gates to the sensor potential.
|
|
296
|
+
alpha_dot: Lever arms from the double dot occupations gates to the sensor potential.
|
|
297
|
+
generate_csd: Method for retrieving ideal data (occupancy numbers and a lead transition mask) for given gate
|
|
298
|
+
voltages.
|
|
299
|
+
offset_mu_sens: The potential offset for the sensor.
|
|
300
|
+
|
|
301
|
+
Returns:
|
|
302
|
+
Tuple(float, float): Potential offset of the sensor barrier gates potential (barrier1_off, barrier2_off).
|
|
303
|
+
"""
|
|
304
|
+
|
|
305
|
+
if volt_g1 is None or volt_g2 is None:
|
|
306
|
+
return (0.0, 0.0)
|
|
307
|
+
else:
|
|
308
|
+
offset1 = volt_g1 * alpha_gate[1, 0] + volt_g2 * alpha_gate[1, 1] + offset_mu_sens[1]
|
|
309
|
+
offset2 = volt_g1 * alpha_gate[2, 0] + volt_g2 * alpha_gate[2, 1] + offset_mu_sens[2]
|
|
310
|
+
if generate_csd is not None:
|
|
311
|
+
# get double dot occupations
|
|
312
|
+
occupations_csd, lead_transitions = generate_csd(
|
|
313
|
+
volt_limits_g1=(volt_g1, volt_g1), volt_limits_g2=(volt_g2, volt_g2), resolution=(1)
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
offset1 += alpha_dot[1, 0] * occupations_csd[0, 0] + alpha_dot[1, 1] * occupations_csd[0, 1]
|
|
317
|
+
offset2 += alpha_dot[2, 0] * occupations_csd[0, 0] + alpha_dot[2, 1] * occupations_csd[0, 1]
|
|
318
|
+
|
|
319
|
+
return (offset1, offset2)
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def calc_sensor_potential(sensor: SensorScanSensorGeneric,
|
|
323
|
+
sweep_range_sensor_g1: Union[np.ndarray, float],
|
|
324
|
+
sweep_range_sensor_g2: Union[np.ndarray, float],
|
|
325
|
+
resolution: Union[int, np.ndarray],
|
|
326
|
+
volt_g1: Optional[float] = None,
|
|
327
|
+
volt_g2: Optional[float] = None,
|
|
328
|
+
generate_csd: Optional[Callable[[np.ndarray, np.ndarray, Union[int, np.ndarray]], Tuple[np.ndarray, np.ndarray]]] = None) -> np.ndarray:
|
|
329
|
+
"""Function for calculating the sensor potential.
|
|
330
|
+
|
|
331
|
+
Calculating the sensor potential for ranges of voltages of the two sensor barrier gates.
|
|
332
|
+
|
|
333
|
+
Args:
|
|
334
|
+
sensor (SensorScanSensorGeneric): Sensor for which the potential is to be calculated.
|
|
335
|
+
sweep_range_sensor_g1 (np.ndarray): Voltage sweep range of sensor gate 1 (second-/x-axis). \n
|
|
336
|
+
Example: \n
|
|
337
|
+
[min_V1, max_V1]
|
|
338
|
+
sweep_range_sensor_g2 (np.ndarray): Voltage sweep range of sensor gate 2 (first-/y-axis). \n
|
|
339
|
+
Example: \n
|
|
340
|
+
[min_V1, max_V1]
|
|
341
|
+
resolution (Union[int, np.ndarray]): Resolution of the sensor scan.
|
|
342
|
+
volt_g1 (Optional[float]): Voltage applied at (plunger) gate 1 (second-/x-axis).
|
|
343
|
+
volt_g2 (Optional[float]): Voltage applied at (plunger) gate 2 (first-/y-axis).
|
|
344
|
+
generate_csd: Method for retrieving ideal data (occupancy numbers and a lead transition mask) for given gate
|
|
345
|
+
voltages.
|
|
346
|
+
|
|
347
|
+
Returns:
|
|
348
|
+
np.ndarray: The sensor potential for different sensor barrier gate voltages as an array.
|
|
349
|
+
"""
|
|
350
|
+
if generate_csd is None or volt_g1 is None or volt_g2 is None:
|
|
351
|
+
if type(resolution) == int:
|
|
352
|
+
occupations = np.zeros((resolution, 2))
|
|
353
|
+
else:
|
|
354
|
+
occupations = np.zeros((resolution[1], resolution[0], 2))
|
|
355
|
+
else:
|
|
356
|
+
# Perform simulation
|
|
357
|
+
occupations_csd, lead_transitions = generate_csd(
|
|
358
|
+
volt_limits_g1=(volt_g1, volt_g1), volt_limits_g2=(volt_g2, volt_g2), resolution=(1)
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
if type(resolution) == int:
|
|
362
|
+
occupations = np.ones((resolution, 2))
|
|
363
|
+
occupations[:, 0] = occupations[:, 0] * occupations_csd[0, 0]
|
|
364
|
+
occupations[:, 1] = occupations[:, 1] * occupations_csd[0, 1]
|
|
365
|
+
else:
|
|
366
|
+
occupations = np.ones((resolution[1], resolution[0], 2))
|
|
367
|
+
occupations[:, :, 0] = occupations[:, :, 0] * occupations_csd[0, 0]
|
|
368
|
+
occupations[:, :, 1] = occupations[:, :, 1] * occupations_csd[0, 1]
|
|
369
|
+
|
|
370
|
+
# calculate the sensor potential from the distorted occupations
|
|
371
|
+
potential = sensor.sensor_potential(
|
|
372
|
+
occupations=occupations, volt_limits_g1=volt_g1, volt_limits_g2=volt_g2,
|
|
373
|
+
volt_limits_sensor_g1=sweep_range_sensor_g1, volt_limits_sensor_g2=sweep_range_sensor_g2
|
|
374
|
+
)[0] # choose sensor potential from [sensor dot potential, barrier 1 potential, barrier 2 potential]
|
|
375
|
+
|
|
376
|
+
return potential
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def calc_fully_conductive_potential(metadata: Dict) -> float:
|
|
380
|
+
"""Function for calculating the sensor potential at the fully conductive point.
|
|
381
|
+
|
|
382
|
+
First determining the fully conductive point in the two-dimensional voltage space, then calculating the potential.
|
|
383
|
+
|
|
384
|
+
Args:
|
|
385
|
+
metadata: Dictionary with sensor metadata.
|
|
386
|
+
|
|
387
|
+
Returns:
|
|
388
|
+
float: The sensor potential at the fully conductive point.
|
|
389
|
+
"""
|
|
390
|
+
# get pinch-off and fully conductive values from metadata
|
|
391
|
+
barrier_potentials = (metadata["sensor"].barrier_functions[0].pinch_off,
|
|
392
|
+
metadata["sensor"].barrier_functions[0].fully_conductive,
|
|
393
|
+
metadata["sensor"].barrier_functions[1].pinch_off,
|
|
394
|
+
metadata["sensor"].barrier_functions[1].fully_conductive)
|
|
395
|
+
|
|
396
|
+
# get get_csd_data()-method from ideal_csd_config
|
|
397
|
+
generate_csd = None if metadata["ideal_csd_config"] is None else deepcopy(metadata["ideal_csd_config"].get_csd_data)
|
|
398
|
+
|
|
399
|
+
# calculate potential offset for both barrier gates
|
|
400
|
+
potential_offsets = calc_potential_offset(
|
|
401
|
+
volt_g1=metadata["volt_g1"],
|
|
402
|
+
volt_g2=metadata["volt_g2"],
|
|
403
|
+
alpha_gate=metadata["sensor"].alpha_gate,
|
|
404
|
+
alpha_dot=metadata["sensor"].alpha_dot,
|
|
405
|
+
generate_csd=generate_csd,
|
|
406
|
+
offset_mu_sens=metadata["sensor"].offset_mu_sens
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
# calculation of fully conductive point in two-dimensional voltage space
|
|
410
|
+
fully_conductive = calc_oscillation_area_boundaries_intersect_points(
|
|
411
|
+
gate_potentials=barrier_potentials,
|
|
412
|
+
alpha_sensor_gate=metadata["sensor"].alpha_sensor_gate,
|
|
413
|
+
potential_offset=potential_offsets)[2]
|
|
414
|
+
|
|
415
|
+
# calculation of the sensor potential at the fully conductive point
|
|
416
|
+
fully_conductive_potential = calc_sensor_potential(sensor=metadata["sensor"],
|
|
417
|
+
sweep_range_sensor_g1=fully_conductive[0],
|
|
418
|
+
sweep_range_sensor_g2=fully_conductive[1],
|
|
419
|
+
resolution=1,
|
|
420
|
+
volt_g1=metadata["volt_g1"],
|
|
421
|
+
volt_g2=metadata["volt_g2"],
|
|
422
|
+
generate_csd=generate_csd)[0] # choose value from array
|
|
423
|
+
|
|
424
|
+
return fully_conductive_potential
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def calc_track_boundaries_intersect_points(volt_range_g1: np.ndarray,
|
|
428
|
+
volt_range_g2: np.ndarray,
|
|
429
|
+
line_points: np.ndarray,
|
|
430
|
+
labels: List[Dict]) -> Tuple[np.ndarray, List[str]]:
|
|
431
|
+
"""Calculates the intersection points of a one-dimensional, straight measurement track with the boundaries of the
|
|
432
|
+
Coulomb oscillation area in the two-dimensional sensor data space.
|
|
433
|
+
|
|
434
|
+
Args:
|
|
435
|
+
volt_range_g1 (np.ndarray): Range of sensor g1 of the one-dimensional measurement.
|
|
436
|
+
volt_range_g2 (np.ndarray): Range of sensor g2 of the one-dimensional measurement.
|
|
437
|
+
line_points (np.ndarray): Array containing all start and end points of the boundaries of the Coulomb
|
|
438
|
+
oscillation area. Every row of the array represents one line as [x_start, y_start, x_stop, y_stop].
|
|
439
|
+
labels (List[str]): Dictionary containing labels for each boundary of the Coulomb oscillation area given in
|
|
440
|
+
line_points.
|
|
441
|
+
|
|
442
|
+
Returns:
|
|
443
|
+
Tuple[np.ndarray, List[Dict]]: Returns an array of the intersection points of the one-dimensional, straight
|
|
444
|
+
measurement track with the boundaries of the Coulomb oscillation area. Each row of the array represents one
|
|
445
|
+
intersection point.
|
|
446
|
+
In addition, a list of labels of the calculated intersection points is returned.
|
|
447
|
+
If both the array and the List are empty, there are no intersections.
|
|
448
|
+
"""
|
|
449
|
+
intersect_points = []
|
|
450
|
+
point_labels = []
|
|
451
|
+
|
|
452
|
+
# Calculate line coordinates of the track of the 1D scan:
|
|
453
|
+
track_start = (volt_range_g1[0], volt_range_g2[0])
|
|
454
|
+
track_end = (volt_range_g1[-1], volt_range_g2[-1])
|
|
455
|
+
|
|
456
|
+
# measurement is horizontal or vertical:
|
|
457
|
+
if math.isclose(volt_range_g1[-1], volt_range_g1[0]) or math.isclose(volt_range_g2[-1], volt_range_g2[0]):
|
|
458
|
+
for i, line in enumerate(line_points):
|
|
459
|
+
intersect_points.append(line[:2])
|
|
460
|
+
point_labels.append(labels[i])
|
|
461
|
+
else:
|
|
462
|
+
for i, line in enumerate(line_points):
|
|
463
|
+
# calculation intersection point:
|
|
464
|
+
intersection = line_intersection(p1=(line[0], line[1]), p2=(line[2], line[3]), q1=track_start, q2=track_end)
|
|
465
|
+
|
|
466
|
+
if intersection is not None:
|
|
467
|
+
# append point (x, y) as line format [x, y, x, y]
|
|
468
|
+
intersect_points.append(np.array([intersection[0], intersection[1], intersection[0], intersection[1]]))
|
|
469
|
+
point_labels.append(labels[i])
|
|
470
|
+
|
|
471
|
+
return np.array(intersect_points), point_labels
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Functions for handling metadata, e.g. reconstruct the dtypes of metadata loaded from h5 files.
|
|
2
|
+
|
|
3
|
+
@author: k.havemann
|
|
4
|
+
"""
|
|
5
|
+
import warnings
|
|
6
|
+
from copy import deepcopy
|
|
7
|
+
from typing import Dict
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
# required for eval to work
|
|
12
|
+
from numpy import array
|
|
13
|
+
from simcats.sensor import SensorGeneric, SensorScanSensorGeneric, SensorPeakLorentzian, SensorRiseGLF
|
|
14
|
+
from simcats.sensor.barrier_function import BarrierFunctionGLF
|
|
15
|
+
from simcats.ideal_csd import IdealCSDGeometric
|
|
16
|
+
from simcats.distortions import OccupationDotJumps
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def reconstruct_metadata_types(metadata: Dict) -> Dict:
|
|
20
|
+
"""Reconstruct metadata types from the sensor metadata dictionary.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
metadata: Dictionary with sensor metadata.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
Dict: Dictionary with sensor metadata with reconstructed data types.
|
|
27
|
+
"""
|
|
28
|
+
meta = deepcopy(metadata)
|
|
29
|
+
if isinstance(meta["resolution"], str): # 1D
|
|
30
|
+
meta["resolution"] = int(meta["resolution"])
|
|
31
|
+
|
|
32
|
+
meta["sweep_range_sensor_g1"] = np.array(meta["sweep_range_sensor_g1"])
|
|
33
|
+
meta["sweep_range_sensor_g2"] = np.array(meta["sweep_range_sensor_g2"])
|
|
34
|
+
|
|
35
|
+
if isinstance(meta["sensor"], str):
|
|
36
|
+
meta["sensor"] = eval(meta["sensor"])
|
|
37
|
+
|
|
38
|
+
if isinstance(meta["ideal_csd_config"], str):
|
|
39
|
+
meta["ideal_csd_config"] = eval(meta["ideal_csd_config"])
|
|
40
|
+
|
|
41
|
+
meta["volt_g1"] = float(meta["volt_g1"]) if meta["volt_g1"] != "None" else None
|
|
42
|
+
meta["volt_g2"] = float(meta["volt_g2"]) if meta["volt_g2"] != "None" else None
|
|
43
|
+
|
|
44
|
+
if meta["volt_limits_sensor_g1"] is not None:
|
|
45
|
+
meta["volt_limits_sensor_g1"] = np.array(meta["volt_limits_sensor_g1"])
|
|
46
|
+
else:
|
|
47
|
+
meta["volt_limits_sensor_g1"] = np.array([-1.0, 0.0])
|
|
48
|
+
warnings.warn(
|
|
49
|
+
f"No voltage limits are specified for the sensor gate 1 in the metadata. "
|
|
50
|
+
f"[-1.0; 0.0] is used as the voltage limits for sensor gate 1. "
|
|
51
|
+
f"This may not be consistent with the scan configuration.")
|
|
52
|
+
|
|
53
|
+
if meta["volt_limits_sensor_g2"] is not None:
|
|
54
|
+
meta["volt_limits_sensor_g2"] = np.array(meta["volt_limits_sensor_g2"])
|
|
55
|
+
else:
|
|
56
|
+
meta["volt_limits_sensor_g2"] = np.array([-1.0, 0.0])
|
|
57
|
+
warnings.warn(
|
|
58
|
+
f"No voltage limits are specified for the sensor gate 2 in the metadata. "
|
|
59
|
+
f"[-1.0; 0.0] is used as the voltage limits for sensor gate 2. "
|
|
60
|
+
f"This may not be consistent with the scan configuration.")
|
|
61
|
+
|
|
62
|
+
return meta
|