From fbc010b9f31b916d04d7a5ff4d21843c347a1a49 Mon Sep 17 00:00:00 2001 From: Tristan Pinsonneault-Marotte Date: Wed, 5 Nov 2025 12:45:04 -0800 Subject: [PATCH 1/9] Remove references to epics --- sodetlib/__init__.py | 1 - sodetlib/det_config.py | 11 +++-------- sodetlib/util.py | 17 ++++++++--------- 3 files changed, 11 insertions(+), 18 deletions(-) diff --git a/sodetlib/__init__.py b/sodetlib/__init__.py index a6ab53ec..9a5821a1 100644 --- a/sodetlib/__init__.py +++ b/sodetlib/__init__.py @@ -5,7 +5,6 @@ import os from functools import wraps try: - import epics from pysmurf.client.util.pub import set_action except Exception as e: # Just return base function regularly if can't import set_action diff --git a/sodetlib/det_config.py b/sodetlib/det_config.py index f3effc5f..2d8472f3 100644 --- a/sodetlib/det_config.py +++ b/sodetlib/det_config.py @@ -625,22 +625,19 @@ def dump_configs(self, output_dir=None, clobber=False, dump_rogue_tree=False): return outfiles - def get_smurf_control(self, offline=False, epics_root=None, + def get_smurf_control(self, offline=False, smurfpub_id=None, make_logfile=False, setup=False, dump_configs=None, config_dir=None, apply_dev_configs=False, load_device_tune=True, **pysmurf_kwargs): """ Creates pysmurf instance based off of configuration parameters. - If not specified as keyword arguments ``epics_root`` and ``smurf_pub`` + If not specified as keyword arguments ``smurf_pub`` will be created based on the slot and crate id's. Args: offline (bool): Whether to start pysmurf in offline mode. Defaults to False - epics_root (str, optional): - Pysmurf epics root. If none, it will be set to - ``smurf_server_s``. smurfpub_id (str, optional): Pysmurf publisher ID. If None, will default to crate_slot. @@ -659,8 +656,6 @@ def get_smurf_control(self, offline=False, epics_root=None, import pysmurf.client slot_cfg = self.sys['slots'][f'SLOT[{self.slot}]'] - if epics_root is None: - epics_root = f'smurf_server_s{self.slot}' if smurfpub_id is None: smurfpub_id = self.stream_id if dump_configs is None: @@ -673,7 +668,7 @@ def get_smurf_control(self, offline=False, epics_root=None, S = pysmurf.client.SmurfControl(offline=True) else: S = pysmurf.client.SmurfControl( - epics_root=epics_root, cfg_file=self.pysmurf_file, setup=setup, + cfg_file=self.pysmurf_file, setup=setup, make_logfile=make_logfile, data_path_id=smurfpub_id, **pysmurf_kwargs) self.S = S diff --git a/sodetlib/util.py b/sodetlib/util.py index f960f7c9..45ef6adf 100644 --- a/sodetlib/util.py +++ b/sodetlib/util.py @@ -15,7 +15,6 @@ if not os.environ.get('NO_PYSMURF', False): try: - import epics import pysmurf from pysmurf.client.command.cryo_card import cmd_make except Exception: @@ -208,8 +207,8 @@ def get_metadata(S, cfg): 'iv_file': cfg.dev.exp.get('iv_file'), 'v_bias': S.get_tes_bias_bipolar_array(), 'pysmurf_client_version': pysmurf.__version__, - 'rogue_version': S._caget(f'{S.epics_root}:AMCc:RogueVersion'), - 'smurf_core_version': S._caget(f'{S.epics_root}:AMCc:SmurfApplication:SmurfVersion'), + 'rogue_version': S._caget('AMCc.RogueVersion'), + 'smurf_core_version': S._caget('AMCc.SmurfApplication.SmurfVersion'), 'sodetlib_version': sodetlib.__version__, 'fpga_git_hash': S.get_fpga_git_hash_short(), 'cryocard_fw_version': S.C.get_fw_version(), @@ -541,7 +540,7 @@ def get_r2(sig, sig_hat): class _Register: def __init__(self, S, addr): self.S = S - self.addr = S.epics_root + ":" + addr + self.addr = addr def get(self, **kw): return self.S._caget(self.addr, **kw) @@ -555,11 +554,11 @@ class Registers: they are not in the standard rogue tree, or settable by existing pysmurf get/set functions """ - _root = 'AMCc:' - _processor = _root + "SmurfProcessor:" - _sostream = _processor + "SOStream:" - _sofilewriter = _sostream + 'SOFileWriter:' - _source_root = _root + 'StreamDataSource:' + _root = 'AMCc.' + _processor = _root + "SmurfProcessor." + _sostream = _processor + "SOStream." + _sofilewriter = _sostream + 'SOFileWriter.' + _source_root = _root + 'StreamDataSource.' _registers = { 'pysmurf_action': _sostream + 'pysmurf_action', From e831e183f185bc1178ff0520b284dba569bb4027 Mon Sep 17 00:00:00 2001 From: Tristan Pinsonneault-Marotte Date: Wed, 5 Nov 2025 12:46:49 -0800 Subject: [PATCH 2/9] fix(util.set_current_mode): Fix up cryo card access bypassing pysmurf. --- sodetlib/util.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/sodetlib/util.py b/sodetlib/util.py index 45ef6adf..c797e7e0 100644 --- a/sodetlib/util.py +++ b/sodetlib/util.py @@ -672,25 +672,19 @@ def set_current_mode(S, bgs, mode, const_current=True): nbits = S._rtm_slow_dac_nbits dac_data = np.clip(dac_data, -2**(nbits-1), 2**(nbits-1)-1) - dac_data_reg = S.rtm_spi_max_root + S._rtm_slow_dac_data_array_reg - - - if isinstance(S.C.writepv, str): - cryocard_writepv = S.C.writepv - else: - cryocard_writepv = S.C.writepv.pvname + dac_data_reg = S.rtm_spi_max_root + S._rtm_slow_dac_data_reg # It takes longer for DC voltages to settle than it does to toggle the # high-current relay, so we can set them at the same time when switchign # to hcm, but when switching to lcm we need a sleep statement to prevent # dets from latching. if mode: - epics.caput_many([cryocard_writepv, dac_data_reg], [relay_data, dac_data], - wait=True) + S._caput(dac_data_reg, dac_data, wait_done=False) + S.C.do_write(S.C.relay_address, new_relay) else: S._caput(dac_data_reg, dac_data) time.sleep(0.04) - S._caput(cryocard_writepv, relay_data) + S.C.do_write(S.C.relay_address, new_relay) time.sleep(0.1) # Just to be safe From 722514c20ddcf171c2528cb039a6f04da1131cf7 Mon Sep 17 00:00:00 2001 From: Tristan Pinsonneault-Marotte Date: Wed, 5 Nov 2025 16:24:57 -0800 Subject: [PATCH 3/9] fix(get_smurf_control): Added server_port argument. --- sodetlib/det_config.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/sodetlib/det_config.py b/sodetlib/det_config.py index 2d8472f3..34944db4 100644 --- a/sodetlib/det_config.py +++ b/sodetlib/det_config.py @@ -625,7 +625,7 @@ def dump_configs(self, output_dir=None, clobber=False, dump_rogue_tree=False): return outfiles - def get_smurf_control(self, offline=False, + def get_smurf_control(self, offline=False, server_port=None, smurfpub_id=None, make_logfile=False, setup=False, dump_configs=None, config_dir=None, apply_dev_configs=False, load_device_tune=True, @@ -638,6 +638,9 @@ def get_smurf_control(self, offline=False, Args: offline (bool): Whether to start pysmurf in offline mode. Defaults to False + server_port (int, optional): + The port for the SMuRF server to connect to. Will infer from + slot number if not provided. smurfpub_id (str, optional): Pysmurf publisher ID. If None, will default to crate_slot. @@ -660,6 +663,8 @@ def get_smurf_control(self, offline=False, smurfpub_id = self.stream_id if dump_configs is None: dump_configs = self.dump + if server_port is None: + server_port = 9000 + 2 * self.slot # Pysmurf publisher will check this to determine publisher id. os.environ['SMURFPUB_ID'] = smurfpub_id @@ -670,7 +675,8 @@ def get_smurf_control(self, offline=False, S = pysmurf.client.SmurfControl( cfg_file=self.pysmurf_file, setup=setup, make_logfile=make_logfile, data_path_id=smurfpub_id, - **pysmurf_kwargs) + server_port=server_port, **pysmurf_kwargs + ) self.S = S # Lets just stash this in pysmurf... S._sodetlib_cfg = self From fc489a1660c9226f879eeccc02220ea89d4027bc Mon Sep 17 00:00:00 2001 From: Tristan Pinsonneault-Marotte Date: Wed, 5 Nov 2025 17:49:57 -0800 Subject: [PATCH 4/9] fix(jackhammer): Update wait for epics server. --- hammers/jackhammer | 49 ++++++++++++++++++++++++++++------------------ requirements.txt | 1 + 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/hammers/jackhammer b/hammers/jackhammer index 700087f3..6a9a0ea4 100755 --- a/hammers/jackhammer +++ b/hammers/jackhammer @@ -9,6 +9,7 @@ import time import os import threading from typing import List, Literal +import zmq class TermColors: @@ -110,36 +111,47 @@ def util_run(cmd, args=[], name=None, rm=True, **run_kwargs): return subprocess.run(shlex.split(cmd), cwd=cwd, **run_kwargs) -def check_epics_connection(epics_server, retry=False): +def check_server_connection(server_port, retry=False, timeout=1): """ - Checks if we can connect to a specific epics server. + Checks if we can connect to a specific server. Args: - epics_server (string): - epics server to connect to + server_port (int): + ZMQ server to connect to retry (bool): If true, will continuously check until a connection has been established. """ + # setup connection + c = zmq.Context() + + # message to check if server is ready + msg = {'path': 'AMCc.Ready', 'attr': 'get', 'args': [], 'kwargs': {}} + def do_ping(): + s = c.socket(zmq.REQ) + s.setsockopt(zmq.RCVTIMEO, timeout * 1000) + s.setsockopt(zmq.LINGER, 0) # discard undelivered messages + s.connect(f"tcp://localhost:{server_port + 1}") + try: + s.send_pyobj(msg) + resp = s.recv_pyobj() + return bool(resp) + except zmq.error.Again: + return False + finally: + s.close() + if retry: - print(f"Waiting for epics connection to {epics_server}", end='', flush=True) + print(f"Waiting for connection to server on port {server_port}", end='', flush=True) while True: - x = util_run( - 'caget', args=[f'{epics_server}:AMCc:enable'], - stdout=subprocess.PIPE, stderr=subprocess.STDOUT - ) - if "True" in x.stdout.decode(): + if do_ping(): break print('.', end='', flush=True) print("\nConnected!") return True else: - x = util_run( - 'caget', args=[f'{epics_server}:AMCc:enable'], - stdout=subprocess.PIPE, stderr=subprocess.STDOUT - ) - return "True" in x.stdout.decode() + return do_ping() def get_running_dockers(get_all=True): @@ -245,13 +257,13 @@ def dump_docker_logs(slots, dump_rogue_tree=False): if dump_rogue_tree: dump_script = '/sodetlib/scripts/dump_rogue_state.py' for slot in slots: - if check_epics_connection(f'smurf_server_s{slot}', retry=False): + if check_server_connection(9000 + 2 * slot, retry=False): out_file = os.path.join(dump_dir, f'rogue_state_s{slot}.yml') cprint(f"Dumping s{slot} state to {out_file}", style=TermColors.HEADER) util_run('python3', args=[dump_script, str(slot), out_file], name=f'rogue_dump_s{slot}') else: - print(f"Could not connect to epics for slot {slot}") + print(f"Could not connect to server for slot {slot}") def run_on_shelf_manager(cmd_str): """ Runs a command on the shelf manager. Takes in the command as a string""" @@ -476,8 +488,7 @@ def hammer_func(args): # Waits for streamer-dockers to start print("Waiting for server dockers to connect. This might take a few minutes...") for slot in slots: - epics_server = f'smurf_server_s{slot}' - check_epics_connection(epics_server, retry=True) + check_server_connection(9000 + 2 * slot, retry=True) if reboot and not args.skip_setup: cprint("Configuring pysmurf", style=TermColors.HEADER) diff --git a/requirements.txt b/requirements.txt index 366b29e7..3371b338 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,3 +11,4 @@ lmfit pyfftw pandas seaborn +zmq From 9daea115193aa61aa06e2c0ae2c2234c22609efb Mon Sep 17 00:00:00 2001 From: Tristan Pinsonneault-Marotte Date: Thu, 20 Nov 2025 15:58:22 -0800 Subject: [PATCH 5/9] fix: Remove references to deprecated GradientDescent parameters. --- sodetlib/det_config.py | 3 --- sodetlib/operations/uxm_relock.py | 7 +------ 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/sodetlib/det_config.py b/sodetlib/det_config.py index 34944db4..d6d87909 100644 --- a/sodetlib/det_config.py +++ b/sodetlib/det_config.py @@ -102,11 +102,8 @@ def odict_rep(dumper, data): # Gradient Descent Parameters "gradientDescentMaxIters": 100, "gradientDescentAverages": 2, - "gradientDescentGain": 0.001, "gradientDescentConvergeHz": 500, "gradientDescentStepHz": 5000, - "gradientDescentMomentum": 1, - "gradientDescentBeta": 0.1, # Fixed tones "fixed_tones": {"enabled": False, "freq_offsets": [], "channels": [], "tone_power": 0}, diff --git a/sodetlib/operations/uxm_relock.py b/sodetlib/operations/uxm_relock.py index 678031bb..396c89b4 100644 --- a/sodetlib/operations/uxm_relock.py +++ b/sodetlib/operations/uxm_relock.py @@ -65,8 +65,7 @@ def reload_tune(S, cfg, bands, setup_notches=False, @sdl.set_action() def run_grad_descent_and_eta_scan( - S, cfg, bands=None, update_tune=False, force_run=False, max_iters=None, - gain=None): + S, cfg, bands=None, update_tune=False, force_run=False, max_iters=None): """ This function runs serial gradient and eta scan for each band. Critically, it pulls in gradient descent tune parameters from the device @@ -108,14 +107,10 @@ def run_grad_descent_and_eta_scan( if max_iters is None: max_iters = bcfg['gradientDescentMaxIters'] - if gain is None: - gain = bcfg['gradientDescentGain'] S.set_gradient_descent_step_hz(b, bcfg['gradientDescentStepHz']) S.set_gradient_descent_max_iters(b, max_iters) - S.set_gradient_descent_gain(b, gain) S.set_gradient_descent_converge_hz(b, bcfg['gradientDescentConvergeHz']) - S.set_gradient_descent_beta(b, bcfg['gradientDescentBeta']) S.log(f"Running grad descent and eta scan on band {b}") From 1a806e0f20c574497439f906167b8ce93f170f3b Mon Sep 17 00:00:00 2001 From: Tristan Pinsonneault-Marotte Date: Mon, 15 Dec 2025 11:10:52 -0800 Subject: [PATCH 6/9] fix: Update server port convention to use factor of 3 --- hammers/jackhammer | 6 +++--- sodetlib/det_config.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/hammers/jackhammer b/hammers/jackhammer index 6a9a0ea4..cb22fbb9 100755 --- a/hammers/jackhammer +++ b/hammers/jackhammer @@ -257,7 +257,7 @@ def dump_docker_logs(slots, dump_rogue_tree=False): if dump_rogue_tree: dump_script = '/sodetlib/scripts/dump_rogue_state.py' for slot in slots: - if check_server_connection(9000 + 2 * slot, retry=False): + if check_server_connection(9000 + 3 * slot, retry=False): out_file = os.path.join(dump_dir, f'rogue_state_s{slot}.yml') cprint(f"Dumping s{slot} state to {out_file}", style=TermColors.HEADER) util_run('python3', args=[dump_script, str(slot), out_file], @@ -488,7 +488,7 @@ def hammer_func(args): # Waits for streamer-dockers to start print("Waiting for server dockers to connect. This might take a few minutes...") for slot in slots: - check_server_connection(9000 + 2 * slot, retry=True) + check_server_connection(9000 + 3 * slot, retry=True) if reboot and not args.skip_setup: cprint("Configuring pysmurf", style=TermColors.HEADER) @@ -577,7 +577,7 @@ def gui_func(args): slot = args.slot else: slot = available_slots[0] - server_port = 9000 + 2*slot + server_port = 9000 + 3*slot sodetlib_root = os.environ.get('SODETLIB_ROOT', '/home/cryo/sodetlib') script_path = os.path.join(sodetlib_root, 'hammers', 'run_gui.sh') diff --git a/sodetlib/det_config.py b/sodetlib/det_config.py index d6d87909..76c38a00 100644 --- a/sodetlib/det_config.py +++ b/sodetlib/det_config.py @@ -661,7 +661,7 @@ def get_smurf_control(self, offline=False, server_port=None, if dump_configs is None: dump_configs = self.dump if server_port is None: - server_port = 9000 + 2 * self.slot + server_port = 9000 + 3 * self.slot # Pysmurf publisher will check this to determine publisher id. os.environ['SMURFPUB_ID'] = smurfpub_id From 8d50baaf6a71bd15c96d8bff00fd59899cb522ff Mon Sep 17 00:00:00 2001 From: Tristan Pinsonneault-Marotte Date: Tue, 6 Jan 2026 11:55:00 -0800 Subject: [PATCH 7/9] feat(setup_amps): Add argument to pass on to optimisation function --- sodetlib/operations/uxm_setup.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/sodetlib/operations/uxm_setup.py b/sodetlib/operations/uxm_setup.py index 4340e4d5..244d8230 100644 --- a/sodetlib/operations/uxm_setup.py +++ b/sodetlib/operations/uxm_setup.py @@ -78,7 +78,7 @@ def find_gate_voltage(S, target_Id, amp_name, vg_min=-2.0, vg_max=0, return False @sdl.set_action() -def setup_amps(S, cfg, update_cfg=True, enable_300K_LNA=True): +def setup_amps(S, cfg, update_cfg=True, enable_300K_LNA=True, opt_args={}): """ Initial setup for 50k and hemt amplifiers. For C04/C05 cryocards, will first check if the drain voltages are set. Then checks if drain @@ -105,6 +105,8 @@ def setup_amps(S, cfg, update_cfg=True, enable_300K_LNA=True): If true, will update the device cfg and save the file. enable_300K_LNA: If true, will turn on the 300K LNAs. + opt_args : dict (optional) + Extra kwargs to pass to the `find_gate_voltage` calls. """ sdl.pub_ocs_log(S, "Starting setup_amps") @@ -153,6 +155,9 @@ def setup_amps(S, cfg, update_cfg=True, enable_300K_LNA=True): if Vd != exp[f"amp_{amp}_drain_volt"]: S.set_amp_drain_voltage(amp, exp[f"amp_{amp}_drain_volt"]) + # extra args for optimisation + if "wait_time" not in opt_args: + opt_args["wait_time"] = exp['amp_step_wait_time'] # Check drain currents / scan gate voltages delta_drain_currents = dict() for amp in amp_list: @@ -161,8 +166,8 @@ def setup_amps(S, cfg, update_cfg=True, enable_300K_LNA=True): S.log(f"{amp} current not within tolerance, scanning for correct gate voltage") S.set_amp_gate_voltage(amp, exp[f'amp_{amp}_init_gate_volt'],override=True) success = find_gate_voltage( - S, exp[f"amp_{amp}_drain_current"], amp, wait_time=exp['amp_step_wait_time'], - id_tolerance=exp[f'amp_{amp}_drain_current_tolerance'] + S, exp[f"amp_{amp}_drain_current"], amp, + id_tolerance=exp[f'amp_{amp}_drain_current_tolerance'], **opt_args ) if not success: sdl.pub_ocs_log(S, f"Failed determining {amp} gate voltage") From 65ae5214db4b6335ed49524a7c9fc1612fcb35b1 Mon Sep 17 00:00:00 2001 From: Tristan Pinsonneault-Marotte Date: Tue, 6 Jan 2026 14:42:17 -0800 Subject: [PATCH 8/9] fix(requirements): Remove conflicts with base dockers --- requirements.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 3371b338..aafd9813 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,6 @@ -jupyterlab tqdm setuptools >= 56.2.0 ipykernel>=5.5.4 -jedi==0.17.1 PyYAML numpy scipy From 763e3ca338997d1c8a18486607278988ae94e95c Mon Sep 17 00:00:00 2001 From: simonscryo Date: Mon, 23 Feb 2026 23:02:07 +0000 Subject: [PATCH 9/9] fix(det_config): str/int typo --- sodetlib/det_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sodetlib/det_config.py b/sodetlib/det_config.py index 76c38a00..f01afa8f 100644 --- a/sodetlib/det_config.py +++ b/sodetlib/det_config.py @@ -661,7 +661,7 @@ def get_smurf_control(self, offline=False, server_port=None, if dump_configs is None: dump_configs = self.dump if server_port is None: - server_port = 9000 + 3 * self.slot + server_port = 9000 + 3 * int(self.slot) # Pysmurf publisher will check this to determine publisher id. os.environ['SMURFPUB_ID'] = smurfpub_id