From 6d277d4e71b6ce7bbb90b4f34b78ab846d7b245d Mon Sep 17 00:00:00 2001 From: Alexander Steppke Date: Tue, 25 Nov 2025 15:09:12 +0100 Subject: [PATCH] after p22760 before cleanup --- beamline/components.py | 3 +- beamline/cristallina_mono.py | 105 ++++++++++++ beamline/photon_energy.py | 30 ++-- channels/bs_channels.py | 23 ++- channels/pv_channels.py | 33 +++- cristallina.py | 80 ++++----- crq_exp/dilsc-frappy.py | 182 ++++++++++++++++++++ crq_exp/dilsc.py | 213 ++++++------------------ crq_exp/synchronization.py | 94 ++++++++++- devices/LakeShore372/curves/X165053.340 | 187 +++++++++++++++++++++ devices/LakeShore372/readme.md | 2 +- p22760.py | 43 +++++ pgroups.py | 11 +- stand/time.py | 2 +- 14 files changed, 779 insertions(+), 229 deletions(-) create mode 100755 beamline/cristallina_mono.py create mode 100755 crq_exp/dilsc-frappy.py create mode 100644 devices/LakeShore372/curves/X165053.340 create mode 100644 p22760.py diff --git a/beamline/components.py b/beamline/components.py index e96157d..40e0150 100755 --- a/beamline/components.py +++ b/beamline/components.py @@ -3,7 +3,8 @@ from loguru import logger from slic.devices.xoptics.aramis_attenuator import Attenuator from slic.devices.xoptics.pulsepicker import PulsePicker from slic.devices.xdiagnostics.intensitymonitor import IntensityMonitorPBPS -from .Cristallina_mono import CristallinaMono +#from .Cristallina_mono import CristallinaMono +from .cristallina_mono import CristallinaMono from .alignment_laser import AlignmentLaser from slic.devices.xoptics.kb import KBHor, KBVer diff --git a/beamline/cristallina_mono.py b/beamline/cristallina_mono.py new file mode 100755 index 0000000..3c33170 --- /dev/null +++ b/beamline/cristallina_mono.py @@ -0,0 +1,105 @@ +from types import SimpleNamespace +from time import sleep +import numpy as np + +from slic.core.adjustable import Adjustable, PVAdjustable, PVEnumAdjustable +from slic.core.adjustable.pvchangemon import PVChangeMonitor +from slic.core.device import Device +from slic.utils.hastyepics import get_pv as PV +from slic.devices.general.motor import Motor +from slic.devices.cameras import CameraCA + +class CristallinaMono(Device): + """Cristallina mono SAROP31-ODCC110""" + def __init__(self, ID, name="Cristallina double-channel-cut monochromator", **kwargs): + super().__init__(ID, name=name, **kwargs) + + self.TX = Motor(ID + ":MOT_TX1") + self.bragg1 = Motor(ID + ":MOT_RX1") + self.bragg2 = Motor(ID + ":MOT_RX2") + self.energy = Motor(ID + ":MOT_ENY") + self.angle2_offset = Motor(ID + ":MOT_OFS") + self.screen = CristallinaMonoScreen("SAROP31-PSCR110", name='SAROP31-PSCR110') + +class CristallinaMonoScreen(Device): + + def __init__(self, ID, name="Profile Monitor", **kwargs): + super().__init__(ID, name=name, **kwargs) + + self.cam = CameraCA(ID) + + self.target = PVEnumAdjustable(ID + ":SCR_SP", name="target") + self.target_pos = Motor(ID + ":MOT_TY1", name="target position") + self.target_in_position = PVEnumAdjustable(ID + ":IN_POS",name="target in valid position") + + def move_mono(self): + return self.target.set_target_value(1) + + def move_pink(self): + return self.target.set_target_value(2) + + def move_out(self): + return self.target.set_target_value(0) + + + + +# class DoubleCrystalMonoEnergy(Adjustable): + +# def __init__(self, ID, name=None): +# self.wait_time = 0.1 + +# pvname_setvalue = "SAROP21-ARAMIS:ENERGY_SP" +# pvname_readback = "SAROP21-ARAMIS:ENERGY" +# pvname_moving = "SAROP21-ODCM098:MOVING" +# pvname_stop = "SAROP21-ODCM098:STOP.PROC" + +# pv_setvalue = PV(pvname_setvalue) +# pv_readback = PV(pvname_readback) +# pv_moving = PV(pvname_moving) +# pv_stop = PV(pvname_stop) + +# units = pv_readback.units +# super().__init__(ID, name=name, units=units) + +# self.pvnames = SimpleNamespace( +# setvalue = pvname_setvalue, +# readback = pvname_readback, +# moving = pvname_moving, +# stop = pvname_stop +# ) + +# self.pvs = SimpleNamespace( +# setvalue = pv_setvalue, +# readback = pv_readback, +# moving = pv_moving, +# stop = pv_stop +# ) + + +# def get_current_value(self): +# return self.pvs.readback.get() + +# def set_current_value(self, value): +# self.pvs.setvalue.put(value) +# sleep(3) + +# def set_target_value(self, value): +# self.set_current_value(value) +# # while abs(self.wait_for_valid_value() - value) > accuracy: +# while self.is_moving(): +# sleep(self.wait_time) + + +# def wait_for_valid_value(self): +# val = np.nan +# while not np.isfinite(val): +# val = self.get_current_value() +# return val + +# def is_moving(self): +# moving = self.pvs.moving.get() +# return bool(moving) + +# def stop(self): +# self.pvs.stop.put(1) diff --git a/beamline/photon_energy.py b/beamline/photon_energy.py index 062e598..8a6021a 100755 --- a/beamline/photon_energy.py +++ b/beamline/photon_energy.py @@ -26,17 +26,16 @@ N_UNDS = list(range(3, 15 + 1)) ### SETTINGS #### -PSSS_MOVE = True -DCCM_MOVE = True +PSSS_MOVE = False +DCCM_MOVE = False TRAJECTORY_FEEDBACK_DISABLE_ENABLE = False POINTING_FEEDFORWARD = False -energy_offset_undulators = 52 # eV -energy_offset_PSSS = 2 # eV +energy_offset_undulators = 43 # eV +energy_offset_PSSS = 0 # eV energy_offset_DCCM = 0 # eV -DCCM_RX2_energy_offset = 1.6 # eV - # eV +DCCM_RX2_angle_offset = 0.0617 # deg ################ @@ -49,8 +48,8 @@ def print_configuration(): print(f"Undulator energy offset = {energy_offset_undulators}") print(f"PSSS energy offset = {energy_offset_PSSS}") print(f"DCCM energy offset = {energy_offset_DCCM}") - print(f" DCCM RX2 energy offset = {DCCM_RX2_energy_offset}") - + # print(f" DCCM RX2 energy offset = {DCCM_RX2_energy_offset}") + print(f" DCCM RX2 angle offset = {DCCM_RX2_angle_offset}") def pointing_slope_X(energy): @@ -97,12 +96,19 @@ def set_DCCM_energy(energy: float): DCCM_energy_PV_name = "SAROP31-ODCC110:MOT_ENY" DCCM_energy_PV = PV(DCCM_energy_PV_name) - DCCM_energy_offset_PV_name = "SAROP31-ODCC110:MOT_OFS" - DCCM_energy_offset_PV = PV(DCCM_energy_offset_PV_name) +# DCCM_energy_offset_PV_name = "SAROP31-ODCC110:MOT_OFS" +# DCCM_energy_offset_PV = PV(DCCM_energy_offset_PV_name) - DCCM_energy_offset_PV.put(DCCM_RX2_energy_offset, wait=True) +# DCCM_energy_offset_PV.put(DCCM_RX2_energy_offset, wait=True) +# DCCM_energy_PV.put(energy, wait=False) +# DCCM_energy_offset_PV.put(DCCM_RX2_energy_offset, wait=False)# + + DCCM_angle_offset_PV_name = "SAROP31-ODCC110:MOT_OFS" + DCCM_angle_offset_PV = PV(DCCM_angle_offset_PV_name) + + DCCM_angle_offset_PV.put(DCCM_RX2_angle_offset, wait=True) DCCM_energy_PV.put(energy, wait=False) - DCCM_energy_offset_PV.put(DCCM_RX2_energy_offset, wait=False) + DCCM_angle_offset_PV.put(DCCM_RX2_angle_offset, wait=False) print(f"Finished adjusting DCCM.") diff --git a/channels/bs_channels.py b/channels/bs_channels.py index e6d151d..6e96729 100755 --- a/channels/bs_channels.py +++ b/channels/bs_channels.py @@ -6,13 +6,13 @@ from slic.core.acquisition.detcfg import DetectorConfig # TODO: JF settings regarding raw conversion, compression, etc. -detectors = [ - "JF16T03V02", # 1.5M from 2025 +#detectors = [ +# "JF16T03V02", # 1.5M from 2025 # "JF16T03V01", # 1.5M from 2022 # "JF17T16V01", # 8M - "JF20T01V01", # IO +# "JF20T01V01", # IO # "JF05T01V01", # 0.5M stripsel borrowed from Bernina, now registered in esc network -] +#] # ALLOWED_PARAMS = dict( # adc_to_energy = bool, @@ -28,7 +28,7 @@ detectors = [ # save_dap_results = bool # ) -detectors = DetectorConfig(detectors) +detectors = DetectorConfig("JF16T03V02","JF20T01V01") # detectors_with_config["JF16T03V01"]['save_dap_results'] = True # JF 1.5M default settings @@ -38,6 +38,7 @@ detectors["JF16T03V02"]['adc_to_energy'] = True detectors["JF16T03V02"]['compression'] = True detectors["JF16T03V02"]['save_dap_results'] = False detectors["JF16T03V02"]['geometry'] = True +#detectors["JF16T03V02"]['disabled_modules'] = [0, 2] # bottom module:0, middle module:1, top module:2 # I0 JF default settings detectors["JF20T01V01"]['remove_raw_files'] = True @@ -47,6 +48,16 @@ detectors["JF20T01V01"]['compression'] = True detectors["JF20T01V01"]['save_dap_results'] = False detectors["JF20T01V01"]['geometry'] = False +# JF 8M default settings +# detectors["JF17T16V01"]['remove_raw_files'] = True # We switched off quite a few modules, let's not keep all the raw data. +# detectors["JF17T16V01"]['factor'] = 0.1 # Some useful compromise to save space. +# detectors["JF17T16V01"]['adc_to_energy'] = True # We switched off quite a few modules, let's not keep all the raw data. +# detectors["JF17T16V01"]['compression'] = True +# detectors["JF17T16V01"]['save_dap_results'] = False +# detectors["JF17T16V01"]['geometry'] = True +#detectors["JF17T16V01"]['disabled_modules'] = [0, 1, 2, 3, 9, 10, 11, 12, 13, 14, 15] + + detectors_I0_only = DetectorConfig(["JF20T01V01"]) # I0 JF settings with finer resolution @@ -555,7 +566,7 @@ bs_channels = ( + channels_PSCD153 + channels_EVR + channels_digitizer - # + channels_Xeye + #+ channels_Xeye + diffractometer_1_bs + diffractometer_2_bs # + camera_channels diff --git a/channels/pv_channels.py b/channels/pv_channels.py index 13413c6..183b9bb 100755 --- a/channels/pv_channels.py +++ b/channels/pv_channels.py @@ -300,7 +300,6 @@ pvs_OOMV092_bernina = [ pvs_PPRM094_bernina = [ "SAROP21-PPRM113:MOTOR_PROBE", - #"SAROP21-PPRM113:FPICTURE", ] @@ -863,6 +862,7 @@ pvs_diffractometer_extras = [ ############################### # DilSc +# LakeShore ID_DilSc_LakeShore = "SARES31-DIL-LS1" pvs_DilSc_Lakeshore = [ ID_DilSc_LakeShore + ":A_KELVIN", @@ -873,14 +873,33 @@ pvs_DilSc_Lakeshore = [ ID_DilSc_LakeShore + ":7_RES", ID_DilSc_LakeShore + ":8_RES", + ID_DilSc_LakeShore + ":1_RES", + ID_DilSc_LakeShore + ":2_RES", + ID_DilSc_LakeShore + ":3_RES", + ID_DilSc_LakeShore + ":A_EX_STRING.SVAL", ID_DilSc_LakeShore + ":7_EX_STRING.SVAL", ID_DilSc_LakeShore + ":8_EX_STRING.SVAL", + ID_DilSc_LakeShore + ":1_EX_STRING.SVAL", + ID_DilSc_LakeShore + ":2_EX_STRING.SVAL", + ID_DilSc_LakeShore + ":3_EX_STRING.SVAL", + ID_DilSc_LakeShore + ":H0_SETP_GET", ID_DilSc_LakeShore + ":H0_PWR_GET", ] +# Magnet Mercury iPS +ID_DilSc_MagnetPSU = "SARES31-MAG-IPS1" + +pvs_DilSc_MagnetPSU = [] +for axis in ['X','Y','Z']: + ID_with_axis = f'{ID_DilSc_MagnetPSU}'+'-'+f'{axis}' + pvs_DilSc_MagnetPSU.append( ID_with_axis + ':SIG_FLD' ) + pvs_DilSc_MagnetPSU.append( ID_with_axis + ':SIG_FSET' ) + pvs_DilSc_MagnetPSU.append( ID_with_axis + ':SIG_RFST' ) + pvs_DilSc_MagnetPSU.append( ID_with_axis + ':ACTN' ) + ############################### # PuMa Aerotech motor controller @@ -970,11 +989,13 @@ pv_channels = ( + pvs_standa # + pvs_newport_300 # + pvs_smaract_xyz - # + pvs_diffractometer_1 - + pvs_diffractometer_2 - + pvs_diffractometer_extras - # + pvs_DilSc_Lakeshore - + pvs_PuMa_Aerotech + + pvs_diffractometer_1 + # + pvs_diffractometer_2 + # these do not work properly at the moment: + # + pvs_diffractometer_extras + + pvs_DilSc_Lakeshore + + pvs_DilSc_MagnetPSU + # + pvs_PuMa_Aerotech + pvs_huber_z + pvs_JJ_slits # + pvs_attocube diff --git a/cristallina.py b/cristallina.py index 6e1fc21..87d09e0 100755 --- a/cristallina.py +++ b/cristallina.py @@ -38,7 +38,8 @@ def setup_general_logging(): def setup_logging_pgroup(pgroup, level="INFO"): try: logger.add( - f"/sf/cristallina/data/{pgroup}/scratch/slic.log", +# f"/sf/cristallina/data/{pgroup}/scratch/slic.log", + f"/sf/cristallina/data/{pgroup}/res/slic.log", format="{time:YYYY-MM-DD at HH:mm:ss} | {level} | {message}", level=level, rotation="1 week", @@ -135,11 +136,10 @@ from beamline import photon_energy cr_photon_energy = photon_energy.PhotonEnergy() # added limit to photon energy setpoint -cr_photon_energy.set_limits(5720-100, 5720+100) +cr_photon_energy.set_limits(5000, 13000) -logger.info(f"Photon energy offsets: PSSS {photon_energy.energy_offset_PSSS} eV , DCCM {photon_energy.energy_offset_DCCM} eV, undulator {photon_energy.energy_offset_undulators} eV.") - +logger.info(f"Photon energy offsets: PSSS {photon_energy.energy_offset_PSSS} eV, DCCM {photon_energy.energy_offset_DCCM} eV, undulator {photon_energy.energy_offset_undulators} eV.") @@ -167,19 +167,19 @@ dm1 = Diffractometer("SARES31-GPS") dm2 = Diffractometer("SARES32-GPS") # Set according to which diffractometer is being used -diffractometer = dm2 +diffractometer = dm1 # Dilution fridge -#from crq_exp.dilsc import Dilution +from crq_exp.dilsc import Dilution try: - dilution = Dilution() + dilution = Dilution('SARES31-DIL-LS1','SARES31-MAG-IPS1') except Exception as e: logger.warning(f"Error: Could not connect to dilution fridge. {e}") dilution = None -# MX adajustables +# MX adjustables # import mx.mx_adjustables # Temporary quick hack thermometer addition for stand @@ -196,8 +196,8 @@ downstream_transmission = PVAdjustable("SARFE10-OATT053:UsrRec.TC1") -from crq_exp.puma import Puma -puma = Puma() +# from crq_exp.puma import Puma +# puma = Puma() ################# Stand setup ################## @@ -225,25 +225,25 @@ adjs_for_spreadsheet = { "TRYBASE": diffractometer.try_base, "THETA": diffractometer.theta, "TWOTHETA": diffractometer.twotheta, - "Sample_X": puma.sample_x, - "Sample_Y": puma.sample_y, - "Sample_Z": puma.sample_z, - "Sample_R": puma.sample_r, - "Magnet_X": puma.magnet_x, - "Magnet_Y": puma.magnet_y, - "Magnet_Z": puma.magnet_z, + # PuMa positions if available: + # "Sample_X": puma.sample_x, + # "Sample_Y": puma.sample_y, + # " Sample_Z": puma.sample_z, + # "Sample_R": puma.sample_r, + # "Magnet_X": puma.magnet_x, + # "Magnet_Y": puma.magnet_y, + # "Magnet_Z": puma.magnet_z, } -dilution = None + if dilution is not None: adjs_dilsc = { - #"Magnet_X": dilution.x, - #"Magnet_Y": dilution.y, - #"Magnet_Z": dilution.z, - "DilSc_T_chip": T_chip, - "DilSc_T_plato": T_plato, - #"DilSc_T_chip": dilution.T_chip, - "DilSc_T_pucksensor": T_reg, + "Magnet_X": dilution.x, + "Magnet_Y": dilution.y, + "Magnet_Z": dilution.z, + "DilSc_T_reg": dilution.T_CHA, + "DilSc_T_CH7": dilution.T_CH7, + "DilSc_T_CH8": dilution.T_CH8, } adjs_for_spreadsheet.update(adjs_dilsc) @@ -260,6 +260,7 @@ spreadsheet = Spreadsheet( port=9090, ) + try: from stand.client import Client stand_host = "saresc-vcons-02.psi.ch" @@ -332,7 +333,7 @@ DAQS = multiple_daqs.generate_DAQS(instrument, pgroup, bs_channels, pv_channels, # required fraction defines amount of data recorded to save the step and move on to the next one check_intensity_gas_monitor = PVCondition( "SARFE10-PBPG050:PHOTON-ENERGY-PER-PULSE-US", - vmin=400, + vmin=500, vmax=2000, wait_time=0.5, required_fraction=0.8, @@ -363,20 +364,21 @@ temperature_setpoint=PVAdjustable('SARES31-DIL-LS1:H0_SETP_SET') # d fixed to 3.0 mm, x_0 = 4.574 mm and theta_0 = -67.853 deg import numpy as np -d = 3 -theta_0 = -67.853 -x_0 = 4.574 -thetas = np.linspace(-2, 25) -TRX = d * np.sin(np.deg2rad(thetas + theta_0)) + x_0 -linked_theta = LinkedInterpolated("ThetaLinked", diffractometer.theta, diffractometer.tr_x, thetas, TRX) + +# d = 3 +# theta_0 = -67.853 +# x_0 = 4.574 +# thetas = np.linspace(-2, 25) +# TRX = d * np.sin(np.deg2rad(thetas + theta_0)) + x_0 +# linked_theta = LinkedInterpolated("ThetaLinked", diffractometer.theta, diffractometer.tr_x, thetas, TRX) # Sample 3 -d = 2.5000000 -x_0 = 4.04466204 -theta_0 = -64.1376413 -thetas = np.linspace(-10, 15) -TRX = d * np.sin(np.deg2rad(thetas + theta_0)) + x_0 -linked_theta_s3 = LinkedInterpolated("ThetaLinked", diffractometer.theta, diffractometer.tr_x, thetas, TRX) +# d = 2.5000000 +# x_0 = 4.04466204 +# theta_0 = -64.1376413 +# thetas = np.linspace(-10, 15) +# TRX = d * np.sin(np.deg2rad(thetas + theta_0)) + x_0 +# linked_theta_s3 = LinkedInterpolated("ThetaLinked", diffractometer.theta, diffractometer.tr_x, thetas, TRX) # Beware: double_pixels_action interpolate does not seem to work, results in empty data @@ -415,7 +417,7 @@ daq_1p5M_I0 = SFAcquisition( spreadsheet=spreadsheet) -from tqdm.notebook import tqdm + def scan_with_sync(adjustable, start_pos, end_pos, step_size, n_pulses, filename, acquisitions = [daq], return_to_initial_values=False, condition=check_intensity_gas_monitor, spreadsheet=spreadsheet): """ diff --git a/crq_exp/dilsc-frappy.py b/crq_exp/dilsc-frappy.py new file mode 100755 index 0000000..f3e01f9 --- /dev/null +++ b/crq_exp/dilsc-frappy.py @@ -0,0 +1,182 @@ +""" DilSc prototype + +""" + +from slic.core.adjustable import Adjustable, PVAdjustable + +from slic.core.device import Device, SimpleDevice + +from frappy.client import SecopClient +from frappy import states +from frappy.datatypes import StatusType + + +class Dilution(Device): + def __init__(self, **kwargs): + + self.name = 'DilSc' + ID = self.name + super().__init__(ID, **kwargs) + + self.address = 'dilsc.psi.ch:5000' + self.dilsc = SecopClient(self.address) + self.dilsc.connect() + + self.x = MagnetCoil("X", self.dilsc, 'x', limit_low=-0.6, limit_high=0.6) + self.y = MagnetCoil("Y", self.dilsc, 'y', limit_low=-0.6, limit_high=0.6) + self.z = MagnetCoil("Z", self.dilsc, 'z', limit_low=-5.2, limit_high=5.2) + self.T_plato = Thermometer('T_plato', self.dilsc, limit_low=0, limit_high=300) + self.T_chip = Thermometer('T_chip', self.dilsc, limit_low=0, limit_high=300) + self.T_reg = Thermometer('T_reg', self.dilsc, limit_low=0, limit_high=300) + +class Thermometer(Adjustable): + + def __init__(self, name, dilsc_connection, limit_low=-0.0001, limit_high=0.0001): + + super().__init__(name, limit_low=limit_low, limit_high=limit_high) + self.dilsc = dilsc_connection + + # Heater to regulate channel A on the Galgen LakeShore372. Give value in Ohm. + self.heater_resistance = 82 + # Defines heater ranges and PID values for various control temperature regions. + self.heater_settings = [ + {'range': 'off', 'pid': (800, 1, 0), 'temp_start': -1, 'temp_end': 0.00099}, + {'range': '300uA', 'pid': (800, 1, 0), 'temp_start': 0.001, 'temp_end': 0.037}, + {'range': '1mA', 'pid': (40, 14, 0), 'temp_start': 0.038, 'temp_end': 0.108}, + {'range': '3mA', 'pid': (4, 15, 0), 'temp_start': 0.110, 'temp_end': 0.350}, + {'range': '10mA', 'pid': (2, 12, 0), 'temp_start': 0.352, 'temp_end': 1.100}, + {'range': '30mA', 'pid': (2, 60, 0), 'temp_start': 1.100, 'temp_end': 2.300} + ] + + def _check_connection(func): + def checker(self, *args, **kwargs): + if not self.dilsc.online: + raise ConnectionError(f'No connection to dilsc at {self.address}') + else: + return func(self, *args, **kwargs) + return checker + + @_check_connection + def set_heater_range(self, range_value): + """ Sets the heater range for the control channel and sets the PID parameters for the associated control loop. + TODO: Consider redoing so that it only works for the T_reg channel. + TODO: the pid values are now sent with stringio and not saved as parameters in slic, this should be fixed. + - + """ + heater_range_map = { + 'off': 0, '30uA': 1, '100uA': 2, '300uA': 3, '1mA': 4, '3mA': 5, '10mA': 6, '30mA': 7, '100mA': 8} + if range_value not in heater_range_map: + raise ValueError("Invalid range value. Allowed are: ['off','30uA','100uA','300uA','1mA','3mA','10mA','30mA','100mA']") + enum_value = heater_range_map[range_value] + self.dilsc.setParameter(self.name, 'htrrng', enum_value) + + for setting in self.heater_settings: + if setting['range'] == range_value: + self.set_PID_parameters(*setting['pid']) + break + return + + @_check_connection + def get_current_value(self): + cacheitem = self.dilsc.getParameter(f'{self.name}', 'value', trycache=False) + return cacheitem.value + + + @_check_connection + def set_target_value(self, value, adjust_heater_range=True): + if adjust_heater_range: + range_to_set = None + for setting in self.heater_settings: + if setting['temp_start'] <= value and setting['temp_end'] >= value: + range_to_set = setting['range'] + if range_to_set != None: + self.set_heater_range(range_to_set) + else: + raise ValueError(f"Heater range for the target value {value} could not be set") + self.dilsc.setParameter(f'{self.name}', 'target', value) + + @_check_connection + def get_target_value(self): + cacheitem = self.dilsc.getParameter(f'{self.name}', 'target', trycache=False) + return cacheitem.value + + + @_check_connection + def is_moving(self): + response = self.dilsc.getParameter(f'{self.name}','status', trycache=False) + return response[0][0] > StatusType.PREPARED + + @_check_connection + def get_PID_parameters(self): + """ Returns the current PID parameters associated with the control loop for + this thermometer. + """ + # response = self.dilsc.getParameter(f'{self.name}','ctrlpars', trycache=False) + response = self.dilsc.execCommand('lscio', 'communicate', 'PID?') + return response + + @_check_connection + def set_PID_parameters(self, p, i, d): + """ Sets the PID parameters for the associated control loop. + TODO: + - This still returns a timeout error but sets the correct values. + - The range is limited to less than the Lakeshore range allows, this needs + to be fixed in frappy. + """ + # self.dilsc.setParameter(f'{self.name}', 'ctrlpars', {'p': p, 'i': i, 'd': d}) + command_string = f'PID {0},{p},{i},{d}; PID?' + self.dilsc.execCommand('lscio', 'communicate', command_string) + + +class MagnetCoil(Adjustable): + + def __init__(self, name, dilsc_connection, direction, limit_low=-0.0001, limit_high=0.0001): + + super().__init__(name, limit_low=-0.0001, limit_high=0.0001) + + self.direction = direction.lower() + + if self.direction not in ["x", "y", "z"]: + raise ValueError("Direction must be either x, y or z.") + + self.dilsc = dilsc_connection + + + def _check_connection(func): + def checker(self, *args, **kwargs): + if not self.dilsc.online: + raise ConnectionError(f'No connection to dilsc at {self.address}') + else: + return func(self, *args, **kwargs) + return checker + + @_check_connection + def get_current_value(self): + cacheitem = self.dilsc.getParameter(f'mf{self.direction}', 'value', trycache=False) + return cacheitem.value + + + @_check_connection + def set_target_value(self, value): + self.dilsc.setParameter(f'mf{self.direction}', 'target', value) + + @_check_connection + def is_moving(self): + response = self.dilsc.getParameter(f'mf{self.direction}','status', trycache=False) + return response[0][0] > StatusType.PREPARED + + @_check_connection + def set_ramp_speed(self, direction, value): + """ Sets ramp speed for a given direction (x,y or z) and value in T/min. + """ + if value > 0.5: + raise ValueError('Do not exceed 0.5 T/min unless you want a quench party') + if value <= 0: + raise ValueError('Only positive values are allowed') + if self.direction not in ["x", "y", "z"]: + raise ValueError("Direction must be either x, y or z.") + else: + self.dilsc.setParameter(direction, 'ramp', value) + return + + \ No newline at end of file diff --git a/crq_exp/dilsc.py b/crq_exp/dilsc.py index f3e01f9..268e3ff 100755 --- a/crq_exp/dilsc.py +++ b/crq_exp/dilsc.py @@ -1,182 +1,75 @@ -""" DilSc prototype - +""" DilSc prototype with Edwin's EPICS panels """ -from slic.core.adjustable import Adjustable, PVAdjustable - +from slic.core.adjustable import Adjustable, PVAdjustable, Collection from slic.core.device import Device, SimpleDevice - -from frappy.client import SecopClient -from frappy import states -from frappy.datatypes import StatusType - +import time +import numpy as np class Dilution(Device): - def __init__(self, **kwargs): - + def __init__(self, ID_DilSc_LakeShore, ID_DilSc_MagnetPSU, **kwargs): + + super().__init__(ID_DilSc_LakeShore,ID_DilSc_MagnetPSU, **kwargs) + self.name = 'DilSc' - ID = self.name - super().__init__(ID, **kwargs) - - self.address = 'dilsc.psi.ch:5000' - self.dilsc = SecopClient(self.address) - self.dilsc.connect() + self.ID_DilSc_LakeShore = ID_DilSc_LakeShore - self.x = MagnetCoil("X", self.dilsc, 'x', limit_low=-0.6, limit_high=0.6) - self.y = MagnetCoil("Y", self.dilsc, 'y', limit_low=-0.6, limit_high=0.6) - self.z = MagnetCoil("Z", self.dilsc, 'z', limit_low=-5.2, limit_high=5.2) - self.T_plato = Thermometer('T_plato', self.dilsc, limit_low=0, limit_high=300) - self.T_chip = Thermometer('T_chip', self.dilsc, limit_low=0, limit_high=300) - self.T_reg = Thermometer('T_reg', self.dilsc, limit_low=0, limit_high=300) - -class Thermometer(Adjustable): - - def __init__(self, name, dilsc_connection, limit_low=-0.0001, limit_high=0.0001): + self.x = MagnetCoil(ID_DilSc_MagnetPSU, 'X', limit_low=-0.6, limit_high=0.6) + self.y = MagnetCoil(ID_DilSc_MagnetPSU, 'Y', limit_low=-0.6, limit_high=0.6) + self.z = MagnetCoil(ID_DilSc_MagnetPSU, 'Z', limit_low=-5.2, limit_high=5.2) - super().__init__(name, limit_low=limit_low, limit_high=limit_high) - self.dilsc = dilsc_connection + self.T_CHA = PVAdjustable(ID_DilSc_LakeShore + ":A_KELVIN") + self.T_CH7 = PVAdjustable(ID_DilSc_LakeShore + ":7_KELVIN") + self.T_CH8 = PVAdjustable(ID_DilSc_LakeShore + ":8_KELVIN") - # Heater to regulate channel A on the Galgen LakeShore372. Give value in Ohm. - self.heater_resistance = 82 - # Defines heater ranges and PID values for various control temperature regions. - self.heater_settings = [ - {'range': 'off', 'pid': (800, 1, 0), 'temp_start': -1, 'temp_end': 0.00099}, - {'range': '300uA', 'pid': (800, 1, 0), 'temp_start': 0.001, 'temp_end': 0.037}, - {'range': '1mA', 'pid': (40, 14, 0), 'temp_start': 0.038, 'temp_end': 0.108}, - {'range': '3mA', 'pid': (4, 15, 0), 'temp_start': 0.110, 'temp_end': 0.350}, - {'range': '10mA', 'pid': (2, 12, 0), 'temp_start': 0.352, 'temp_end': 1.100}, - {'range': '30mA', 'pid': (2, 60, 0), 'temp_start': 1.100, 'temp_end': 2.300} - ] - - def _check_connection(func): - def checker(self, *args, **kwargs): - if not self.dilsc.online: - raise ConnectionError(f'No connection to dilsc at {self.address}') - else: - return func(self, *args, **kwargs) - return checker + self.T_reg_setpoint = PVAdjustable(ID_DilSc_LakeShore + ':H0_SETP_SET') - @_check_connection - def set_heater_range(self, range_value): - """ Sets the heater range for the control channel and sets the PID parameters for the associated control loop. - TODO: Consider redoing so that it only works for the T_reg channel. - TODO: the pid values are now sent with stringio and not saved as parameters in slic, this should be fixed. - - - """ - heater_range_map = { - 'off': 0, '30uA': 1, '100uA': 2, '300uA': 3, '1mA': 4, '3mA': 5, '10mA': 6, '30mA': 7, '100mA': 8} - if range_value not in heater_range_map: - raise ValueError("Invalid range value. Allowed are: ['off','30uA','100uA','300uA','1mA','3mA','10mA','30mA','100mA']") - enum_value = heater_range_map[range_value] - self.dilsc.setParameter(self.name, 'htrrng', enum_value) - - for setting in self.heater_settings: - if setting['range'] == range_value: - self.set_PID_parameters(*setting['pid']) - break - return + self.heater = SimpleDevice('DilSc sample heater', p = PVAdjustable(ID_DilSc_LakeShore + ':H0_PID_P'), + i = PVAdjustable(ID_DilSc_LakeShore + ':H0_PID_I'), + d = PVAdjustable(ID_DilSc_LakeShore + ':H0_PID_D'), + ) - @_check_connection - def get_current_value(self): - cacheitem = self.dilsc.getParameter(f'{self.name}', 'value', trycache=False) - return cacheitem.value - - - @_check_connection - def set_target_value(self, value, adjust_heater_range=True): - if adjust_heater_range: - range_to_set = None - for setting in self.heater_settings: - if setting['temp_start'] <= value and setting['temp_end'] >= value: - range_to_set = setting['range'] - if range_to_set != None: - self.set_heater_range(range_to_set) - else: - raise ValueError(f"Heater range for the target value {value} could not be set") - self.dilsc.setParameter(f'{self.name}', 'target', value) + def set_pids(self,P,I,D): + self.heater.p.set_target_value(P) + self.heater.i.set_target_value(I) + self.heater.d.set_target_value(D) - @_check_connection - def get_target_value(self): - cacheitem = self.dilsc.getParameter(f'{self.name}', 'target', trycache=False) - return cacheitem.value + PVAdjustable(self.ID_DilSc_LakeShore +':H0_01_SEND.PROC').set_target_value(1) - - @_check_connection - def is_moving(self): - response = self.dilsc.getParameter(f'{self.name}','status', trycache=False) - return response[0][0] > StatusType.PREPARED - - @_check_connection - def get_PID_parameters(self): - """ Returns the current PID parameters associated with the control loop for - this thermometer. - """ - # response = self.dilsc.getParameter(f'{self.name}','ctrlpars', trycache=False) - response = self.dilsc.execCommand('lscio', 'communicate', 'PID?') - return response - - @_check_connection - def set_PID_parameters(self, p, i, d): - """ Sets the PID parameters for the associated control loop. - TODO: - - This still returns a timeout error but sets the correct values. - - The range is limited to less than the Lakeshore range allows, this needs - to be fixed in frappy. - """ - # self.dilsc.setParameter(f'{self.name}', 'ctrlpars', {'p': p, 'i': i, 'd': d}) - command_string = f'PID {0},{p},{i},{d}; PID?' - self.dilsc.execCommand('lscio', 'communicate', command_string) - class MagnetCoil(Adjustable): + def __init__(self, ID, direction, limit_low, limit_high): + + super().__init__(ID, limit_low=limit_low, limit_high=limit_high) + + self.direction = direction + self.ID = self.name + '-' + self.direction - def __init__(self, name, dilsc_connection, direction, limit_low=-0.0001, limit_high=0.0001): + if self.direction not in ["X", "Y", "Z"]: + raise ValueError("Direction must be either X, Y or Z.") - super().__init__(name, limit_low=-0.0001, limit_high=0.0001) - - self.direction = direction.lower() + self.target = PVAdjustable(self.ID+':SET_FSET') + self.target_readback = PVAdjustable(self.ID+':SIG_FSET') + self.ramp_rate = PVAdjustable(self.ID+':SIG_RFST') + self.done = PVAdjustable(self.ID+':FSET_FLAG') + self.field_target_tolerance = 1e-3 - if self.direction not in ["x", "y", "z"]: - raise ValueError("Direction must be either x, y or z.") - - self.dilsc = dilsc_connection - - - def _check_connection(func): - def checker(self, *args, **kwargs): - if not self.dilsc.online: - raise ConnectionError(f'No connection to dilsc at {self.address}') - else: - return func(self, *args, **kwargs) - return checker - - @_check_connection - def get_current_value(self): - cacheitem = self.dilsc.getParameter(f'mf{self.direction}', 'value', trycache=False) - return cacheitem.value - - - @_check_connection def set_target_value(self, value): - self.dilsc.setParameter(f'mf{self.direction}', 'target', value) - - @_check_connection - def is_moving(self): - response = self.dilsc.getParameter(f'mf{self.direction}','status', trycache=False) - return response[0][0] > StatusType.PREPARED + self.target.set_target_value(value) + PVAdjustable(self.ID+':ACTN_RTOS.PROC').set_target_value(1) + + def get_current_value(self): + return PVAdjustable(self.ID+':SIG_FLD').get_current_value() - @_check_connection - def set_ramp_speed(self, direction, value): - """ Sets ramp speed for a given direction (x,y or z) and value in T/min. - """ - if value > 0.5: - raise ValueError('Do not exceed 0.5 T/min unless you want a quench party') - if value <= 0: - raise ValueError('Only positive values are allowed') - if self.direction not in ["x", "y", "z"]: - raise ValueError("Direction must be either x, y or z.") - else: - self.dilsc.setParameter(direction, 'ramp', value) - return + def hold(self): + return PVAdjustable(self.ID+':ACTN_HOLD.PROC').set_target_value(1) + + def press_to_setpoint(self): + return PVAdjustable(self.ID+':ACTN_RTOS.PROC').set_target_value(1) + + #TODO: Bodge. There must be a better way.. + def is_moving(self): + time.sleep(0.1) + return abs( self.target.get_current_value() - self.get_current_value() ) > self.field_target_tolerance \ No newline at end of file diff --git a/crq_exp/synchronization.py b/crq_exp/synchronization.py index cd3e7f1..6cb9039 100755 --- a/crq_exp/synchronization.py +++ b/crq_exp/synchronization.py @@ -2,6 +2,13 @@ import requests import numpy as np from loguru import logger +from tqdm import tqdm + +from slic.core.condition import ValueCondition +from slic.utils.npy import nice_arange +from slic.core.scanner.scaninfo import ScanInfo + + def start_sequence(n: int = 100, pulse_phase: float = np.pi/8): @@ -10,4 +17,89 @@ def start_sequence(n: int = 100, pulse_phase: float = np.pi/8): r = requests.get(url, params=parameters) d = r.json() - return d['pids'] \ No newline at end of file + return d['pids'] + + +def happy_condition(): + """ Enjoy the bright side of life. + """ + + get_value = lambda: 1 + check_time = 0.1 + vmin = 0 + vmax = 2 + wait_time = 0.1 + required_fraction = 0 + condition = ValueCondition(get_value, check_time, vmin, vmax, wait_time, required_fraction) + + return condition + + +def scan_with_sync(adjustable, start_pos, end_pos, step_size, n_pulses, filename, acquisitions = [None], return_to_initial_values=False, condition=None, spreadsheet=None, stand_client=None): + """ + Convenience function to make scans when pulse tube synchronisation is used for a given + start position, end position and step size. + + """ + positions = nice_arange(start_pos, end_pos, step_size) + + scan_with_sync_seq(adjustable, positions, n_pulses, filename, acquisitions=acquisitions, return_to_initial_values=return_to_initial_values, condition=condition, spreadsheet=spreadsheet, stand_client=stand_client) + + +def scan_with_sync_seq(adjustable, positions, n_pulses, filename, acquisitions = [None], return_to_initial_values=False, condition=None, spreadsheet=None, stand_client=None): + """ + Convenience function to make scans when pulse tube synchronisation is used for a given + list of positions to drive to. + """ + + scaninfo = ScanInfo(filename, "scan_info", [adjustable], positions) + assert len(acquisitions) == 1, f"Only one acquisition can be given for one run. Now {acquisitions} were given." + + for i, position in enumerate(tqdm(positions)): + # Go to the position and save readback + adjustable.set_target_value(position).wait() + readback = adjustable.get_current_value() + scaninfo.append([position], [readback], None, None) + + if condition is None: + condition = happy_condition() + + successful_acquisition = False + + while not successful_acquisition: + condition.clear_and_start_counting() + + try: + # Measure the step and save the pids + # It's done with try because every now and then the pids are not retrieved and the step should be repeated. + pids = start_sequence(n_pulses) + scan_info_d = scaninfo.to_sfdaq_dict() + successful_acquisition = condition.stop_counting_and_analyze() + except KeyboardInterrupt: + print('Aborting.') + break + except: + successful_acquisition = False + + # Retrieve data + # First step needs to be made separately, because a run number can't be given to sfdaq until the folder is created. + # TODO: is this still true? + if i == 0: + for acquisition in acquisitions: + first_step = acquisition.retrieve(filename, pulseids=pids, scan_info=scan_info_d) + run_number= first_step['run_number'] + else: + for acquisition in acquisitions: + acquisition.retrieve(filename, pulseids=pids, run_number=run_number, scan_info=scan_info_d) + + if return_to_initial_values: + adjustable.set_target_value(positions[0]).wait() + + # Write to stand table + if stand_client is not None: + if spreadsheet is not None: + stand_client.add_row(run=str(run_number), filename=filename, n_pulses=str(n_pulses), sync="True", **spreadsheet.get_adjs_values()) + else: + stand_client.add_row(run=str(run_number), filename=filename, n_pulses=str(n_pulses), sync="True") + + print(f"Scan {run_number} finished") \ No newline at end of file diff --git a/devices/LakeShore372/curves/X165053.340 b/devices/LakeShore372/curves/X165053.340 new file mode 100644 index 0000000..5e9702c --- /dev/null +++ b/devices/LakeShore372/curves/X165053.340 @@ -0,0 +1,187 @@ +Sensor Model: CX-1030-CD-0.3L +Serial Number: X165053 +Data Format: 4 (Log Ohms/Kelvin) +SetPoint Limit: 325.0 (Kelvin) +Temperature coefficient: 1 (Negative) +Number of Breakpoints: 178 + +No. Units Temperature (K) + + 1 1.53734 330.052 + 2 1.54198 325.000 + 3 1.54763 319.000 + 4 1.55292 313.500 + 5 1.55832 308.000 + 6 1.56384 302.500 + 7 1.56948 297.000 + 8 1.57524 291.500 + 9 1.58112 286.000 + 10 1.58714 280.500 + 11 1.59329 275.000 + 12 1.59958 269.500 + 13 1.60602 264.000 + 14 1.61260 258.500 + 15 1.61934 253.000 + 16 1.62623 247.500 + 17 1.63329 242.000 + 18 1.64052 236.500 + 19 1.64724 231.500 + 20 1.65411 226.500 + 21 1.66114 221.500 + 22 1.66833 216.500 + 23 1.67568 211.500 + 24 1.68321 206.500 + 25 1.69091 201.500 + 26 1.69879 196.500 + 27 1.70687 191.500 + 28 1.71515 186.500 + 29 1.72363 181.500 + 30 1.73233 176.500 + 31 1.74125 171.500 + 32 1.75041 166.500 + 33 1.75886 162.000 + 34 1.76752 157.500 + 35 1.77640 153.000 + 36 1.78550 148.500 + 37 1.79485 144.000 + 38 1.80444 139.500 + 39 1.81429 135.000 + 40 1.82443 130.500 + 41 1.83485 126.000 + 42 1.84439 122.000 + 43 1.85418 118.000 + 44 1.86425 114.000 + 45 1.87462 110.000 + 46 1.88530 106.000 + 47 1.89631 102.000 + 48 1.90483 99.000 + 49 1.91207 96.500 + 50 1.91947 94.000 + 51 1.92704 91.500 + 52 1.93478 89.000 + 53 1.94271 86.500 + 54 1.95083 84.000 + 55 1.95915 81.500 + 56 1.96770 79.000 + 57 1.97647 76.500 + 58 1.98549 74.000 + 59 1.99477 71.500 + 60 2.00241 69.500 + 61 2.01023 67.500 + 62 2.01825 65.500 + 63 2.02648 63.500 + 64 2.03493 61.500 + 65 2.04406 59.400 + 66 2.05302 57.400 + 67 2.06225 55.400 + 68 2.07177 53.400 + 69 2.08160 51.400 + 70 2.09073 49.600 + 71 2.10015 47.800 + 72 2.10987 46.000 + 73 2.11991 44.200 + 74 2.13031 42.400 + 75 2.14109 40.600 + 76 2.15164 38.900 + 77 2.16193 37.300 + 78 2.17259 35.700 + 79 2.18366 34.100 + 80 2.19516 32.500 + 81 2.20638 31.000 + 82 2.21806 29.500 + 83 2.22941 28.100 + 84 2.24124 26.700 + 85 2.25360 25.300 + 86 2.26561 24.000 + 87 2.27721 22.800 + 88 2.28934 21.600 + 89 2.30210 20.400 + 90 2.31162 19.550 + 91 2.32031 18.800 + 92 2.32871 18.100 + 93 2.33679 17.450 + 94 2.34517 16.800 + 95 2.35388 16.150 + 96 2.36224 15.550 + 97 2.37092 14.950 + 98 2.37997 14.350 + 99 2.38863 13.800 +100 2.39765 13.250 +101 2.40709 12.700 +102 2.41606 12.200 +103 2.42545 11.700 +104 2.43529 11.200 +105 2.44564 10.700 +106 2.45546 10.250 +107 2.46577 9.800 +108 2.47666 9.350 +109 2.48819 8.900 +110 2.49907 8.500 +111 2.51056 8.100 +112 2.52278 7.700 +113 2.53582 7.300 +114 2.54801 6.950 +115 2.56097 6.600 +116 2.57485 6.250 +117 2.59065 5.880 +118 2.60540 5.560 +119 2.62128 5.240 +120 2.63739 4.940 +121 2.65480 4.640 +122 2.67249 4.360 +123 2.69172 4.080 +124 2.70745 3.870 +125 2.72182 3.690 +126 2.73720 3.510 +127 2.75279 3.340 +128 2.76853 3.180 +129 2.78651 3.010 +130 2.80468 2.850 +131 2.82307 2.700 +132 2.84302 2.550 +133 2.86320 2.410 +134 2.88511 2.270 +135 2.90906 2.130 +136 2.93348 2.000 +137 2.95817 1.880 +138 2.98524 1.760 +139 3.01521 1.640 +140 3.04574 1.530 +141 3.07965 1.420 +142 3.11779 1.310 +143 3.15700 1.210 +144 3.16370 1.195 +145 3.17876 1.160 +146 3.19702 1.120 +147 3.21628 1.080 +148 3.23668 1.040 +149 3.25832 1.000 +150 3.28136 0.960 +151 3.30594 0.920 +152 3.32890 0.885 +153 3.35329 0.850 +154 3.37933 0.815 +155 3.40721 0.780 +156 3.43716 0.745 +157 3.46945 0.710 +158 3.49934 0.680 +159 3.53133 0.650 +160 3.56588 0.620 +161 3.60325 0.590 +162 3.64391 0.560 +163 3.68840 0.530 +164 3.72895 0.505 +165 3.76208 0.486 +166 3.79551 0.468 +167 3.83118 0.450 +168 3.86939 0.432 +169 3.91040 0.414 +170 3.94954 0.398 +171 3.99133 0.382 +172 4.03619 0.366 +173 4.08440 0.350 +174 4.13658 0.334 +175 4.18610 0.320 +176 4.23214 0.308 +177 4.26529 0.300 +178 4.45964 0.260 diff --git a/devices/LakeShore372/readme.md b/devices/LakeShore372/readme.md index 84e1c05..4ba0f06 100644 --- a/devices/LakeShore372/readme.md +++ b/devices/LakeShore372/readme.md @@ -1,7 +1,7 @@ # How to upload .340 curve to a LakeShore 372 in EPICS If something unclear, contact the creator Edwin -active the enviromnet with pyepics in it. Then in this folder run 'python UploadTempCurve.py _340-file_ _LakeShoreBaseName:CVCurveNumber_' +active the enviromnet with pyepics in it (e.g. slic). Then in this folder run 'python UploadTempCurve.py _340-file_ _LakeShoreBaseName:CVCurveNumber_' e.g. python UploadTempCurve.py U08910.340 SARES31-DIL-LS1:CV1 the code gives an error, but works. diff --git a/p22760.py b/p22760.py new file mode 100644 index 0000000..c078933 --- /dev/null +++ b/p22760.py @@ -0,0 +1,43 @@ +"""Module for the p22760 beamtime experiment at Cristallina. For slic purposes only. +""" + +import scipy +import numpy as np + + + +def get_theta_prediction(B_field): + """Get predicted theta angle from B field using linear interpolation. + + Args: + B_field (float): Magnetic field value in Tesla. + + Returns: + tuple: Predicted theta angles (left, right) in degrees. + """ + + B = np.array([0. , 0. , 0.05 , 0.1 , 0.15 , 0.2 , 0.25 , 0.3 , + 0.35 , 0.4 , 0.5 , 0.525 , 0.53 , 0.55 , 0.555 , 0.56 , + 0.565 , 0.5675, 0.57 , 0.5725, 0.575 , 0.6 ]) + + left = np.array([-16.72402197, -16.72592595, -16.72266943, -16.71780642, + -16.70710748, -16.68879129, -16.67129814, -16.64430405, + -16.61258093, -16.57162955, -16.43958512, -16.36767873, + -16.35235521, -16.20000072, -16.14585335, -16.14986136, + -16.14953497, -16.15033841, -16.14907281, -16.14883662, + -16.14936827, -16.14544489]) + + right = np.array([-15.82641889, -15.82810495, -15.82810142, -15.83362565, + -15.84191596, -15.85030174, -15.86875095, -15.88716295, + -15.91257856, -15.94358668, -16.0436225 , -16.07455302, + -16.08374928, -16.13199655, -16.14585335, -16.14986136, + -16.14953497, -16.15033841, -16.14907281, -16.14883662, + -16.14936827, -16.14544489]) + + interpolation_right = scipy.interpolate.interp1d(B, right, kind='linear') + interpolation_left = scipy.interpolate.interp1d(B, left, kind='linear') + + theta_right = interpolation_right(B_field) + theta_left = interpolation_left(B_field) + + return theta_left, theta_right \ No newline at end of file diff --git a/pgroups.py b/pgroups.py index c3e48d1..9c34d2d 100755 --- a/pgroups.py +++ b/pgroups.py @@ -1,7 +1,8 @@ pgroup_scratch = "p19150" # Scratch -# pgroup = "p19150" # Scratch + +pgroup = "p19150" # Scratch # pgroup = "p19152" # Scratch # pgroup = "p19739" # commissioning March 2022 -- July 2022 @@ -54,4 +55,10 @@ pgroup_scratch = "p19150" # Scratch # pgroup = "p22581" # Cr-Q commissioning Puma June 2025 -pgroup = "p22761" # Cr-Q commissioning Puma June 2025 +# pgroup = "p22761" # Cr-Q commissioning Puma Sep 2025 + +# pgroup = "p23016" # Cr-Q user experiment Vonka Nov 2025 on /sf/maloja/data + +# pgroup = "p22760" # Cr-Q user experiment Vonka Nov 2025 + +# pgroup = "p22569" # Cr-Q user experiment Bianchi Dec 2025 diff --git a/stand/time.py b/stand/time.py index 86fc76b..519a34b 100755 --- a/stand/time.py +++ b/stand/time.py @@ -5,7 +5,7 @@ class Time(Adjustable): """ Adjustable only for spreadsheet, no other functionality """ def __init__(self): - super().__init__(self, "") + super().__init__(self, "Time") def get_current_value(self): return datetime.datetime.now().replace(microsecond=0).isoformat()