simcats 1.1.0__py3-none-any.whl → 2.0.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.
Files changed (42) hide show
  1. simcats/__init__.py +4 -3
  2. simcats/_default_configs.py +129 -13
  3. simcats/_simulation.py +451 -69
  4. simcats/config_samplers/_GaAs_v1_random_variations_v3_config_sampler.py +1059 -0
  5. simcats/config_samplers/__init__.py +9 -0
  6. simcats/distortions/_distortion_interfaces.py +1 -1
  7. simcats/distortions/_dot_jumps.py +8 -6
  8. simcats/distortions/_random_telegraph_noise.py +4 -4
  9. simcats/distortions/_transition_blurring.py +5 -5
  10. simcats/distortions/_white_noise.py +2 -2
  11. simcats/ideal_csd/geometric/_generate_lead_transition_mask.py +3 -3
  12. simcats/ideal_csd/geometric/_get_electron_occupation.py +5 -5
  13. simcats/ideal_csd/geometric/_ideal_csd_geometric.py +5 -5
  14. simcats/ideal_csd/geometric/_ideal_csd_geometric_class.py +9 -9
  15. simcats/ideal_csd/geometric/_tct_bezier.py +5 -5
  16. simcats/sensor/__init__.py +10 -6
  17. simcats/sensor/{_generic_sensor.py → _sensor_generic.py} +1 -1
  18. simcats/sensor/_sensor_interface.py +164 -11
  19. simcats/sensor/_sensor_rise_glf.py +229 -0
  20. simcats/sensor/_sensor_scan_sensor_generic.py +929 -0
  21. simcats/sensor/barrier_function/__init__.py +9 -0
  22. simcats/sensor/barrier_function/_barrier_function_glf.py +280 -0
  23. simcats/sensor/barrier_function/_barrier_function_interface.py +43 -0
  24. simcats/sensor/barrier_function/_barrier_function_multi_glf.py +157 -0
  25. simcats/sensor/deformation/__init__.py +9 -0
  26. simcats/sensor/deformation/_sensor_peak_deformation_circle.py +109 -0
  27. simcats/sensor/deformation/_sensor_peak_deformation_interface.py +65 -0
  28. simcats/sensor/deformation/_sensor_peak_deformation_linear.py +77 -0
  29. simcats/support_functions/__init__.py +11 -3
  30. simcats/support_functions/_generalized_logistic_function.py +146 -0
  31. simcats/support_functions/_linear_algebra.py +171 -0
  32. simcats/support_functions/_parameter_sampling.py +108 -19
  33. simcats/support_functions/_pixel_volt_transformation.py +24 -0
  34. simcats/support_functions/_reset_offset_mu_sens.py +43 -0
  35. {simcats-1.1.0.dist-info → simcats-2.0.0.dist-info}/METADATA +93 -29
  36. simcats-2.0.0.dist-info/RECORD +53 -0
  37. {simcats-1.1.0.dist-info → simcats-2.0.0.dist-info}/WHEEL +1 -1
  38. simcats-1.1.0.dist-info/RECORD +0 -37
  39. /simcats/sensor/{_gaussian_sensor_peak.py → _sensor_peak_gaussian.py} +0 -0
  40. /simcats/sensor/{_lorentzian_sensor_peak.py → _sensor_peak_lorentzian.py} +0 -0
  41. {simcats-1.1.0.dist-info → simcats-2.0.0.dist-info/licenses}/LICENSE +0 -0
  42. {simcats-1.1.0.dist-info → simcats-2.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1059 @@
1
+ """
2
+ This module contains a sampler for SimCATS configurations, corresponding to the random_variations_v3 parameters.
3
+ The configurations can be used to initialize an object of the simulation class.
4
+
5
+ @author: f.hader, b.papajewski
6
+ """
7
+
8
+ import math
9
+ import random
10
+ from copy import deepcopy
11
+ from typing import Dict, List, Tuple
12
+
13
+ import numpy as np
14
+
15
+ from simcats import Simulation, default_configs
16
+ from simcats.distortions import (
17
+ OccupationTransitionBlurringFermiDirac,
18
+ SensorPotentialPinkNoise,
19
+ SensorResponseWhiteNoise,
20
+ OccupationDistortionInterface, SensorPotentialDistortionInterface, SensorResponseDistortionInterface,
21
+ SensorPotentialRTN, SensorResponseRTN, OccupationDotJumps,
22
+ )
23
+ from simcats.ideal_csd import IdealCSDGeometric
24
+ from simcats.sensor import SensorGeneric, SensorPeakLorentzian, SensorScanSensorGeneric, SensorPeakInterface, \
25
+ SensorRiseGLF, SensorRiseInterface
26
+ from simcats.sensor.barrier_function import BarrierFunctionGLF, BarrierFunctionInterface
27
+ from simcats.sensor.deformation import SensorPeakDeformationLinear, SensorPeakDeformationCircle
28
+ from simcats.support_functions import (
29
+ NormalSamplingRange,
30
+ ExponentialSamplingRange,
31
+ rotate_points, UniformSamplingRange,
32
+ inverse_glf
33
+ )
34
+
35
+ __all__ = []
36
+
37
+
38
+ def sample_random_variations_v3_config(sensor_type: str = "SensorGeneric",
39
+ quality_multiplier: float = 1.0,
40
+ compensated_sensor: bool = False,
41
+ set_sensor_potential_offset_to_steepest_point: bool = False) -> Dict:
42
+ """
43
+ Samples a full random_variations_v3 SimCATS config.
44
+
45
+ Args:
46
+ sensor_type: The type of sensor implementation to use. Can be either "SensorGeneric" or
47
+ "SensorScanSensorGeneric". Defaults to "SensorGeneric".
48
+ quality_multiplier: The quality multiplier to use, which defines how much of the ranges for distortions etc. is
49
+ used. This basically allows to restrict configurations to better data quality, as future improvements of the
50
+ sample quality are expected. The parameter further restricts the preset ranges for distortions (reducing the
51
+ allowed maximum for distortions) and things like the height of Coulomb peaks (increasing the minimum). The
52
+ factor must be in the range (0.0, 1.0]. Defaults to 1.0.
53
+ compensated_sensor: Whether the effect of the double quantum dot on the sensor dot is compensated (lever-arms of
54
+ the double dot gate voltages on the sensor are close to zero). Defaults to False.
55
+ set_sensor_potential_offset_to_steepest_point: Whether the steepest point of the sensor function is determined
56
+ and the corresponding potential is set as the potential offset (offset_mu_sens). This enables simple
57
+ "retuning" of the sensor by always resetting the potential offset before every CSD measurement. Defaults to
58
+ False.
59
+
60
+ Returns:
61
+ Dict: Full config for the SimCATS Simulation class.
62
+ """
63
+ if not (quality_multiplier > 0.0 and quality_multiplier <= 1.0):
64
+ raise ValueError(f"quality_multiplier must be in the range (0.0, 1.0], but {quality_multiplier} is given.")
65
+ config = dict()
66
+ config["volt_limits_g1"] = np.array([-0.2, -0.06])
67
+ config["volt_limits_g2"] = np.array([-0.2, -0.06])
68
+ config["volt_limits_sensor_g1"] = np.array([-1, 0])
69
+ config["volt_limits_sensor_g2"] = np.array([-1, 0])
70
+ config["ideal_csd_config"] = sample_IdealCSDGeometric()
71
+ if sensor_type == "SensorGeneric":
72
+ config["sensor"] = sample_SensorGeneric(quality_multiplier=quality_multiplier,
73
+ compensated_sensor=compensated_sensor,
74
+ set_sensor_potential_offset_to_steepest_point=set_sensor_potential_offset_to_steepest_point)
75
+ elif sensor_type == "SensorScanSensorGeneric":
76
+ config["sensor"] = sample_SensorScanSensorGeneric(quality_multiplier=quality_multiplier,
77
+ compensated_sensor=compensated_sensor
78
+ )
79
+ else:
80
+ raise ValueError(
81
+ f"The parameter sensor_type must be either 'SensorGeneric' or 'SensorScanSensorGeneric'. The supplied value"
82
+ f" '{sensor_type}' is not supported.")
83
+ config["occupation_distortions"] = sample_occupation_distortions()
84
+ config["sensor_potential_distortions"] = sample_sensor_potential_distortions()
85
+ config["sensor_response_distortions"] = sample_sensor_response_distortions()
86
+ return config
87
+
88
+
89
+ def sample_IdealCSDGeometric() -> IdealCSDGeometric:
90
+ """
91
+ Samples a random_variations_v3 IdealCSDGeometric config.
92
+
93
+ Returns:
94
+ IdealCSDGeometric: IdealCSDGeometric object to be used as ideal_csd_config for the SimCATS Simulation class.
95
+ """
96
+ # slopes of the lead transitions in 45°-rotated space
97
+ slope1_min = 0.21429
98
+ slope1_max = 0.54688
99
+ slope2_min = -0.44
100
+ slope2_max = -0.07692
101
+ # angle between lead transitions
102
+ angle_lead_min = 0.43760867228
103
+ angle_lead_max = 1.6789624091
104
+ # lengths of the lead transitions (between minima and maxima of a TCT)
105
+ length_min = 0.01
106
+ length_max = 0.025
107
+ # angle between interdot and first lead transition (slope1 is rotated counter-clockwise by this angle to get the direction of the interdot vector)
108
+ angle_interdot_min = 0.58153916891
109
+ angle_interdot_max = 1.396938844
110
+ # length of the interdot transition
111
+ interdot_length_min = 0.00261
112
+ interdot_length_max = 0.00987
113
+ interdot_length_mean = 0.004 # Currently not used, as we sample interdot length with a uniform distribution. Kept for the sake of completeness.
114
+ interdot_length_std = 0.0015 # Currently not used, as we sample interdot length with a uniform distribution. Kept for the sake of completeness.
115
+ # width of the interdot transition, larger values lead to more curved TCTs
116
+ interdot_width_min = 0.00043
117
+ interdot_width_max = 0.00814
118
+ # relation between width and length of the interdot transition
119
+ rel_interdot_length_width_min = 0.8692269873603532
120
+ rel_interdot_length_width_max = 9.055385138137407
121
+ # limits for the plunger gate voltages used for the simulation
122
+ volt_limits_g1 = np.array([-0.2, -0.06])
123
+ volt_limits_g2 = np.array([-0.2, -0.06])
124
+ # place for the first bezier anchor, which determines were the first interdot transition is located
125
+ first_bezier_point_min, first_bezier_point_max = -0.14, -0.14
126
+ # lookup table entries to use for the calculation of the tct_bezier
127
+ lut_entries = 1000
128
+
129
+ # create random number generator
130
+ rng = np.random.default_rng()
131
+
132
+ # sample slope
133
+ slope1 = np.array([1, rng.uniform(slope1_min, slope1_max)])
134
+ slope2 = np.array([1, rng.uniform(slope2_min, slope2_max)])
135
+
136
+ angle_lead = np.arccos(slope1.dot(slope2) / (np.linalg.norm(slope1) * np.linalg.norm(slope2)))
137
+
138
+ # create the IdealCSDGeometric object
139
+ # fresh default config
140
+ ideal_csd_geometric = deepcopy(default_configs["GaAs_v1"]["ideal_csd_config"])
141
+
142
+ # basic settings regarding volt_limits and lookup table entries
143
+ ideal_csd_geometric.lut_entries = lut_entries
144
+
145
+ # resample slope if angle_lead is not between angle_lead_min and angle_lead_max
146
+ while not (angle_lead_min <= angle_lead <= angle_lead_max):
147
+ slope1 = np.array([1, rng.uniform(slope1_min, slope1_max)])
148
+ slope2 = np.array([1, rng.uniform(slope2_min, slope2_max)])
149
+ angle_lead = np.arccos(slope1.dot(slope2) / (np.linalg.norm(slope1) * np.linalg.norm(slope2)))
150
+
151
+ # sample lengths
152
+ lengths = [
153
+ rng.uniform(length_min, length_max),
154
+ rng.uniform(length_min, length_max),
155
+ ]
156
+
157
+ # sample interdot_vec
158
+ interdot_vec = rotate_points(slope1, rng.uniform(angle_interdot_min, angle_interdot_max))
159
+ # rotate interdot_vec like it would be in the CSD space
160
+ interdot_vec_rot = rotate_points(interdot_vec, -np.pi / 4)
161
+ while interdot_vec_rot[0] < 0 or interdot_vec_rot[1] < 0:
162
+ # interdot vector should not go backwards in the image space, so resample if the rotated vector has negative entries
163
+ interdot_vec = rotate_points(slope1, rng.uniform(angle_interdot_min, angle_interdot_max))
164
+ # rotate interdot_vec
165
+ interdot_vec_rot = rotate_points(interdot_vec, -np.pi / 4)
166
+ interdot_vec = interdot_vec / np.linalg.norm(interdot_vec)
167
+
168
+ # sample interdot_length
169
+ # usage of uniform distribution to get more diverse data
170
+ interdot_length = rng.uniform(interdot_length_min, interdot_length_max)
171
+ while interdot_length < interdot_length_min or interdot_length > interdot_length_max:
172
+ interdot_length = rng.uniform(interdot_length_min, interdot_length_max)
173
+
174
+ # sample bezier_width
175
+ bezier_width = rng.uniform(interdot_width_min, interdot_width_max)
176
+ # resample interdot_length and bezier_width until interdot_length / bezier_width is between rel_interdot_length_width_min and rel_interdot_length_width_max
177
+ while not (
178
+ rel_interdot_length_width_min <= interdot_length / bezier_width
179
+ and interdot_length / bezier_width <= rel_interdot_length_width_max
180
+ ):
181
+ interdot_length = rng.uniform(interdot_length_min, interdot_length_max)
182
+ bezier_width = rng.uniform(interdot_width_min, interdot_width_max)
183
+ interdot_vec = interdot_vec * interdot_length
184
+
185
+ # place first bezier_point in lower left corner
186
+ bezier_point = rng.uniform(first_bezier_point_min, first_bezier_point_max, size=2)
187
+ bezier_point = rotate_points(bezier_point)
188
+
189
+ # calculate the second bezier point, so that it is in the correct direction
190
+ # direction for bezier_point is the mean of both slopes
191
+ direction = np.array([1, (slope1[1] + slope2[1]) / 2])
192
+ direction = direction / np.linalg.norm(direction)
193
+ bezier_point2 = bezier_point + bezier_width * direction
194
+
195
+ # set up shift vector to calculate next tct params
196
+ shift_vec = np.array([-lengths[1] + interdot_vec[0], interdot_vec[1] - lengths[1] * slope2[1]])
197
+
198
+ # set up first tct params
199
+ temp_params = np.array(
200
+ [
201
+ lengths[0],
202
+ lengths[1],
203
+ slope1[1],
204
+ slope2[1],
205
+ bezier_point[0],
206
+ bezier_point[1],
207
+ bezier_point2[0],
208
+ bezier_point2[1],
209
+ ]
210
+ )
211
+
212
+ # Add as many TCTs as required, by using a sensor and testing if sufficient TCTs were added to cover the whole
213
+ # voltage space
214
+ ideal_csd_geometric.tct_params = [temp_params]
215
+ running = True
216
+ while running:
217
+ try:
218
+ # set up simulation
219
+ temp_config = dict()
220
+ temp_config["volt_limits_g1"] = volt_limits_g1
221
+ temp_config["volt_limits_g2"] = volt_limits_g2
222
+ temp_config["ideal_csd_config"] = ideal_csd_geometric
223
+ sim = Simulation(**temp_config)
224
+ # measure
225
+ _ = sim.measure(volt_limits_g1, volt_limits_g2, resolution=np.array([10, 10]))
226
+ except IndexError as e:
227
+ ideal_csd_geometric.tct_params = _add_one_tct(ideal_csd_geometric.tct_params, shift_vec)
228
+ else:
229
+ running = False
230
+
231
+ return ideal_csd_geometric
232
+
233
+
234
+ def sample_SensorGeneric(quality_multiplier: float = 1.0,
235
+ compensated_sensor: bool = False,
236
+ set_sensor_potential_offset_to_steepest_point: bool = False) -> SensorGeneric:
237
+ """
238
+ Sample parameters and initialize a SensorGeneric object.
239
+
240
+ Args:
241
+ quality_multiplier: The quality multiplier to use, which defines how much of the ranges for Coulomb peak heights
242
+ etc. is used. This basically allows to restrict configurations to better data quality, as future
243
+ improvements of the sample quality are expected. The parameter further restricts the preset ranges for
244
+ things like the height of Coulomb peaks (increasing the minimum) and the strength of the lever-arms of the
245
+ double quantum dot gates towards the sensor. The factor must be in the range (0.0, 1.0]. Defaults to 1.0.
246
+ compensated_sensor: Whether the effect of the double quantum dot on the sensor dot is compensated (lever-arms of
247
+ the double dot gate voltages on the sensor are close to zero). Defaults to False.
248
+ set_sensor_potential_offset_to_steepest_point: Whether the steepest point of the sensor function is determined
249
+ and the corresponding potential is set as the potential offset (offset_mu_sens). This enables simple
250
+ "retuning" of the sensor by always resetting the potential offset before every CSD measurement. Defaults to
251
+ False.
252
+
253
+ Returns:
254
+ SensorGeneric: Fully initialized SensorGeneric object.
255
+ """
256
+ # limits for the plunger gate voltages used for the simulation
257
+ volt_limits_g1 = np.array([-0.2, -0.06])
258
+ volt_limits_g2 = np.array([-0.2, -0.06])
259
+ num_sensor_peak_min, num_sensor_peak_max = 3, 6
260
+ # s_off is the offset of the peaks (y)
261
+ sensor_offset_min, sensor_offset_max = -0.42275, -0.0838
262
+ # mu_0 (offset_mu_sens) from sensor (x)
263
+ sensor_mu_0_min, sensor_mu_0_max = -0.12168, -0.03824
264
+ sensor_height_min, sensor_height_max = 0.02245, 0.19204
265
+ sensor_gamma_min, sensor_gamma_max = 0.0009636, 0.0029509
266
+ if compensated_sensor:
267
+ sensor_alpha_gate_1_min, sensor_alpha_gate_1_max = 0.0001, 0.0005
268
+ sensor_alpha_gate_2_min, sensor_alpha_gate_2_max = 0.0001, 0.0005 # Currently not used, as we sample alpha gate 2 in dependence on alpha gate 1. Kept for the sake of completeness.
269
+ else:
270
+ sensor_alpha_gate_1_min, sensor_alpha_gate_1_max = 0.02805, 0.15093
271
+ sensor_alpha_gate_2_min, sensor_alpha_gate_2_max = 0.03788, 0.19491 # Currently not used, as we sample alpha gate 2 in dependence on alpha gate 1. Kept for the sake of completeness.
272
+ sensor_alpha_dot_1_min, sensor_alpha_dot_1_max = -0.0007994, -0.0000961
273
+ sensor_alpha_dot_1_max = sensor_alpha_dot_1_min + quality_multiplier * (
274
+ sensor_alpha_dot_1_max - sensor_alpha_dot_1_min)
275
+ sensor_alpha_dot_2_min, sensor_alpha_dot_2_max = -0.0005214, -0.0000630
276
+ sensor_alpha_dot_2_max = sensor_alpha_dot_2_min + quality_multiplier * (
277
+ sensor_alpha_dot_2_max - sensor_alpha_dot_2_min)
278
+
279
+ quantile_relative_pos = 0.6 * quality_multiplier
280
+ quantile_percentage = 0.99
281
+ exp_scale_sensor_height = quantile_relative_pos / np.log(1 / (1 - quantile_percentage))
282
+
283
+ # create random number generator
284
+ rng = np.random.default_rng()
285
+
286
+ # sample alpha gates and alpha dots
287
+ sensor_alpha_gate_1 = rng.uniform(low=sensor_alpha_gate_1_min, high=sensor_alpha_gate_1_max)
288
+ sensor_alpha_gate_2 = rng.uniform(low=0.5 * sensor_alpha_gate_1, high=2 * sensor_alpha_gate_1)
289
+
290
+ # sample alpha dot 1 depending on alpha gate 1
291
+ # -0.03 / 100 = at least as much change for occ change (alpha dot) as change for sweeping one pixel (alpha gate)
292
+ # Keep in mind negative means, max is actually min (think of absolute values).
293
+ # Set to 2* alpha gate per pixel value, because stepping one pixel adds 1* and we want to jump back at least
294
+ # 1* pixel step (starting with random_variations_v2).
295
+ # Should actually always ensure to have higher min sensitivity (max = min because of negative values). Therefore,
296
+ # np.min with the actual value has been added (starting with random_variations_v3).
297
+ temp_sensor_alpha_dot_1_max = np.min([2 * sensor_alpha_gate_1 * -0.03 / 100,
298
+ sensor_alpha_dot_1_max])
299
+ sensor_alpha_dot_1 = rng.uniform(low=sensor_alpha_dot_1_min, high=temp_sensor_alpha_dot_1_max)
300
+
301
+ # sample alpha dot 2 depending on alpha gate 2 and alpha dot 1
302
+ # Set to 2* alpha gate per pixel value, because stepping one pixel adds 1* and we want to jump back at least
303
+ # 1* pixel step (starting with random_variations_v2).
304
+ # Should actually always ensure to have higher min sensitivity (max = min because of negative values). Therefore,
305
+ # np.min with the actual value has been added (starting with random_variations_v3).
306
+ temp_sensor_alpha_dot_2_max = np.min([0.5 * sensor_alpha_dot_1, 2 * sensor_alpha_gate_2 * -0.03 / 100,
307
+ sensor_alpha_dot_2_max])
308
+ if temp_sensor_alpha_dot_2_max < sensor_alpha_dot_2_min:
309
+ temp_sensor_alpha_dot_2_max = sensor_alpha_dot_2_max
310
+ # Should actually only ensure to be at most twice as strongly coupled compared to dot 1, but never stronger than
311
+ # given by the pre-defined range. Therefore, np.max with the actual value has been added (starting with
312
+ # random_variations_v3).
313
+ temp_sensor_alpha_dot_2_min = np.max([2 * sensor_alpha_dot_1, sensor_alpha_dot_2_min])
314
+ sensor_alpha_dot_2 = rng.uniform(low=temp_sensor_alpha_dot_2_min, high=temp_sensor_alpha_dot_2_max)
315
+
316
+ sensor = SensorGeneric(
317
+ sensor_peak_function=None,
318
+ alpha_dot=np.array([sensor_alpha_dot_1, sensor_alpha_dot_2]),
319
+ alpha_gate=np.array([sensor_alpha_gate_1, sensor_alpha_gate_2]),
320
+ )
321
+
322
+ # calculate potential to find mu_sens limits
323
+ occupations = np.array([[0.0, 0.0], [5.0, 5.0]])
324
+ potentials = sensor.sensor_potential(occupations=occupations,
325
+ volt_limits_g1=volt_limits_g1,
326
+ volt_limits_g2=volt_limits_g2)
327
+ sensor_mu_0_min, sensor_mu_0_max = np.min(potentials), np.max(potentials)
328
+
329
+ # sample number of peaks
330
+ num_sensor_peak = rng.choice(np.arange(num_sensor_peak_min, num_sensor_peak_max + 1))
331
+
332
+ sensor_peak_functions = list()
333
+
334
+ sensor_height_sampler = ExponentialSamplingRange(
335
+ total_range=(sensor_height_max, sensor_height_min),
336
+ scale=exp_scale_sensor_height
337
+ ) # using other sampler starting with random_variations_v2
338
+
339
+ # sample individual peaks
340
+ for i in range(num_sensor_peak):
341
+ sensor_gamma = rng.uniform(low=sensor_gamma_min, high=sensor_gamma_max)
342
+ sensor_mu_0 = rng.uniform(low=sensor_mu_0_min, high=sensor_mu_0_max)
343
+ if i > 0: # added a rule for the variation of mu0 between peaks of the same config (starting with random_variations_v2)
344
+ # make sure that sensors have at least (0.5 * potential_range) / number_of_peaks distance (for mu0)
345
+ counter = 0
346
+ # introduced this counter, because we could end up in a case where it is not possible to place the next peak,
347
+ # because two peaks can be slightly below twice min required distance apart from each other and therefore block 2x
348
+ # and not just 1.5x (sensor_mu_0_max - sensor_mu_0_min) / (num_sensor_peak). Try 1000 times and else allow 2x.
349
+ while (np.min([np.abs(sensor_mu_0 - sampled_peak.mu0) for sampled_peak in sensor_peak_functions]) <= (
350
+ sensor_mu_0_max - sensor_mu_0_min) / (num_sensor_peak * 1.5) and counter < 1000) or np.min(
351
+ [np.abs(sensor_mu_0 - sampled_peak.mu0) for sampled_peak in sensor_peak_functions]) <= (
352
+ sensor_mu_0_max - sensor_mu_0_min) / (num_sensor_peak * 2):
353
+ sensor_mu_0 = rng.uniform(low=sensor_mu_0_min, high=sensor_mu_0_max)
354
+ counter += 1
355
+ if i == 0: # added a rule for the variation of peak_offset between peaks of the same config (starting with random_variations_v2)
356
+ sensor_peak_offset = rng.uniform(low=sensor_offset_min, high=sensor_offset_max)
357
+ else:
358
+ # make sure to have at most 20% difference in peak_offset to first peak_offset
359
+ sensor_peak_offset = rng.uniform(low=sensor_peak_functions[0].offset * 1.2,
360
+ high=sensor_peak_functions[0].offset * 0.8)
361
+ if i == 0: # added a rule for the variation of height between peaks of the same config (starting with random_variations_v2)
362
+ sensor_height = sensor_height_sampler.sample_parameter()
363
+ else:
364
+ # make sure to have at most 20% difference in height to first height
365
+ sensor_height = rng.uniform(low=sensor_peak_functions[0].height * 0.8,
366
+ high=sensor_peak_functions[0].height * 1.2)
367
+ sensor_peak_functions.append(
368
+ SensorPeakLorentzian(
369
+ gamma=sensor_gamma,
370
+ mu0=sensor_mu_0,
371
+ height=sensor_height,
372
+ offset=sensor_peak_offset,
373
+ )
374
+ )
375
+ sensor.sensor_peak_function = sensor_peak_functions
376
+
377
+ if set_sensor_potential_offset_to_steepest_point:
378
+ # calculate steepest point of sensor peak flank for offset_mu_sens (used for the assumption of sensor retuning
379
+ # to shift sensor to this point)
380
+ sample_mu_sens = np.linspace(sensor_mu_0_min - 0.2 * (sensor_mu_0_max - sensor_mu_0_min),
381
+ sensor_mu_0_max + 0.2 * (sensor_mu_0_max - sensor_mu_0_min),
382
+ 1000000)
383
+ sample_sens_response = sensor.sensor_response(sample_mu_sens)
384
+ sample_sens_response_grad = np.gradient(sample_sens_response)
385
+ sensor.offset_mu_sens = sample_mu_sens[np.argmax(sample_sens_response_grad)]
386
+
387
+ return sensor
388
+
389
+
390
+ def _sensor_peak_list_add_peak(peak_list: List[SensorPeakInterface],
391
+ ref_height: float,
392
+ base_offset: float = 0) -> list[SensorPeakInterface]:
393
+ """
394
+ Method to add a single lorentzian peak to a list of sensor peaks.
395
+
396
+ This function is only required when sampling a SensorScanSensorGeneric.
397
+
398
+ Args:
399
+ peak_list (List[SensorPeakInterface]): List of sensor peaks to which a peak is added
400
+ ref_height (float): Reference height of all peaks. Based on this, the height of the new peak is sampled at
401
+ random. The reference height is used as the mean value of a distribution from which the height of the new
402
+ peak is sampled.
403
+ base_offset (float): Potential offset of the first sensor peak. The default value is 0. This value has no effect
404
+ for every other peak.
405
+
406
+ Returns:
407
+ List[SensorPeakInterface]: The list of sensor peaks with an extra lorentzian peak added.
408
+ """
409
+ mu_dif_absolute_initial_lower_limit = 0
410
+ mu_dif_absolute_initial_upper_limit = 0.00367179654343025 * 4
411
+ mu_dif_absolute_initial_std = 0.00023884164727643042 / 2 # MAD
412
+ # mu_dif_absolute_initial_mean = 0.002902416771776037 * 1.5 # MEDIAN
413
+ mu_dif_absolute_initial_mean = 0.002902416771776037 * 2.5 # MEDIAN
414
+ mu_dif_relative_std = 0.09509839063162284 / 2 # MAD
415
+ mu_dif_relative_mean_fitted_line_slope = -0.0374000174907983 / 4
416
+ mu_dif_relative_max_deviation = 1
417
+ mu_dif_relative_mean_fitted_line_intercept = 1.0189427890611187
418
+ mu_dif_relative_line_max_peak_num = 10
419
+
420
+ gamma_absolute_initial_std = 3.6990706645585366e-05 /2 # MAD
421
+ gamma_absolute_initial_mean = 9.878642853157584e-05 * 2.5 # MEDIAN
422
+ gamma_relative_mean_fitted_line_slope = 0
423
+ gamma_relative_mean_fitted_line_intercept = 1.25
424
+ gamma_relative_std = 0.4695086254881995 / 2
425
+ gamma_relative_lower_limit = 0.85
426
+ gamma_relative_higher_limit = 1.5
427
+ gamma_rel_line_max_peak_num = 5
428
+ gamma_max_value = 0.002 #0.0025
429
+ gamma_max_value_deviation_percentage = 0.1
430
+
431
+ height_std = 0.04720315300812465 / 2 # MAD
432
+
433
+ if len(peak_list) == 0:
434
+ mu0 = base_offset
435
+
436
+ gamma = NormalSamplingRange(
437
+ total_range=(0, np.inf),
438
+ std=gamma_absolute_initial_std,
439
+ mean=gamma_absolute_initial_mean
440
+ ).sample_parameter()
441
+
442
+ elif len(peak_list) == 1:
443
+ mu_dif = NormalSamplingRange(
444
+ total_range=(mu_dif_absolute_initial_lower_limit, mu_dif_absolute_initial_upper_limit),
445
+ std=mu_dif_absolute_initial_std,
446
+ mean=mu_dif_absolute_initial_mean
447
+ ).sample_parameter()
448
+ mu0 = peak_list[0].mu0 + mu_dif
449
+
450
+ gamma_rel_dif = NormalSamplingRange(
451
+ total_range=(gamma_relative_lower_limit, gamma_relative_higher_limit),
452
+ std=gamma_relative_std,
453
+ mean=gamma_relative_mean_fitted_line_slope * (
454
+ len(peak_list) - 1) + gamma_relative_mean_fitted_line_intercept
455
+ ).sample_parameter()
456
+ gamma = peak_list[-1].gamma * gamma_rel_dif
457
+
458
+ else:
459
+ mu_factor = peak_list[1].mu0 - peak_list[0].mu0
460
+ mu_peak_num = len(peak_list) if len(
461
+ peak_list) < mu_dif_relative_line_max_peak_num else mu_dif_relative_line_max_peak_num
462
+ mu_rel_dif = NormalSamplingRange(
463
+ total_range=(
464
+ -mu_dif_relative_max_deviation * mu_dif_relative_std,
465
+ mu_dif_relative_max_deviation * mu_dif_relative_std),
466
+ std=mu_dif_relative_std,
467
+ mean=0,
468
+ ).sample_parameter() + mu_dif_relative_mean_fitted_line_slope * (
469
+ mu_peak_num - 1) + mu_dif_relative_mean_fitted_line_intercept
470
+
471
+ mu0 = peak_list[-1].mu0 + mu_rel_dif * mu_factor
472
+
473
+ gamma_peak_num = len(peak_list) if len(peak_list) < gamma_rel_line_max_peak_num else gamma_rel_line_max_peak_num
474
+ gamma_rel_dif = NormalSamplingRange(
475
+ total_range=(gamma_relative_lower_limit, gamma_relative_higher_limit),
476
+ std=gamma_relative_std,
477
+ mean=gamma_relative_mean_fitted_line_slope * (
478
+ gamma_peak_num - 1) + gamma_relative_mean_fitted_line_intercept
479
+ ).sample_parameter()
480
+ gamma = peak_list[-1].gamma * gamma_rel_dif
481
+
482
+ if gamma > gamma_max_value:
483
+ gamma = UniformSamplingRange(
484
+ total_range=(gamma_max_value * (1 - gamma_max_value_deviation_percentage),
485
+ gamma_max_value * (1 + gamma_max_value_deviation_percentage)),
486
+ ).sample_parameter()
487
+
488
+ height = ref_height + NormalSamplingRange(
489
+ total_range=(-ref_height, np.inf),
490
+ std=height_std,
491
+ mean=0
492
+ ).sample_parameter()
493
+
494
+ return peak_list + [SensorPeakLorentzian(
495
+ mu0=mu0,
496
+ gamma=gamma,
497
+ height=height,
498
+ offset=0, # A minimum sensor response of 0 is assumed
499
+ ), ]
500
+
501
+
502
+ def _sample_sensor_peak_list(num_peaks: int,
503
+ ref_height: float,
504
+ offset: float) -> List[SensorPeakLorentzian]:
505
+ """
506
+ Function to sample a list of sensor peaks.
507
+ Objects of the SensorPeakLorentzian class are used as peaks here.
508
+
509
+ This function is only required when sampling a SensorScanSensorGeneric.
510
+
511
+ Args:
512
+ num_peaks (int): Number of lorentzian peaks to sample.
513
+ ref_height (float): Reference height of all peaks. The reference height is used as the mean for the
514
+ distributions all peaks are sampled from.
515
+ offset (float): Potential offset of the first sensor peak.
516
+
517
+ Returns:
518
+ List[SensorPeakLorentzian]: A list of Lorentzian peaks.
519
+ """
520
+ peak_list = []
521
+
522
+ for i in range(num_peaks):
523
+ peak_list = _sensor_peak_list_add_peak(peak_list=peak_list, ref_height=ref_height, base_offset=offset)
524
+
525
+ return peak_list
526
+
527
+
528
+ def sample_sensor_function(offset: float, quality_multiplier: float) -> Tuple[List[SensorPeakInterface], SensorRiseInterface]:
529
+ """
530
+ Function to sample a whole sensor function.
531
+ A whole sensor function consists of a list of lorentzian sensor peaks and ends with a sigmoid sensor rise, which
532
+ represents an increase to the maximum conductivity.
533
+
534
+ This function is only required when sampling a SensorScanSensorGeneric.
535
+
536
+ Args:
537
+ offset (float): Potential offset of the first sensor peak.
538
+ quality_multiplier (float): The quality multiplier of the sensor function sampler restricts the reference height
539
+ that specifies the mean of the sensor peak height. This also indirectly restricts the height of the final
540
+ rise, as this depends on the maximum height of the preceding sensor peaks. The factor must be in the range
541
+ (0.0, 1.0]. Defaults to 1.0.
542
+
543
+ Returns:
544
+ List[SensorPeakInterface]: A whole sensor function as a list returned as tuple. The tuple contains the sensor
545
+ peaks as a list of multiple lorentzian and the sensor rise, a rise to the maximum sensor response.
546
+ """
547
+ peak_num_range = (20, 30)
548
+
549
+ height_ref_mean = 0.07543071921134054 * quality_multiplier # MEDIAN
550
+ height_ref_range = (0, 1)
551
+ height_ref_std = 0.04804196857755959
552
+
553
+ sensor_rise_max_multiplier = 1.05
554
+ sensor_rise_growth_rate_range = (400, 600)
555
+ sensor_rise_asymmetry_range = (0.01, 1)
556
+ sensor_rise_shape_factor_range = (0.01, 100)
557
+ # The distance of the sensor rise to the previous lorentzian peak is based on a multiple of the distance of the last
558
+ # two peaks. The value of the multiple is sampled from this range.
559
+ sensor_rise_dif_multiplier_range = (2, 3) # (4, 6)
560
+ sensor_rise_end_percentage = 0.95
561
+
562
+ # Sample reference peak height
563
+ ref_height = NormalSamplingRange(
564
+ total_range=height_ref_range,
565
+ std=height_ref_std,
566
+ mean=height_ref_mean
567
+ ).sample_parameter()
568
+
569
+ # Sample Lorentzian peaks
570
+ num_peaks = int(UniformSamplingRange(total_range=peak_num_range).sample_parameter())
571
+ sensor_func = _sample_sensor_peak_list(num_peaks=num_peaks, ref_height=ref_height, offset=offset)
572
+
573
+ x_values = np.linspace(-5, 5, 500000)
574
+ sensor_resp = np.zeros(500000)
575
+ for func in sensor_func:
576
+ sensor_resp += func.sensor_function(x_values)
577
+ maximum = np.max(sensor_resp)
578
+
579
+ left_asymptote = 0 # A minimum sensor response of 0 is assumed
580
+ right_asymptote = maximum * sensor_rise_max_multiplier
581
+
582
+ param_dict = {
583
+ "asymptote_left": left_asymptote,
584
+ "asymptote_right": right_asymptote,
585
+ "growth_rate": UniformSamplingRange(total_range=sensor_rise_growth_rate_range).sample_parameter(),
586
+ "asymmetry": UniformSamplingRange(total_range=sensor_rise_asymmetry_range).sample_parameter(),
587
+ "shape_factor": UniformSamplingRange(total_range=sensor_rise_shape_factor_range).sample_parameter(),
588
+ "mu0": 0
589
+ }
590
+
591
+ sensor_rise = SensorRiseGLF(
592
+ fully_conductive_percentage=sensor_rise_end_percentage,
593
+ **param_dict
594
+ )
595
+
596
+ return sensor_func, sensor_rise
597
+
598
+
599
+ def sample_barrier_function(
600
+ offset: float = 0,
601
+ height: float = 0.25,
602
+ quality_multiplier: float = 1.0,
603
+ ) -> BarrierFunctionInterface:
604
+ """
605
+ Method to sample a barrier function.
606
+
607
+ This function is only required when sampling a SensorScanSensorGeneric.
608
+
609
+ Args:
610
+ offset (float): Potential offset of the barrier function. Without this offset the pinch of point of the barrier
611
+ function is at a potential of 0.
612
+ height (float): Maximal height and conductance value of the barrier function.
613
+ quality_multiplier (float): For the barrier function, the quality multiplier restricts the barrier height,
614
+ therefore, the factor is multiplied with the mean of the distribution the sensors are sampled from. The
615
+ factor must be in the range (0.0, 1.0]. Defaults to 1.0.
616
+
617
+ Returns:
618
+ BarrierFunctionInterface: A SimCATS barrier function.
619
+ """
620
+ return _sample_simple_barrier_function(offset=offset, height=height, quality_multiplier=quality_multiplier,
621
+ pinch_off_percentage=0.001, fully_conductive_percentage=0.999)
622
+
623
+
624
+ def _sample_simple_barrier_function(offset: float = 0,
625
+ height: float = 0.25,
626
+ quality_multiplier: float = 1.0,
627
+ pinch_off_percentage: float = 0.05,
628
+ fully_conductive_percentage: float = 0.95) -> BarrierFunctionGLF:
629
+ """
630
+ Method to sample a simple barrier function without any defects. For that a single GLF is used.
631
+
632
+ This function is only required when sampling a SensorScanSensorGeneric.
633
+
634
+ Args:
635
+ offset (float): Potential offset of the barrier function. Without this offset the pinch of point of the barrier
636
+ function is at a potential of 0.
637
+ height (float): Maximal height and conductance value of the barrier function.
638
+ quality_multiplier (float): For the barrier function, the quality multiplier restricts the barrier height,
639
+ therefore, the factor is multiplied with the mean of the distribution the sensors are sampled from. The
640
+ factor must be in the range (0.0, 1.0]. Defaults to 1.0.
641
+ pinch_off_percentage (float): Percentage of the barrier conductance range which is considered as the pinch-off
642
+ value. E.g.: If the barrier function goes from zero to one and the pinch_off_percentage is set to 0.01, then
643
+ the pinch-off is at the potential that leads to a conductance of 0.01. Defaults to 0.05.
644
+ fully_conductive_percentage (float): Percentage of the barrier conductance range which is considered as the
645
+ point where the barrier vanishes and becomes fully conductive. E.g.: If the barrier function goes from zero
646
+ to one and the fully_conductive_percentage is set to 0.99, then the fully conductive point is at the
647
+ potential that leads to a conductance of 0.99. Defaults to 0.95.
648
+
649
+ Returns:
650
+ BarrierFunctionGLF: A barrier function based on a GLF.
651
+ """
652
+ barrier_func_asymmetry_range = (0.000001, 0.0001)
653
+
654
+ barrier_func_shape_factor_range = (0.01, 100)
655
+ barrier_func_growth_rate_range = (20,40)
656
+
657
+ param_dict = {
658
+ "asymptote_left": 0,
659
+ "asymptote_right": height * quality_multiplier,
660
+ "growth_rate": UniformSamplingRange(total_range=barrier_func_growth_rate_range).sample_parameter(),
661
+ "asymmetry": UniformSamplingRange(total_range=barrier_func_asymmetry_range).sample_parameter(),
662
+ "shape_factor": UniformSamplingRange(total_range=barrier_func_shape_factor_range).sample_parameter(),
663
+ "denominator_offset": 1,
664
+ }
665
+
666
+ pinch_off = inverse_glf(value=param_dict["asymptote_right"] * pinch_off_percentage, **param_dict)
667
+ param_dict["offset"] = offset - pinch_off
668
+
669
+ return BarrierFunctionGLF(
670
+ pinch_off_percentage=pinch_off_percentage,
671
+ fully_conductive_percentage=fully_conductive_percentage,
672
+ **param_dict
673
+ )
674
+
675
+
676
+ def sample_SensorScanSensorGeneric(quality_multiplier: float = 1.0,
677
+ compensated_sensor: bool = False) -> SensorScanSensorGeneric:
678
+ """
679
+ Sample parameters and initialize a SensorScanSensorGeneric object.
680
+
681
+ Args:
682
+ quality_multiplier: The quality multiplier to use, which defines how much of the ranges for Coulomb peak heights
683
+ etc. is used. This basically allows to restrict configurations to better data quality, as future
684
+ improvements of the sample quality are expected. The parameter further restricts the preset ranges for
685
+ things like the height of Coulomb peaks (increasing the minimum) and the strength of the lever-arms of the
686
+ double quantum dot gates towards the sensor. The factor must be in the range (0.0, 1.0]. Defaults to 1.0.
687
+ compensated_sensor: Whether the effect of the double quantum dot on the sensor dot is compensated (lever-arms of
688
+ the double dot gate voltages on the sensor are close to zero). Defaults to False.
689
+ set_sensor_potential_offset_to_steepest_point: Whether the steepest point of the sensor function is determined
690
+ and the corresponding potential is set as the potential offset (offset_mu_sens). This enables simple
691
+ "retuning" of the sensor by always resetting the potential offset before every CSD measurement. Defaults to
692
+ False.
693
+
694
+ Returns:
695
+ SensorScanSensorGeneric: Fully initialized SensorScanSensorGeneric object.
696
+ """
697
+ alpha_sensor_size = 0.1
698
+ alpha_barrier_size = 0.99
699
+ barrier_height = 0.25
700
+
701
+ alpha_sensor_gate_sensor_range = (0, 2)
702
+ alpha_sensor_gate_sensor_std = 0.0846429965722433
703
+ alpha_sensor_gate_sensor_mean = 0.9986776964521298
704
+
705
+ alpha_sensor_gate_barrier_range = (0.1, 0.3)
706
+
707
+ base_point_range = (-0.688, -0.509)
708
+ base_point_pot_offset = 0.005 * 10 # Offset that specifies how much sensor potential before the base point potential the wavefronts should begin
709
+
710
+ include_deformations = False
711
+ deformation_circular_percentage = 4.5
712
+ deformation_linear_percentage = 4.3
713
+ deformation_circular_radius_range = (1, 1.5)
714
+ deformation_linear_angle_range = (math.radians(85), math.radians(95)) # The angles have to be specified in radian
715
+
716
+ # Sample alpha gate sensor
717
+ # Sample alpha gate sensor - sensor
718
+ alpha_sensor_gate_sensor_ratio_sampler = NormalSamplingRange(total_range=alpha_sensor_gate_sensor_range,
719
+ std=alpha_sensor_gate_sensor_std,
720
+ mean=alpha_sensor_gate_sensor_mean)
721
+ alpha_sensor_gate_sensor_ratio = alpha_sensor_gate_sensor_ratio_sampler.sample_parameter()
722
+ alpha_sensor_gate_sensor = np.array([1, 1 / alpha_sensor_gate_sensor_ratio])
723
+ alpha_sensor_gate_sensor = (alpha_sensor_gate_sensor / np.max(alpha_sensor_gate_sensor)) * alpha_sensor_size
724
+
725
+ # Sample alpha gate sensor - barrier
726
+ alpha_sensor_gate_barrier_sampler = UniformSamplingRange(total_range=alpha_sensor_gate_barrier_range)
727
+
728
+ alpha_sensor_gate_barrier1 = np.array([alpha_barrier_size, alpha_sensor_gate_barrier_sampler.sample_parameter()])
729
+ alpha_sensor_gate_barrier2 = np.array([alpha_sensor_gate_barrier_sampler.sample_parameter(), alpha_barrier_size])
730
+
731
+ alpha_sensor_gate = np.array([
732
+ alpha_sensor_gate_sensor,
733
+ alpha_sensor_gate_barrier1,
734
+ alpha_sensor_gate_barrier2
735
+ ])
736
+
737
+ if compensated_sensor:
738
+ alpha_gate_1_min, alpha_gate_1_max = 0.0001, 0.0005
739
+ alpha_gate_2_min, alpha_gate_2_max = 0.0001, 0.0005 # Currently not used, as we sample alpha gate 2 in dependence on alpha gate 1. Kept for the sake of completeness.
740
+ else:
741
+ alpha_gate_1_min, alpha_gate_1_max = 0.02805, 0.15093
742
+ alpha_gate_2_min, alpha_gate_2_max = 0.03788, 0.19491 # Currently not used, as we sample alpha gate 2 in dependence on alpha gate 1. Kept for the sake of completeness.
743
+
744
+ alpha_dot_1_min, alpha_dot_1_max = -0.0007994, -0.0000961
745
+ alpha_dot_1_max = alpha_dot_1_min + quality_multiplier * (
746
+ alpha_dot_1_max - alpha_dot_1_min)
747
+ alpha_dot_2_min, alpha_dot_2_max = -0.0005214, -0.0000630
748
+ alpha_dot_2_max = alpha_dot_2_min + quality_multiplier * (
749
+ alpha_dot_2_max - alpha_dot_2_min)
750
+
751
+ # create random number generator
752
+ rng = np.random.default_rng()
753
+
754
+ # sample alpha gates and alpha dots
755
+ alpha_gate_1 = rng.uniform(low=alpha_gate_1_min, high=alpha_gate_1_max)
756
+ alpha_gate_2 = rng.uniform(low=0.5 * alpha_gate_1, high=2 * alpha_gate_1)
757
+
758
+ # sample alpha dot 1 depending on alpha gate 1
759
+ # -0.03 / 100 = at least as much change for occ change (alpha dot) as change for sweeping one pixel (alpha gate)
760
+ # Keep in mind negative means, max is actually min (think of absolute values).
761
+ # Set to 2* alpha gate per pixel value, because stepping one pixel adds 1* and we want to jump back at least
762
+ # 1* pixel step (starting with random_variations_v2).
763
+ # Should actually always ensure to have higher min sensitivity (max = min because of negative values). Therefore,
764
+ # np.min with the actual value has been added (starting with random_variations_v3).
765
+ temp_alpha_dot_1_max = np.min([2 * alpha_gate_1 * -0.03 / 100,
766
+ alpha_dot_1_max])
767
+ alpha_dot_1 = rng.uniform(low=alpha_dot_1_min, high=temp_alpha_dot_1_max)
768
+
769
+ # sample alpha dot 2 depending on alpha gate 2 and alpha dot 1
770
+ # Set to 2* alpha gate per pixel value, because stepping one pixel adds 1* and we want to jump back at least
771
+ # 1* pixel step (starting with random_variations_v2).
772
+ # Should actually always ensure to have higher min sensitivity (max = min because of negative values). Therefore,
773
+ # np.min with the actual value has been added (starting with random_variations_v3).
774
+ temp_sensor_alpha_dot_2_max = np.min([0.5 * alpha_dot_1, 2 * alpha_gate_2 * -0.03 / 100,
775
+ alpha_dot_2_max])
776
+ if temp_sensor_alpha_dot_2_max < alpha_dot_2_min:
777
+ temp_sensor_alpha_dot_2_max = alpha_dot_2_max
778
+ # Should actually only ensure to be at most twice as strongly coupled compared to dot 1, but never stronger than
779
+ # given by the pre-defined range. Therefore, np.max with the actual value has been added (starting with
780
+ # random_variations_v3).
781
+ temp_sensor_alpha_dot_2_min = np.max([2 * alpha_dot_1, alpha_dot_2_min])
782
+ alpha_dot_2 = rng.uniform(low=temp_sensor_alpha_dot_2_min, high=temp_sensor_alpha_dot_2_max)
783
+
784
+ alpha_gate = np.array((alpha_gate_1, alpha_gate_2))
785
+ alpha_dot = np.array((alpha_dot_1, alpha_dot_2))
786
+
787
+ # Base Point
788
+ base_point_sampler = UniformSamplingRange(total_range=base_point_range)
789
+ base_point = np.array([base_point_sampler.sample_parameter(), base_point_sampler.sample_parameter()])
790
+
791
+ sensor_func_offset = np.sum(alpha_sensor_gate_sensor * base_point) - base_point_pot_offset
792
+
793
+ barrier_1_start = np.sum(alpha_sensor_gate_barrier1 * base_point)
794
+ barrier_2_start = np.sum(alpha_sensor_gate_barrier2 * base_point)
795
+
796
+ sensor_func, final_rise = sample_sensor_function(
797
+ offset=sensor_func_offset,
798
+ quality_multiplier=quality_multiplier
799
+ )
800
+
801
+ deformation_dict = {}
802
+ # Sample deformations
803
+ if include_deformations:
804
+ angle_sampler = UniformSamplingRange(
805
+ total_range=deformation_linear_angle_range)
806
+ radius_sampler = UniformSamplingRange(total_range=deformation_circular_radius_range)
807
+
808
+ for wavefront_num in range(len(sensor_func) - 1):
809
+ deformation_ran_num = random.random() * 100
810
+
811
+ if deformation_ran_num < deformation_linear_percentage:
812
+ deformation_dict[wavefront_num] = SensorPeakDeformationLinear(
813
+ angle=angle_sampler.sample_parameter()
814
+ )
815
+
816
+ elif deformation_ran_num < deformation_linear_percentage + deformation_circular_percentage:
817
+ sign = random.choice([-1, 1])
818
+
819
+ deformation_dict[wavefront_num] = SensorPeakDeformationCircle(
820
+ radius=sign * radius_sampler.sample_parameter()
821
+ )
822
+
823
+ return SensorScanSensorGeneric(
824
+ barrier_functions=(
825
+ sample_barrier_function(
826
+ offset=barrier_1_start,
827
+ height=barrier_height,
828
+ quality_multiplier=quality_multiplier
829
+ ),
830
+ sample_barrier_function(
831
+ offset=barrier_2_start,
832
+ height=barrier_height,
833
+ quality_multiplier=quality_multiplier
834
+ )
835
+
836
+ ),
837
+ sensor_peak_function=sensor_func,
838
+ final_rise = final_rise,
839
+ sensor_peak_deformations=deformation_dict,
840
+ alpha_sensor_gate=alpha_sensor_gate,
841
+ alpha_gate=alpha_gate,
842
+ alpha_dot=alpha_dot
843
+ )
844
+
845
+
846
+ def sample_occupation_distortions(quality_multiplier: float = 1.0) -> List[OccupationDistortionInterface]:
847
+ """
848
+ Sample the list of occupation distortions for the SimCATS Simulation class.
849
+
850
+ Args:
851
+ quality_multiplier: The quality multiplier to use, which defines how much of the ranges for the strength of
852
+ distortions is used. This basically allows to restrict configurations to better data quality, as future
853
+ improvements of the sample quality are expected. The factor must be in the range (0.0, 1.0]. Defaults to
854
+ 1.0.
855
+
856
+ Returns:
857
+ List[OccupationDistortionInterface]: List of all occupation distortions for the GaAs_v1_random_variations_v3
858
+ configuration.
859
+ """
860
+ return [_sample_occupation_transition_blurring_fermi_dirac(),
861
+ _sample_occupation_dot_jumps_g2(),
862
+ _sample_occupation_dot_jumps_g1()]
863
+
864
+
865
+ def sample_sensor_potential_distortions(quality_multiplier: float = 1.0) -> List[SensorPotentialDistortionInterface]:
866
+ """
867
+ Sample the list of sensor potential distortions for the SimCATS Simulation class.
868
+
869
+ Args:
870
+ quality_multiplier: The quality multiplier to use, which defines how much of the ranges for the strength of
871
+ distortions is used. This basically allows to restrict configurations to better data quality, as future
872
+ improvements of the sample quality are expected. The factor must be in the range (0.0, 1.0]. Defaults to
873
+ 1.0.
874
+
875
+ Returns:
876
+ List[SensorPotentialDistortionInterface]: List of all sensor potential distortions for the
877
+ GaAs_v1_random_variations_v3 configuration.
878
+ """
879
+ return [_sample_sensor_potential_pink_noise(quality_multiplier=quality_multiplier),
880
+ _sample_sensor_potential_rtn(quality_multiplier=quality_multiplier)]
881
+
882
+
883
+ def sample_sensor_response_distortions(quality_multiplier: float = 1.0) -> List[SensorResponseDistortionInterface]:
884
+ """
885
+ Sample the list of sensor response distortions for the SimCATS Simulation class.
886
+
887
+ Args:
888
+ quality_multiplier: The quality multiplier to use, which defines how much of the ranges for the strength of
889
+ distortions is used. This basically allows to restrict configurations to better data quality, as future
890
+ improvements of the sample quality are expected. The factor must be in the range (0.0, 1.0]. Defaults to
891
+ 1.0.
892
+
893
+ Returns:
894
+ List[SensorResponseDistortionInterface]: List of all sensor response distortions for the
895
+ GaAs_v1_random_variations_v3 configuration.
896
+ """
897
+ return [_sample_sensor_response_rtn(quality_multiplier=quality_multiplier),
898
+ _sample_sensor_response_white_noise(quality_multiplier=quality_multiplier)]
899
+
900
+
901
+ def _add_one_tct(tct_params: List[np.ndarray],
902
+ shift_vec: np.ndarray) -> List[np.ndarray]:
903
+ """
904
+ Helper function that adds one TCT to the list of TCTs, by shifting the last TCT by the shift vector.
905
+
906
+ Args:
907
+ tct_params: List of TCTs (or more precisely TCT parameters).
908
+ shift_vec: The vector that describes the shift between two adjacent TCTs.
909
+
910
+ Returns:
911
+ List[np.ndarray]: The list of TCTs including one additional TCT.
912
+ """
913
+ temp_params = tct_params[-1].copy()
914
+ temp_params[4] += shift_vec[0]
915
+ temp_params[5] += shift_vec[1]
916
+ temp_params[6] += shift_vec[0]
917
+ temp_params[7] += shift_vec[1]
918
+ tct_params.append(temp_params)
919
+ return tct_params
920
+
921
+
922
+ def _sample_occupation_transition_blurring_fermi_dirac() -> OccupationTransitionBlurringFermiDirac:
923
+ """
924
+ Sample parameters and initialize an OccupationTransitionBlurringFermiDirac object.
925
+
926
+ Returns:
927
+ OccupationTransitionBlurringFermiDirac: Fully initialized OccupationTransitionBlurringFermiDirac object.
928
+ """
929
+ occ_trans_blur_fermi_dirac_sigma_min = (
930
+ 0.25 * 0.03 / 100
931
+ )
932
+ occ_trans_blur_fermi_dirac_sigma_max = 2 * 0.03 / 100 # changed min and max starting with random_variations_v2
933
+ # use 3*sigma distance from mean to borders
934
+ std_transition_blurring = (occ_trans_blur_fermi_dirac_sigma_max - occ_trans_blur_fermi_dirac_sigma_min) / 6
935
+ return OccupationTransitionBlurringFermiDirac(
936
+ sigma=NormalSamplingRange(
937
+ total_range=(occ_trans_blur_fermi_dirac_sigma_min, occ_trans_blur_fermi_dirac_sigma_max),
938
+ std=std_transition_blurring,
939
+ ) # using other sampler starting with random_variations_v2
940
+ )
941
+
942
+
943
+ def _sample_occupation_dot_jumps_g1() -> OccupationDotJumps:
944
+ """
945
+ Sample parameters and initialize an OccupationDotJumps object affecting g1.
946
+
947
+ Returns:
948
+ OccupationDotJumps: Fully initialized OccupationDotJumps object.
949
+ """
950
+ return OccupationDotJumps(ratio=0.01 / 6,
951
+ scale=100 * 0.03 / 100,
952
+ lam=6 * 0.03 / 100,
953
+ axis=1)
954
+
955
+
956
+ def _sample_occupation_dot_jumps_g2() -> OccupationDotJumps:
957
+ """
958
+ Sample parameters and initialize an OccupationDotJumps object affecting g2.
959
+
960
+ Returns:
961
+ OccupationDotJumps: Fully initialized OccupationDotJumps object.
962
+ """
963
+ return OccupationDotJumps(ratio=0.01,
964
+ scale=100 * 0.03 / 100,
965
+ lam=6 * 0.03 / 100,
966
+ axis=0)
967
+
968
+
969
+ def _sample_sensor_potential_pink_noise(quality_multiplier: float = 1.0) -> SensorPotentialPinkNoise:
970
+ """
971
+ Sample parameters and initialize a SensorPotentialPinkNoise object.
972
+
973
+ Args:
974
+ quality_multiplier: The quality multiplier to use, which defines how much of the ranges for the strength of
975
+ distortions is used. This basically allows to restrict configurations to better data quality, as future
976
+ improvements of the sample quality are expected. The factor must be in the range (0.0, 1.0]. Defaults to
977
+ 1.0.
978
+
979
+ Returns:
980
+ SensorPotentialPinkNoise: Fully initialized SensorPotentialPinkNoise object.
981
+ """
982
+ sensor_pot_pink_sigma_min, sensor_pot_pink_sigma_max = 1e-10, 0.0005 # reduced max starting with random_variations_v2
983
+ quantile_relative_pos = 0.6 * quality_multiplier
984
+ quantile_percentage = 0.99
985
+ exp_scale_sensor_dot_pink = quantile_relative_pos / np.log(1 / (1 - quantile_percentage))
986
+ return SensorPotentialPinkNoise(
987
+ sigma=ExponentialSamplingRange(
988
+ total_range=(sensor_pot_pink_sigma_min, sensor_pot_pink_sigma_max),
989
+ scale=exp_scale_sensor_dot_pink
990
+ ) # using other sampler starting with random_variations_v2
991
+ )
992
+
993
+
994
+ def _sample_sensor_potential_rtn(quality_multiplier: float = 1.0) -> SensorPotentialRTN:
995
+ """
996
+ Sample parameters and initialize a SensorPotentialRTN object.
997
+
998
+ Args:
999
+ quality_multiplier: The quality multiplier to use, which defines how much of the ranges for the strength of
1000
+ distortions is used. This basically allows to restrict configurations to better data quality, as future
1001
+ improvements of the sample quality are expected. The factor must be in the range (0.0, 1.0]. Defaults to
1002
+ 1.0.
1003
+
1004
+ Returns:
1005
+ SensorPotentialRTN: Fully initialized SensorPotentialRTN object.
1006
+ """
1007
+ return SensorPotentialRTN(
1008
+ scale=74.56704 * 0.03 / 100,
1009
+ std=3.491734e-05,
1010
+ height=2.53855325e-05,
1011
+ ratio=1 / 6 * quality_multiplier,
1012
+ )
1013
+
1014
+
1015
+ def _sample_sensor_response_rtn(quality_multiplier: float = 1.0) -> SensorResponseRTN:
1016
+ """
1017
+ Sample parameters and initialize a SensorResponseRTN object.
1018
+
1019
+ Args:
1020
+ quality_multiplier: The quality multiplier to use, which defines how much of the ranges for the strength of
1021
+ distortions is used. This basically allows to restrict configurations to better data quality, as future
1022
+ improvements of the sample quality are expected. The factor must be in the range (0.0, 1.0]. Defaults to
1023
+ 1.0.
1024
+
1025
+ Returns:
1026
+ SensorResponseRTN: Fully initialized SensorResponseRTN object.
1027
+ """
1028
+ return SensorResponseRTN(
1029
+ scale=10000 * 0.03 / 100,
1030
+ std=0.047453767599999995,
1031
+ height=0.0152373696,
1032
+ ratio=0.01 * quality_multiplier,
1033
+ # reduced to 0.01 from 0.03 (not required as often with lots of images)
1034
+ )
1035
+
1036
+
1037
+ def _sample_sensor_response_white_noise(quality_multiplier: float = 1.0) -> SensorResponseWhiteNoise:
1038
+ """
1039
+ Sample parameters and initialize a SensorResponseWhiteNoise object.
1040
+
1041
+ Args:
1042
+ quality_multiplier: The quality multiplier to use, which defines how much of the ranges for the strength of
1043
+ distortions is used. This basically allows to restrict configurations to better data quality, as future
1044
+ improvements of the sample quality are expected. The factor must be in the range (0.0, 1.0]. Defaults to
1045
+ 1.0.
1046
+
1047
+ Returns:
1048
+ SensorResponseWhiteNoise: Fully initialized SensorResponseWhiteNoise object.
1049
+ """
1050
+ sensor_res_white_sigma_min, sensor_res_white_sigma_max = 1e-10, 0.0005 # reduced max starting with random_variations_v2
1051
+ quantile_relative_pos = 0.6 * quality_multiplier
1052
+ quantile_percentage = 0.99
1053
+ exp_scale_res_white = quantile_relative_pos / np.log(1 / (1 - quantile_percentage))
1054
+ return SensorResponseWhiteNoise(
1055
+ sigma=ExponentialSamplingRange(
1056
+ total_range=(sensor_res_white_sigma_min, sensor_res_white_sigma_max),
1057
+ scale=exp_scale_res_white
1058
+ ) # using other sampler starting with random_variations_v2
1059
+ )