sodetlib 0.6.1rc1__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.
@@ -0,0 +1,783 @@
1
+ import numpy as np
2
+ import time
3
+ import sodetlib as sdl
4
+ from sodetlib.operations import tracking
5
+
6
+ import matplotlib.pyplot as plt
7
+
8
+
9
+ def find_gate_voltage(S, target_Id, amp_name, vg_min=-2.0, vg_max=0,
10
+ max_iter=50, wait_time=0.5, id_tolerance=0.2):
11
+ """
12
+ Scans through bias voltage for hemt or 50K amplifier to get the correct
13
+ gate voltage for a target current.
14
+
15
+ Args
16
+ -----
17
+ S: pysmurf.client.SmurfControl
18
+ PySmurf control object
19
+ target_Id: float
20
+ Target amplifier current
21
+ vg_min: float
22
+ Minimum allowable gate voltage
23
+ vg_max: float
24
+ Maximum allowable gate voltage
25
+ amp_name: str
26
+ Name of amplifier. Must be one of ['hemt', 'hemt1', 'hemt2', '50k', '50k1', '50k2'].
27
+ max_iter: int, optional
28
+ Maximum number of iterations to find voltage. Defaults to 30.
29
+ wait_time : float
30
+ Time to wait after setting the voltage at each step. Defaults
31
+ to 0.5 sec
32
+ id_tolerance : float
33
+ Max difference between target drain current and actual drain currents
34
+ for this to be considered success (mA). Defaults to 0.2 mA.
35
+
36
+ Returns
37
+ --------
38
+ success : bool
39
+ Returns a boolean signaling whether voltage scan has been successful.
40
+ The set voltages can be read with S.get_amplifier_biases().
41
+ """
42
+ all_amps = S.C.list_of_c02_and_c04_amps
43
+ if amp_name not in all_amps:
44
+ raise ValueError(f"amp_name must be one of {all_amps}")
45
+
46
+ for _ in range(max_iter):
47
+ amp_biases = S.get_amplifier_biases()
48
+ Vg = amp_biases[f"{amp_name}_gate_volt"]
49
+ Id = amp_biases[f"{amp_name}_drain_current"]
50
+ delta = target_Id - Id
51
+
52
+ S.log(delta)
53
+ # Check if Id is within tolerance
54
+ if np.abs(delta) < id_tolerance:
55
+ return True
56
+
57
+ if 'hemt' in amp_name:
58
+ step = np.sign(delta) * (0.1 if np.abs(delta) > 1.5 else 0.01)
59
+ else:
60
+ step = np.sign(delta) * (0.01 if np.abs(delta) > 1.5 else 0.001)
61
+
62
+ Vg_next = Vg + step
63
+ if not (vg_min < Vg_next < vg_max):
64
+ S.log(f"Gate voltage adjustment would go out of range ({vg_min}, {vg_max}). "
65
+ f"Unable to change {amp_name}_drain_current to desired value", False)
66
+ return False
67
+
68
+ if 'hemt' in amp_name:
69
+ S.set_amp_gate_voltage(amp_name, Vg_next, override=True)
70
+ else:
71
+ S.set_amp_gate_voltage(amp_name, Vg_next, override=True)
72
+
73
+ time.sleep(wait_time)
74
+
75
+ S.log(f"Max allowed Vg iterations ({max_iter}) has been reached. "
76
+ f"Unable to get target drain current for {amp_name}.", False)
77
+
78
+ return False
79
+
80
+ @sdl.set_action()
81
+ def setup_amps(S, cfg, update_cfg=True, enable_300K_LNA=True):
82
+ """
83
+ Initial setup for 50k and hemt amplifiers. For C04/C05 cryocards, will first
84
+ check if the drain voltages are set. Then checks if drain
85
+ currents are in range, and if not will scan gate voltage to find one that
86
+ hits the target current. Will update the device cfg if successful.
87
+
88
+ The following parameters can be modified in the device cfg, where {amp}
89
+ is one of ['hemt', 'hemt1', 'hemt2', '50k', '50k1', '50k2']:
90
+
91
+ exp:
92
+ - amp_enable_wait_time (float): Seconds to wait after enabling amps
93
+ before scanning gate voltages
94
+ - amp_{amp}_drain_current (float): Target drain current (mA)
95
+ - amp_{amp}_drain_current_tolerance (float): Tolerance for drain current (mA)
96
+ - amp_{amp}_drain_volt (float) : Drain voltage (V). C04/C05 cryocards only.
97
+
98
+ Args
99
+ -----
100
+ S : SmurfControl
101
+ Pysmurf instance
102
+ cfg : DetConfig
103
+ DetConfig instance
104
+ update_cfg : bool
105
+ If true, will update the device cfg and save the file.
106
+ enable_300K_LNA:
107
+ If true, will turn on the 300K LNAs.
108
+ """
109
+ sdl.pub_ocs_log(S, "Starting setup_amps")
110
+
111
+ exp = cfg.dev.exp
112
+
113
+ # Determine cryocard rev
114
+ major, minor, patch = S.C.get_fw_version()
115
+ if major == 4:
116
+ cc_rev = 'c04'
117
+ amp_list = S.C.list_of_c04_amps
118
+ elif major == 0:
119
+ if sum((major,minor,patch)) == 0:
120
+ raise ValueError("Error communicatin with cryocard; "
121
+ + "is it connected?")
122
+ else:
123
+ raise ValueError(f"Unrecognized cryocard firmware version "
124
+ + "({major},{minor},{patch}).")
125
+ else:
126
+ cc_rev = 'c02'
127
+ amp_list = S.C.list_of_c02_amps
128
+ S.C.write_ps_en(0b11)
129
+ time.sleep(exp['amp_enable_wait_time'])
130
+ if S.C.read_ps_en() != 3:
131
+ raise ValueError("Could not enable amps.")
132
+
133
+ amp_list = list(set(amp_list) & set(exp['amps_to_bias']))
134
+ if len(amp_list) == 0:
135
+ raise ValueError(
136
+ f"exp['amps_to_bias']: {exp['amps_to_bias']} contains no valid amps."
137
+ )
138
+ # Data to be passed back to the ocs-pysmurf-controller clients
139
+ summary = {'success': False}
140
+ for amp in amp_list:
141
+ summary[f'{amp}_gate_volt'] = None
142
+ summary[f'{amp}_drain_current'] = None
143
+ summary[amp + '_enable'] = None
144
+ if cc_rev == 'c04':
145
+ summary[f'{amp}_drain_volt'] = None
146
+
147
+ amp_biases = S.get_amplifier_biases()
148
+
149
+ # For C04, first check drain voltages
150
+ if cc_rev == 'c04':
151
+ for amp in amp_list:
152
+ Vd = amp_biases[f"{amp}_drain_volt"]
153
+ if Vd != exp[f"amp_{amp}_drain_volt"]:
154
+ S.set_amp_drain_voltage(amp, exp[f"amp_{amp}_drain_volt"])
155
+
156
+ # Check drain currents / scan gate voltages
157
+ delta_drain_currents = dict()
158
+ for amp in amp_list:
159
+ delta_Id = np.abs(amp_biases[f"{amp}_drain_current"] - exp[f"amp_{amp}_drain_current"])
160
+ if delta_Id > exp[f'amp_{amp}_drain_current_tolerance']:
161
+ S.log(f"{amp} current not within tolerance, scanning for correct gate voltage")
162
+ S.set_amp_gate_voltage(amp, exp[f'amp_{amp}_init_gate_volt'],override=True)
163
+ success = find_gate_voltage(
164
+ S, exp[f"amp_{amp}_drain_current"], amp, wait_time=exp['amp_step_wait_time'],
165
+ id_tolerance=exp[f'amp_{amp}_drain_current_tolerance']
166
+ )
167
+ if not success:
168
+ sdl.pub_ocs_log(S, f"Failed determining {amp} gate voltage")
169
+ sdl.set_session_data(S, 'setup_amps_summary', summary)
170
+ S.C.write_ps_en(0)
171
+ return False, summary
172
+ # Turn on 300K LNAs
173
+ if enable_300K_LNA:
174
+ S.C.write_optical(0b11)
175
+
176
+ # Update device cfg
177
+ biases = S.get_amplifier_biases()
178
+ if update_cfg:
179
+ for amp in amp_list:
180
+ cfg.dev.update_experiment({f'amp_{amp}_gate_volt': biases[f'{amp}_gate_volt']},
181
+ update_file=True)
182
+
183
+ summary = {'success': True, **biases}
184
+ sdl.set_session_data(S, 'setup_amps_summary', summary)
185
+ return True, summary
186
+
187
+
188
+ @sdl.set_action()
189
+ def setup_phase_delay(S, cfg, bands, update_cfg=True):
190
+ """
191
+ Runs estimate phase delay and updates the device cfg with the results.
192
+ This will run with the current set of attenuation values.
193
+
194
+ Args
195
+ -----
196
+ S : SmurfControl
197
+ Pysmurf instance
198
+ cfg : DetConfig
199
+ DetConfig instance
200
+ uc_att : int
201
+ UC atten to use for phase-delay estimation
202
+ dc_att : int
203
+ DC atten to use for phase-delay estimation
204
+ update_cfg : bool
205
+ If true, will update the device cfg and save the file.
206
+ """
207
+ sdl.pub_ocs_log(S, f"Estimating phase delay for bands {bands}")
208
+
209
+ summary = {
210
+ 'bands': [],
211
+ 'band_delay_us': [],
212
+ }
213
+ for b in bands:
214
+ summary['bands'].append(int(b))
215
+ band_delay_us, _ = S.estimate_phase_delay(b, make_plot=True, show_plot=False)
216
+ band_delay_us = float(band_delay_us)
217
+ summary['band_delay_us'].append(band_delay_us)
218
+ if update_cfg:
219
+ cfg.dev.bands[b].update({
220
+ 'band_delay_us': band_delay_us
221
+ })
222
+
223
+ if update_cfg:
224
+ cfg.dev.update_file()
225
+
226
+ sdl.set_session_data(S, 'setup_phase_delay', summary)
227
+ return True, summary
228
+
229
+
230
+ def estimate_uc_dc_atten(S, cfg, band, update_cfg=True, tone_power=None):
231
+ """
232
+ Provides an initial estimation of uc and dc attenuation for a band in order
233
+ to get the band response at a particular frequency to be within a certain
234
+ range. The goal of this function is to choose attenuations such that the
235
+ ADC isn't saturated, and the response is large enough to find resonators.
236
+
237
+ For simplicity, this searches over the parameter space where uc and dc
238
+ attens are equal, instead of searching over the full 2d space.
239
+
240
+ For further optimization of attens with respect to median white noise, run
241
+ the ``optimize_attens`` function from the
242
+ ``sodetlib/operations/optimize.py``
243
+ module.
244
+
245
+ The following parameters can be modified in the device cfg:
246
+
247
+ bands:
248
+ - tone_power (int): Tone power to use for atten estimation
249
+
250
+ Args
251
+ -----
252
+ S : SmurfControl
253
+ Pysmurf instance
254
+ band : int
255
+ Band to estimate attens for
256
+ update_cfg : bool
257
+ If true, will update the device cfg and save the file. """
258
+ att, step = 15, 7
259
+ sb = S.get_closest_subband(-200, band)
260
+
261
+ resp_range = (0.1, 0.4)
262
+ # Just look at 5 subbands after specified freq.
263
+ sbs = np.arange(sb, sb + 5)
264
+ sbs = np.arange(250, 255)
265
+ nread = 2
266
+
267
+ success = False
268
+ S.log(f"Estimating attens for band {band}")
269
+
270
+ if tone_power is None:
271
+ tone_power = cfg.dev.bands[band]['tone_power']
272
+
273
+ while True:
274
+ S.set_att_uc(band, att)
275
+ S.set_att_dc(band, att)
276
+
277
+ S.log(f'tone_power: {tone_power}')
278
+ _, resp = S.full_band_ampl_sweep(band, sbs, cfg.dev.bands[band]['tone_power'], nread)
279
+ max_resp = np.max(np.abs(resp))
280
+ S.log(f"att: {att}, max_resp: {max_resp}")
281
+
282
+ if resp_range[0] < max_resp < resp_range[1]:
283
+ S.log(f"Estimated atten: {att}")
284
+ success = True
285
+ break
286
+
287
+ if max_resp < resp_range[0]:
288
+ if att == 0:
289
+ S.log(f"Cannot achieve resp in range {resp_range}! Try increasing tone power")
290
+ success = False
291
+ break
292
+ att -= step
293
+
294
+ elif max_resp > resp_range[1]:
295
+ if att == 30:
296
+ S.log(f"Cannot achieve resp in range {resp_range}! Try decreasing tone power")
297
+ success = False
298
+ break
299
+ att += step
300
+ step = int(np.ceil(step / 2))
301
+
302
+ if success and update_cfg:
303
+ cfg.dev.update_band(band, {
304
+ 'uc_att': att,
305
+ 'dc_att': att,
306
+ }, update_file=True)
307
+
308
+ return success
309
+
310
+
311
+ @sdl.set_action()
312
+ def setup_tune(S, cfg, bands, show_plots=False, update_cfg=True):
313
+ """
314
+ Find freq, setup notches, and serial gradient descent and eta scan
315
+
316
+ The following parameters can be modified in the device cfg:
317
+
318
+ exp:
319
+ - res_amp_cut (float): Amplitude cut for peak-finding in find-freq
320
+ - res_grad_cut (float): Gradient cut for peak-finding in find-freq
321
+ bands:
322
+ - tone_power (int): Tone power to use for atten estimation
323
+
324
+ Args
325
+ -----
326
+ S : SmurfControl
327
+ Pysmurf instance
328
+ cfg : DetConfig
329
+ DetConfig instance
330
+ show_plots : bool
331
+ If true, will show find_freq plots. Defaults to False
332
+ update_cfg : bool
333
+ If true, will update the device cfg and save the file.
334
+ """
335
+ bands = np.atleast_1d(bands)
336
+ sdl.pub_ocs_log(S, f"Starting setup_tune for bands {bands}")
337
+
338
+ exp = cfg.dev.exp
339
+
340
+ summary = {}
341
+
342
+ for band in bands:
343
+ sdl.pub_ocs_log(S, f"Find Freq: band {band}")
344
+ bcfg = cfg.dev.bands[band]
345
+ S.find_freq(band, tone_power=bcfg['tone_power'], make_plot=True,
346
+ save_plot=True, show_plot=show_plots,
347
+ amp_cut=exp['res_amp_cut'],
348
+ grad_cut=exp['res_grad_cut'])
349
+
350
+ for band in bands:
351
+ bcfg = cfg.dev.bands[band]
352
+ sdl.pub_ocs_log(S, f"Setup Notches: band {band}")
353
+ S.setup_notches(band, tone_power=bcfg['tone_power'], new_master_assignment=True)
354
+
355
+ for band in bands:
356
+ sdl.pub_ocs_log(S, f"Serial grad descent and eta scan: band {band}")
357
+ S.run_serial_gradient_descent(band)
358
+ S.run_serial_eta_scan(band)
359
+
360
+ if update_cfg:
361
+ cfg.dev.update_experiment({'tunefile': S.tune_file}, update_file=True)
362
+
363
+ return True, summary
364
+
365
+
366
+ def _find_fixed_tone_freq(S, band, ntone, min_spacing, band_start=-250, band_end=250):
367
+ """
368
+ Place fixed tones in the largest gaps between resonators.
369
+
370
+ Args
371
+ ----
372
+ S : SmurfControl
373
+ Pysmurf instance
374
+ band : int
375
+ The band to populate with fixed tones.
376
+ ntone : int
377
+ The number of fixed tones to attempt to place in this band.
378
+ min_spacing : float
379
+ The minimum spacing in MHz between resonators to place a fixed tone in.
380
+ band_start : float
381
+ The lower edge of the band in MHz relative to band center.
382
+ band_end : float
383
+ The upper edge of the band in MHz relative to band center.
384
+ """
385
+
386
+ # get resonator and channel frequencies
387
+ res_freq = np.sort([res["freq"] for res in S.freq_resp[band]["resonances"].values()])
388
+
389
+ # only consider central part of the band
390
+ band_center = S.get_band_center_mhz(band)
391
+ freq_low = band_center + band_start
392
+ freq_high = band_center + band_end
393
+ res_freq = res_freq[(res_freq > freq_low) & (res_freq < freq_high)]
394
+
395
+ # add the band edges
396
+ res_freq = np.concatenate(([freq_low], res_freq, [freq_high]))
397
+
398
+ # gaps between resonators
399
+ gaps = np.diff(res_freq)
400
+
401
+ # check there are enough for the number of tones
402
+ ntone_per_gap = np.floor(gaps / min_spacing)
403
+ num_gaps = ntone_per_gap.sum()
404
+ if num_gaps < ntone:
405
+ S.log(
406
+ f"Only {num_gaps} gaps greater than {min_spacing} MHz were found in band {band}. "
407
+ "That number of fixed tones will be set."
408
+ )
409
+ ntone = int(num_gaps)
410
+ if ntone == 0:
411
+ return np.array([])
412
+ gaps = gaps[ntone_per_gap > 0]
413
+ res_freq = res_freq[np.where(ntone_per_gap > 0)[0]] # just the left edge now
414
+
415
+ # distribute uniformly in available space, accounting for edge buffer
416
+ gaps_cum = np.cumsum(gaps - min_spacing) # can place a tone anywhere in this space
417
+ tones = np.linspace(0, gaps_cum[-1], ntone, endpoint=True)
418
+ tone_freqs = np.zeros_like(tones)
419
+ last_edge = -1 # allow for tone on the leading 0 edge
420
+ for i in range(gaps.size):
421
+ # identify which gap they are in
422
+ tones_in_gap = (tones > last_edge) & (tones <= gaps_cum[i])
423
+ # shift back to real frequency
424
+ tone_freqs[tones_in_gap] = (
425
+ tones[tones_in_gap] - last_edge # position relative to buffer
426
+ + res_freq[i] + min_spacing / 2 # real edge in band + buffer
427
+ )
428
+ last_edge = gaps_cum[i]
429
+
430
+ return tone_freqs
431
+
432
+
433
+ @sdl.set_action()
434
+ def setup_fixed_tones(
435
+ S, cfg, bands=None, fixed_tones_per_band=8, min_gap=10, tone_power=10, update_cfg=True
436
+ ):
437
+ """
438
+ Identify gaps between resonators and attempt to place a number of fixed tones there.
439
+ These will be saved for every band in the device config. A tune should be available
440
+ before running this action.
441
+
442
+ Args
443
+ ----
444
+ S : SmurfControl
445
+ Pysmurf instance
446
+ cfg : DetConfig
447
+ DetConfig instance
448
+ bands : list of int
449
+ The bands to set fixed tones in. Get from config by default.
450
+ fixed_tones_per_band : int
451
+ The number of tones to place in each band. Defaults to 8.
452
+ min_gap : float
453
+ The minimum gap size in order to place a fixed tone, in MHz. Defaults to 10.
454
+ tone_power : int
455
+ The tone power to use for the fixed tones. Defaults to 10.
456
+ update_cfg : bool
457
+ Whether to save the fixed tone configuration to the device config. Defaults to True.
458
+
459
+ Returns
460
+ -------
461
+ fixed_tones : dict
462
+ Fixed tones configuration, as saved to device config.
463
+ """
464
+
465
+ if bands is None:
466
+ bands = cfg.dev.exp['active_bands']
467
+
468
+ # ensure we have a tune available
469
+ try:
470
+ _ = S.freq_resp[bands[0]]["resonances"]
471
+ except KeyError as e:
472
+ raise ValueError("No tune is available to set fixed tones from.") from e
473
+
474
+ # identify available frequencies
475
+ ft_freq = []
476
+ for b in bands:
477
+ ft_freq += list(_find_fixed_tone_freq(S, b, fixed_tones_per_band, min_gap))
478
+
479
+ # turn off any existing fixed tones
480
+ try:
481
+ channels = cfg.dev.bands[b]["fixed_tones"]["channels"]
482
+ amp = S.get_amplitude_scale_array(b)
483
+ amp[channels] = 0.0
484
+ S.set_amplitude_scale_array(b, amp)
485
+ S.log(f"Turned off current fixed tones on band {b}.")
486
+ except KeyError:
487
+ pass
488
+
489
+ # set the fixed tones
490
+ tunefile = cfg.dev.exp.get("tunefile", None)
491
+ fixed_tones = {
492
+ b: {
493
+ "enabled": False,
494
+ "freq_offsets": [],
495
+ "channels": [],
496
+ "tone_power": tone_power,
497
+ "tunefile": tunefile,
498
+ }
499
+ for b in bands
500
+ }
501
+ for fr in ft_freq:
502
+ # this function sets the tone power and disables feedback
503
+ try:
504
+ band, channel = S.set_fixed_tone(fr, tone_power=tone_power)
505
+ except AssertionError as e:
506
+ S.log(f"Failed to assign a fixed tone on frequency {fr} MHz.\n{e}.")
507
+ continue
508
+ # read back the assigned frequency offset
509
+ foff = S.get_center_frequency_mhz_channel(band, channel)
510
+ fixed_tones[band]["freq_offsets"].append(float(foff))
511
+ fixed_tones[band]["channels"].append(int(channel))
512
+ fixed_tones[band]["enabled"] = True
513
+
514
+ # update config
515
+ if update_cfg:
516
+ for band, data in fixed_tones.items():
517
+ cfg.dev.update_band(band, {"fixed_tones": data}, update_file=True)
518
+
519
+ S.log("Done setting fixed tones.")
520
+
521
+ return fixed_tones
522
+
523
+
524
+ @sdl.set_action()
525
+ def turn_off_fixed_tones(S, cfg, bands=None):
526
+ """
527
+ Read fixed tones from the device config and set their amplitude to 0.
528
+
529
+ Args
530
+ ----
531
+ S : SmurfControl
532
+ Pysmurf instance
533
+ cfg : DetConfig
534
+ DetConfig instance
535
+ bands : list of int
536
+ The bands to operate on. Get from config by default.
537
+ """
538
+
539
+ if bands is None:
540
+ bands = cfg.dev.exp['active_bands']
541
+
542
+ for band in bands:
543
+ # try to read from config
544
+ try:
545
+ if not cfg.dev.bands[band]["fixed_tones"].get("enabled", True):
546
+ S.log(f"Fixed tones are already disabled on band {band}.")
547
+ continue
548
+ channels = cfg.dev.bands[band]["fixed_tones"]["channels"]
549
+ except KeyError as e:
550
+ raise ValueError(
551
+ "No fixed tones present in device config."
552
+ ) from e
553
+
554
+ amp = S.get_amplitude_scale_array(band)
555
+ amp[channels] = 0
556
+ S.set_amplitude_scale_array(band, amp)
557
+ cfg.dev.bands[band]["fixed_tones"]["enabled"] = False
558
+
559
+
560
+ @sdl.set_action()
561
+ def turn_on_fixed_tones(S, cfg, bands=None):
562
+ """
563
+ Read fixed tones from the device config and ensure the corresponding channels
564
+ are set to the specified amplitude and have feedback disabled.
565
+
566
+ Args
567
+ ----
568
+ S : SmurfControl
569
+ Pysmurf instance
570
+ cfg : DetConfig
571
+ DetConfig instance
572
+ bands : list of int
573
+ The bands to operate on. Get from config by default.
574
+ """
575
+
576
+ if bands is None:
577
+ bands = cfg.dev.exp['active_bands']
578
+
579
+ for band in bands:
580
+ # try to read from config
581
+ try:
582
+ if not cfg.dev.bands[band]["fixed_tones"].get("enabled", False):
583
+ S.log(f"Fixed tones are not enabled on band {band}.")
584
+ continue
585
+ foff = cfg.dev.bands[band]["fixed_tones"]["freq_offsets"]
586
+ channels = cfg.dev.bands[band]["fixed_tones"]["channels"]
587
+ tone_power = cfg.dev.bands[band]["fixed_tones"].get("tone_power", None)
588
+ tunefile = cfg.dev.bands[band]["fixed_tones"].get("tunefile", None)
589
+ except KeyError as e:
590
+ raise ValueError(
591
+ "No fixed tones present in device config. Use `setup_fixed_tones` to add some."
592
+ ) from e
593
+ if len(foff) != len(channels):
594
+ raise ValueError(
595
+ "Number of fixed tone channels in device config is different from number of "
596
+ f"frequency offsets for band {band} ({len(channels)} != {len(foff)})."
597
+ )
598
+ cfg_tunefile = cfg.dev.exp.get("tunefile", None)
599
+ if cfg_tunefile is None or tunefile is None:
600
+ # warn but do nothing
601
+ S.log(
602
+ "Turning on fixed tones with no associated tunefile could lead "
603
+ "to conflicts with resonator channels.", S.LOG_ERROR
604
+ )
605
+ elif tunefile != cfg_tunefile:
606
+ raise ValueError(
607
+ "The tunefile has changed since fixed tones were set up. Run `setup_fixed_tones "
608
+ "to find available channels."
609
+ )
610
+ S.log(f"Enabling {len(channels)} fixed tones on band {band}.")
611
+
612
+ # set the channel center frequency for each tone
613
+ for df, chan in zip(foff, channels):
614
+ S.set_center_frequency_mhz_channel(band, chan, df)
615
+
616
+ # set amplitude and feedback
617
+ amp = S.get_amplitude_scale_array(band)
618
+ feedback = S.get_feedback_enable_array(band)
619
+
620
+ amp[channels] = tone_power
621
+ feedback[channels] = 0
622
+
623
+ S.set_amplitude_scale_array(band, amp)
624
+ S.set_feedback_enable_array(band, feedback)
625
+
626
+
627
+ @sdl.set_action()
628
+ def uxm_setup(S, cfg, bands=None, show_plots=True, update_cfg=True,
629
+ modify_attens=True, skip_phase_delay=False,
630
+ skip_setup_amps=False):
631
+ """
632
+ The goal of this function is to do a pysmurf setup completely from scratch,
633
+ meaning no parameters will be pulled from the device cfg.
634
+
635
+ The following steps will be run:
636
+
637
+ 1. setup amps
638
+ 2. Estimate phase delay
639
+ 3. Setup tune
640
+ 4. setup tracking
641
+ 5. Measure noise
642
+
643
+ The following device cfg parameters can be changed to modify behavior:
644
+
645
+ exp:
646
+ - downsample_factor (int): Downsample factor to use
647
+ - coupling_mode (str): Determines whether to run in DC or AC mode. Can
648
+ be 'dc' or 'ac'.
649
+ - synthesis_scale (int): Synthesis scale to use
650
+ - amp_enable_wait_time (float): Seconds to wait after enabling amps
651
+ before scanning gate voltages
652
+ - amp_hemt_Id (float): Target drain current for hemt amp (mA)
653
+ - amp_50k_Id (float): Target drain current for 50k amp (mA)
654
+ - amp_hemt_Id_tolerance (float): Tolerance for hemt drain current (mA)
655
+ - amp_50k_Id_tolerance (float): Tolerance for 50k drain current (mA)
656
+ - res_amp_cut (float): Amplitude cut for peak-finding in find-freq
657
+ - res_grad_cut (float): Gradient cut for peak-finding in find-freq
658
+ bands:
659
+ - tone_power (int): Tone power to use for atten estimation
660
+
661
+ Args
662
+ -----
663
+ S : SmurfControl
664
+ Pysmurf instance
665
+ cfg : DetConfig
666
+ DetConfig instance
667
+ show_plots : bool
668
+ If true, will show find_freq plots. Defaults to False
669
+ update_cfg : bool
670
+ If true, will update the device cfg and save the file.
671
+ modify_attens : bool
672
+ If true or attenuations are not set in the device config, will run
673
+ estimate_uc_dc_atten to find a set of attenuations that will work for
674
+ estimate_phase_delay and find_freq. If False and attenuation values
675
+ already exist in the device config, this will use those values.
676
+ skip_phase_delay : bool
677
+ If True, will skip the estimate_phase_delay step
678
+ skip_setup_amps : bool
679
+ If True, will skip amplifier setup
680
+ """
681
+ if bands is None:
682
+ bands = cfg.dev.exp['active_bands']
683
+ bands = np.atleast_1d(bands)
684
+
685
+ exp = cfg.dev.exp
686
+
687
+ #############################################################
688
+ # 1. Reset to known state
689
+ #############################################################
690
+ S.all_off() # Turn off Flux ramp, tones, and biases
691
+ S.set_rtm_arb_waveform_enable(0)
692
+ S.set_filter_disable(0)
693
+ S.set_downsample_factor(exp['downsample_factor'])
694
+ if exp['coupling_mode'] == 'dc':
695
+ S.set_mode_dc()
696
+ else:
697
+ S.set_mode_ac()
698
+
699
+ for band in bands:
700
+ S.set_synthesis_scale(band, exp['synthesis_scale'])
701
+
702
+ summary = {}
703
+ summary['timestamps'] = []
704
+
705
+ #############################################################
706
+ # 2. Setup amps
707
+ #############################################################
708
+ if not skip_setup_amps:
709
+ summary['timestamps'].append(('setup_amps', time.time()))
710
+ sdl.set_session_data(S, 'timestamps', summary['timestamps'])
711
+
712
+ success, summary['setup_amps'] = setup_amps(S, cfg, update_cfg=update_cfg)
713
+ if not success:
714
+ sdl.pub_ocs_log(S, "UXM Setup failed on setup amps step")
715
+ return False, summary
716
+
717
+ #############################################################
718
+ # 3. Estimate Attens
719
+ #############################################################
720
+ summary['timestamps'].append(('estimate_attens', time.time()))
721
+ sdl.set_session_data(S, 'timestamps', summary['timestamps'])
722
+ for band in bands:
723
+ bcfg = cfg.dev.bands[band]
724
+ if modify_attens or (bcfg['uc_att'] is None) or (bcfg['dc_att'] is None):
725
+ success = estimate_uc_dc_atten(S, cfg, band, update_cfg=update_cfg)
726
+ if not success:
727
+ sdl.pub_ocs_log(S, f"Failed to estimate attens on band {band}")
728
+ return False, summary
729
+ else:
730
+ S.set_att_uc(band, bcfg['uc_att'])
731
+ S.set_att_dc(band, bcfg['dc_att'])
732
+
733
+ #############################################################
734
+ # 4. Estimate Phase Delay
735
+ #############################################################
736
+ if not skip_phase_delay:
737
+ summary['timestamps'].append(('setup_phase_delay', time.time()))
738
+ sdl.set_session_data(S, 'timestamps', summary['timestamps'])
739
+ success, summary['setup_phase_delay'] = setup_phase_delay(
740
+ S, cfg, bands, update_cfg=update_cfg)
741
+ if not success:
742
+ S.log("UXM Setup failed on setup phase delay step")
743
+ return False, summary
744
+
745
+ #############################################################
746
+ # 5. Setup Tune
747
+ #############################################################
748
+ summary['timestamps'].append(('setup_tune', time.time()))
749
+ sdl.set_session_data(S, 'timestamps', summary['timestamps'])
750
+ success, summary['setup_tune'] = setup_tune(
751
+ S, cfg, bands, show_plots=show_plots, update_cfg=update_cfg,)
752
+ if not success:
753
+ S.log("UXM Setup failed on setup tune step")
754
+ return False, summary
755
+
756
+ #############################################################
757
+ # 6. Setup Tracking
758
+ #############################################################
759
+ summary['timestamps'].append(('setup_tracking', time.time()))
760
+ sdl.set_session_data(S, 'timestamps', summary['timestamps'])
761
+
762
+ # Ensure feedback is enabled
763
+ for b in bands:
764
+ S.set_feedback_enable(b, 1)
765
+
766
+ tracking_res = tracking.setup_tracking_params(
767
+ S, cfg, bands, show_plots=show_plots, update_cfg=update_cfg
768
+ )
769
+ summary['tracking_res'] = tracking_res
770
+
771
+ #############################################################
772
+ # 7. Noise
773
+ #############################################################
774
+ summary['timestamps'].append(('noise', time.time()))
775
+ sdl.set_session_data(S, 'timestamps', summary['timestamps'])
776
+ _, summary['noise'] = sdl.noise.take_noise(
777
+ S, cfg, 30, show_plot=show_plots, save_plot=True
778
+ )
779
+
780
+ summary['timestamps'].append(('end', time.time()))
781
+ sdl.set_session_data(S, 'timestamps', summary['timestamps'])
782
+
783
+ return True, summary