From 40bb12fe8ab720710b8ede018de1352cacabfebc Mon Sep 17 00:00:00 2001 From: ssoham Date: Mon, 29 Jul 2024 11:13:35 -0700 Subject: [PATCH 1/6] feat: add basic JT PV update --- sc_rf_service/sc_rf_service.py | 50 ++++++++++++++++++++++++++-------- versioneer.py | 4 +-- 2 files changed, 41 insertions(+), 13 deletions(-) diff --git a/sc_rf_service/sc_rf_service.py b/sc_rf_service/sc_rf_service.py index 5a01cd6..baf893f 100644 --- a/sc_rf_service/sc_rf_service.py +++ b/sc_rf_service/sc_rf_service.py @@ -89,14 +89,14 @@ def __init__(self, prefix: str, cm_name: str): async def trigger_setup_script(self): process = await create_subprocess_exec( "python", - "/Users/zacarias/srf/auto_setup/srf_cm_setup_launcher.py", + "/Users/soham/srf_auto_setup/srf_cm_setup_launcher.py", f"-cm={self.cm_name}", ) async def trigger_shutdown_script(self): process = await create_subprocess_exec( "python", - "/Users/zacarias/srf/auto_setup/srf_cm_setup_launcher.py", + "/Users/soham/srf_auto_setup/srf_cm_setup_launcher.py", f"-cm={self.cm_name}", "-off", ) @@ -110,14 +110,14 @@ def __init__(self, prefix: str, linac_idx: int): async def trigger_setup_script(self): process = await create_subprocess_exec( "python", - "/Users/zacarias/srf/auto_setup/srf_linac_setup_launcher.py", + "/Users/soham/srf_auto_setup/srf_linac_setup_launcher.py", f"-cm={self.linac_idx}", ) async def trigger_shutdown_script(self): process = await create_subprocess_exec( "python", - "/Users/zacarias/srf/auto_setup/srf_linac_setup_launcher.py", + "/Users/soham/srf_auto_setup/srf_linac_setup_launcher.py", f"-cm={self.linac_idx}", "-off", ) @@ -130,13 +130,13 @@ def __init__(self, prefix: str): async def trigger_setup_script(self): process = await create_subprocess_exec( "python", - "/Users/zacarias/srf/auto_setup/srf_global_setup_launcher.py", + "/Users/soham/srf_auto_setup/srf_global_setup_launcher.py", ) async def trigger_shutdown_script(self): process = await create_subprocess_exec( "python", - "/Users/zacarias/srf/auto_setup/srf_global_setup_launcher.py", + "/Users/soham/srf_auto_setup/srf_global_setup_launcher.py", "-off", ) @@ -182,7 +182,7 @@ async def status(self, instance, value): async def trigger_setup_script(self): process = await create_subprocess_exec( "python", - "/Users/zacarias/srf/auto_setup/srf_cavity_setup_launcher.py", + "/Users/soham/srf_auto_setup/srf_cavity_setup_launcher.py", f"-cm={self.cm_name}", f"-cav={self.cav_num}", ) @@ -190,7 +190,7 @@ async def trigger_setup_script(self): async def trigger_shutdown_script(self): process = await create_subprocess_exec( "python", - "/Users/zacarias/srf/auto_setup/srf_cavity_setup_launcher.py", + "/Users/soham/srf_auto_setup/srf_cavity_setup_launcher.py", f"-cm={self.cm_name}", f"-cav={self.cav_num}", "-off", @@ -204,6 +204,9 @@ class HeaterPVGroup(PVGroup): manual: PvpropertyBoolEnum = pvproperty(name="MANUAL") sequencer: PvpropertyBoolEnum = pvproperty(name="SEQUENCER") + @readback.putter + async def readback(self, instance, value): + print('HEATER READBACK PUTTER CALLED') class JTPVGroup(PVGroup): readback = pvproperty(name="ORBV", value=30.0) @@ -215,10 +218,33 @@ class JTPVGroup(PVGroup): mode_string: PvpropertyString = pvproperty(name="MODE_STRING", value="AUTO") + def __init__(self, prefix, liquid_group): + super().__init__(prefix) + self.liquid_group: LiquidLevelPVGroup = liquid_group + self.prev_readback: int = 0 + + @readback.putter + async def readback(self, instance, value): + # then we decrease it + # actual process is to decrease readback in steps + + if self.prev_readback < value: + self.prev_readback = self.readback.value + await self.mode_string.write(self.manual.name) + + + while self.liquid_group.downstream.value < 93.0: + curr = self.liquid_group.downstream.value + await self.liquid_group.downstream.write(curr + 0.015) + await sleep(1) + + await self.mode_string.write(self.auto.name) + + class LiquidLevelPVGroup(PVGroup): upstream = pvproperty(name="2601:US:LVL", value=75.0) downstream = pvproperty(name="2301:DS:LVL", value=93.0) - + class CryomodulePVGroup(PVGroup): nrp = pvproperty( @@ -1133,8 +1159,10 @@ def __init__(self): ) self.add_pvs(CavFaultPVGroup(prefix=cav_prefix)) - self.add_pvs(JTPVGroup(prefix=jt_prefix)) - self.add_pvs(LiquidLevelPVGroup(prefix=liquid_level_prefix)) + liquid_level_pv = LiquidLevelPVGroup(prefix=liquid_level_prefix) + self.add_pvs(liquid_level_pv) + self.add_pvs(JTPVGroup(prefix=jt_prefix, liquid_group=liquid_level_pv)) + # Rack PVs are stupidly inconsistent if cav_num in rackA: diff --git a/versioneer.py b/versioneer.py index 64fea1c..3aa5da3 100644 --- a/versioneer.py +++ b/versioneer.py @@ -339,9 +339,9 @@ def get_config_from_root(root): # configparser.NoOptionError (if it lacks "VCS="). See the docstring at # the top of versioneer.py for instructions on writing your setup.cfg . setup_cfg = os.path.join(root, "setup.cfg") - parser = configparser.SafeConfigParser() + parser = configparser.ConfigParser() with open(setup_cfg, "r") as f: - parser.readfp(f) + parser.read_file(f) VCS = parser.get("versioneer", "VCS") # mandatory def get(parser, name): From 5ee900bb5856044a5795b8d80c0d5819431477f2 Mon Sep 17 00:00:00 2001 From: ssoham Date: Thu, 1 Aug 2024 11:21:16 -0700 Subject: [PATCH 2/6] feat: update liquid level through cryomodule --- sc_rf_service/sc_rf_service.py | 171 ++++++++++++++++++++++----------- 1 file changed, 115 insertions(+), 56 deletions(-) diff --git a/sc_rf_service/sc_rf_service.py b/sc_rf_service/sc_rf_service.py index baf893f..a8f990b 100644 --- a/sc_rf_service/sc_rf_service.py +++ b/sc_rf_service/sc_rf_service.py @@ -1,6 +1,6 @@ from asyncio import create_subprocess_exec, get_event_loop, sleep from datetime import datetime -from random import random, randrange, uniform +from random import random, randrange, uniform, randint from typing import List from caproto import ChannelEnum, ChannelFloat, ChannelInteger, ChannelType @@ -19,16 +19,17 @@ pvproperty, run, ) +from lcls_tools.superconducting.sc_cavity import Cavity +from lcls_tools.superconducting.sc_linac import MACHINE from lcls_tools.superconducting.sc_linac_utils import ( ESTIMATED_MICROSTEPS_PER_HZ, L1BHL, LINAC_TUPLES, - PIEZO_HZ_PER_VOLT, + PIEZO_HZ_PER_VOLT, LINAC_CM_DICT, ) from simulacrum import Service - class SeverityProp(pvproperty): def __init__(self, name, value, **cls_kwargs): super().__init__( @@ -196,17 +197,10 @@ async def trigger_shutdown_script(self): "-off", ) +class LiquidLevelPVGroup(PVGroup): + upstream = pvproperty(name="2601:US:LVL", value=75.0) + downstream = pvproperty(name="2301:DS:LVL", value=93.0) -class HeaterPVGroup(PVGroup): - setpoint = pvproperty(name="MANPOS_RQST", value=24.0) - readback = pvproperty(name="ORBV", value=24.0) - mode_string: PvpropertyString = pvproperty(name="MODE_STRING", value="SEQUENCER") - manual: PvpropertyBoolEnum = pvproperty(name="MANUAL") - sequencer: PvpropertyBoolEnum = pvproperty(name="SEQUENCER") - - @readback.putter - async def readback(self, instance, value): - print('HEATER READBACK PUTTER CALLED') class JTPVGroup(PVGroup): readback = pvproperty(name="ORBV", value=30.0) @@ -217,34 +211,68 @@ class JTPVGroup(PVGroup): man_pos = pvproperty(name="MANPOS_RQST", value=40.0) mode_string: PvpropertyString = pvproperty(name="MODE_STRING", value="AUTO") - - def __init__(self, prefix, liquid_group): + def __init__(self, prefix, ll_group: LiquidLevelPVGroup, cm_group): super().__init__(prefix) - self.liquid_group: LiquidLevelPVGroup = liquid_group - self.prev_readback: int = 0 - - @readback.putter - async def readback(self, instance, value): - # then we decrease it - # actual process is to decrease readback in steps - - if self.prev_readback < value: - self.prev_readback = self.readback.value - await self.mode_string.write(self.manual.name) - - - while self.liquid_group.downstream.value < 93.0: - curr = self.liquid_group.downstream.value - await self.liquid_group.downstream.write(curr + 0.015) + + self.ll_group = ll_group + self.cm_group = cm_group + self.stable_value = self.cm_group.total_heat(on_start=True) + + @man_pos.putter + async def man_pos(self, instance, value): + await self.readback.write(value) + self.stable_value = self.cm_group.total_heat() + if value * 1.1 > self.stable_value: + while self.man_pos.value * 1.1 > value: + curr = self.ll_group.downstream.value + await self.ll_group.downstream.write(curr - 0.01) + await sleep(1) + elif value * 0.9 < self.stable_value: + while self.man_pos.value * 0.9 < value: + curr = self.ll_group.downstream.value + await self.ll_group.downstream.write(curr + 0.01) await sleep(1) - - await self.mode_string.write(self.auto.name) - -class LiquidLevelPVGroup(PVGroup): - upstream = pvproperty(name="2601:US:LVL", value=75.0) - downstream = pvproperty(name="2301:DS:LVL", value=93.0) - + +class HeaterPVGroup(PVGroup): + setpoint = pvproperty(name="MANPOS_RQST", value=24.0) + readback = pvproperty(name="ORBV", value=24.0) + mode = pvproperty(name="Mode", value=1) + mode_string: PvpropertyString = pvproperty(name="MODE_STRING", value="SEQUENCER") + manual: PvpropertyBoolEnum = pvproperty(name="MANUAL") + sequencer: PvpropertyBoolEnum = pvproperty(name="SEQUENCER") + + def __init__(self, prefix, jt_group: JTPVGroup, cm_group): + super().__init__(prefix) + self.jt_group = jt_group + self.cm_group = cm_group + + @manual.putter + async def manual(self, instance, value): + if value == 1: + await self.mode.write(0) + await self.mode_string.write("MANUAL") + + @sequencer.putter + async def sequencer(self, instance, value): + if value == 1: + await self.mode.write(1) + await self.mode_string.write("SEQUENCER") + + @setpoint.putter + async def setpoint(self, instance, value): + self.cm_group.heater_value = value + self.jt_group.stable_value = self.cm_group.total_heat(heater_value=value) + if self.jt_group.man_pos.value * 1.1 > self.jt_group.stable_value: + while self.jt_group.man_pos.value * 1.1 > self.jt_group.stable_value: + curr = self.jt_group.ll_group.downstream.value + await self.jt_group.ll_group.downstream.write(curr - 0.01) + await sleep(1) + elif self.jt_group.man_pos.value * 0.9 < self.jt_group.stable_value: + while self.jt_group.man_pos.value * 0.9 < self.jt_group.stable_value: + curr = self.jt_group.ll_group.downstream.value + await self.jt_group.ll_group.downstream.write(curr + 0.01) + await sleep(1) class CryomodulePVGroup(PVGroup): nrp = pvproperty( @@ -254,6 +282,25 @@ class CryomodulePVGroup(PVGroup): # TODO - find this and see what type pv it is on bcs/ops_lcls2_bcs_main.edl bcs = pvproperty(value=0, name="BCSDRVSUM", dtype=ChannelType.DOUBLE) + def __init__(self, prefix, cavities): + + super().__init__(prefix) + self.cavities = cavities + self.heater_value = 24.0 + + def calc_rf_heat_load(self): + rf_heat = 0 + for _, cavity in self.cavities.items(): + rf_heat += (cavity.aact.value * 1e6) ** 2 / (1012 * cavity.q0.value) + return rf_heat + + def total_heat(self, on_start=False, heater_value=None): + if heater_value: + self.heater_value = heater_value + total_heat = self.calc_rf_heat_load() + self.heater_value + if not on_start: + print(f'New JT stable position = Total Heat = {total_heat}') + return total_heat class CryoPVGroup(PVGroup): uhl = SeverityProp(name="LVL", value=0) @@ -745,7 +792,7 @@ class CavityPVGroup(PVGroup): enum_strings=("On resonance", "Cold landing", "Parked", "Other"), ) df_cold: PvpropertyFloat = pvproperty( - value=0.0, name="DF_COLD", dtype=ChannelType.FLOAT + value=randint(-10000, 200000), name="DF_COLD", dtype=ChannelType.FLOAT ) step_temp: PvpropertyFloat = pvproperty( value=35.0, name="STEPTEMP", dtype=ChannelType.FLOAT @@ -828,6 +875,12 @@ class CavityPVGroup(PVGroup): value=0.0, name="SEL_POFF", dtype=ChannelType.FLOAT ) + q0: PvpropertyFloat = pvproperty( + value=randrange(int(2.5e10), int(3.5e10), step=int(0.1e10)), + name="Q0", + dtype=ChannelType.FLOAT + ) + def __init__(self, prefix, isHL: bool): super().__init__(prefix) @@ -1083,6 +1136,7 @@ class MAGNETPVGroup(PVGroup): class CavityService(Service): def __init__(self): super().__init__() + self["PHYS:SYS0:1:SC_CAV_QNCH_RESET_HEARTBEAT"] = ChannelInteger(value=0) self["PHYS:SYS0:1:SC_CAV_FAULT_HEARTBEAT"] = ChannelInteger(value=0) @@ -1093,12 +1147,11 @@ def __init__(self): rackA = range(1, 5) self.add_pvs(PPSPVGroup(prefix="PPS:SYSW:1:")) - self.add_pvs(AutoSetupGlobalPVGroup(prefix="ACCL:SYS0:SC:")) for linac_idx, (linac_name, cm_list) in enumerate(LINAC_TUPLES): linac_prefix = f"ACCL:{linac_name}:1:" - self[f"{linac_prefix}AACTMEANSUM"] = ChannelFloat(value=0.0) + self[f"{linac_prefix}AACTMEANSUM"] = ChannelFloat(value=len(LINAC_CM_DICT[linac_idx]) * 8 * 16.60) self[f"{linac_prefix}ADES_MAX"] = ChannelFloat(value=2800.0) if linac_name == "L1B": cm_list += L1BHL @@ -1110,7 +1163,6 @@ def __init__(self): for cm_name in cm_list: is_hl = cm_name in L1BHL heater_prefix = f"CPIC:CM{cm_name}:0000:EHCV:" - self.add_pvs(HeaterPVGroup(prefix=heater_prefix)) self[f"CRYO:CM{cm_name}:0:CAS_ACCESS"] = ChannelEnum( enum_strings=("Close", "Open"), value=1 @@ -1132,38 +1184,36 @@ def __init__(self): AutoSetupCMPVGroup(prefix=cm_prefix + "00:", cm_name=cm_name) ) + jt_prefix = f"CLIC:CM{cm_name}:3001:PVJT:" + liquid_level_prefix = f"CLL:CM{cm_name}:" + cavities = {} + for cav_num in range(1, 9): cav_prefix = cm_prefix + f"{cav_num}0:" - jt_prefix = f"CLIC:CM{cm_name}:3001:PVJT:" - liquid_level_prefix = f"CLL:CM{cm_name}:" - HOM_prefix = f"CTE:CM{cm_name}:1{cav_num}" - cavityGroup = CavityPVGroup(prefix=cav_prefix, isHL=is_hl) - self.add_pvs(cavityGroup) + cavity_group = CavityPVGroup(prefix=cav_prefix, isHL=is_hl) + self.add_pvs(cavity_group) self.add_pvs( - SSAPVGroup(prefix=cav_prefix + "SSA:", cavityGroup=cavityGroup) + SSAPVGroup(prefix=cav_prefix + "SSA:", cavityGroup=cavity_group) ) + cavities[cav_num] = cavity_group piezo_group = PiezoPVGroup( - prefix=cav_prefix + "PZT:", cavity_group=cavityGroup + prefix=cav_prefix + "PZT:", cavity_group=cavity_group ) + self.add_pvs(piezo_group) self.add_pvs( StepperPVGroup( prefix=cav_prefix + "STEP:", - cavity_group=cavityGroup, + cavity_group=cavity_group, piezo_group=piezo_group, ) ) self.add_pvs(CavFaultPVGroup(prefix=cav_prefix)) - liquid_level_pv = LiquidLevelPVGroup(prefix=liquid_level_prefix) - self.add_pvs(liquid_level_pv) - self.add_pvs(JTPVGroup(prefix=jt_prefix, liquid_group=liquid_level_pv)) - - # Rack PVs are stupidly inconsistent if cav_num in rackA: hwi_prefix = cm_prefix + "00:RACKA:" @@ -1183,7 +1233,16 @@ def __init__(self): self.add_pvs(CryoPVGroup(prefix=cryo_prefix)) self.add_pvs(BeamlineVacuumPVGroup(prefix=cm_prefix + "00:")) self.add_pvs(CouplerVacuumPVGroup(prefix=cm_prefix + "10:")) - self.add_pvs(CryomodulePVGroup(prefix=cm_prefix + "00:")) + + liquid_level_pv = LiquidLevelPVGroup(prefix=liquid_level_prefix) + self.add_pvs(liquid_level_pv) + cryomodule_group = CryomodulePVGroup(prefix=cm_prefix + "00:", cavities=cavities) + self.add_pvs(cryomodule_group) + jtpv_group = JTPVGroup(prefix=jt_prefix, ll_group=liquid_level_pv, cm_group=cryomodule_group) + self.add_pvs(jtpv_group) + self.add_pvs(HeaterPVGroup(prefix=heater_prefix, jt_group=jtpv_group, cm_group=cryomodule_group)) + + def main(): From 001ad1e7a26bedf2667fe648855d43983d719e66 Mon Sep 17 00:00:00 2001 From: ssoham Date: Fri, 2 Aug 2024 12:52:18 -0700 Subject: [PATCH 3/6] refactor: integrate lcls-tools with simulacrum for cryomodule/cavity --- sc_rf_service/sc_rf_refactored_service.py | 121 ++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 sc_rf_service/sc_rf_refactored_service.py diff --git a/sc_rf_service/sc_rf_refactored_service.py b/sc_rf_service/sc_rf_refactored_service.py new file mode 100644 index 0000000..e93d971 --- /dev/null +++ b/sc_rf_service/sc_rf_refactored_service.py @@ -0,0 +1,121 @@ +from asyncio import get_event_loop, sleep +from random import randrange +from typing import Callable, Optional, Dict + +from caproto import ChannelFloat +from caproto.server import ioc_arg_parser, run +from lcls_tools.superconducting.sc_cavity import Cavity +from lcls_tools.superconducting.sc_cryomodule import Cryomodule +from lcls_tools.superconducting.sc_linac import Linac, Machine +from simulacrum import Service + + +class PropertyFloatChannel(ChannelFloat): + def __init__(self, putter: Callable=None, precision=0, **kwargs): + super().__init__(precision=precision, **kwargs) + self.putter = putter + + async def verify_value(self, value): + value = await super().verify_value(value) + if self.putter is not None: + return await self.putter(value) + +class SimulacrumCavity(Cavity): + def __init__(self, cavity_num: int, rack_object: "Rack"): + super().__init__(cavity_num, rack_object) + self.channels = {self.aact_pv: PropertyFloatChannel(value=16.6, putter=self.aact_amp_change), + self.pv_addr("Q0"): ChannelFloat(value=randrange(int(2.5e10), int(3.5e10), step=int(0.1e10)))} + + async def aact_amp_change(self, value: float): + cm = self.cryomodule + cm.jt_stable_pos = cm.total_heat_load(amp_change={self.number: value}) + jt_man_pos = cm.channels[cm.jt_prefix + "MANPOS_RQST"].value + if jt_man_pos * 1.1 > cm.jt_stable_pos: + while jt_man_pos * 1.1 > cm.jt_stable_pos: + await cm.adjust_liquid_level() + elif jt_man_pos * 0.9 < cm.jt_stable_pos: + while jt_man_pos * 0.9 < cm.jt_stable_pos: + await cm.adjust_liquid_level() + + +class SimulacrumCM(Cryomodule): + def __init__(self, cryo_name: str, linac_object: "Linac", ): + super().__init__(cryo_name, linac_object) + self.channels = {} + self.heater_setpoint = f"CPIC:CM{self.name}:0000:EHCV:MANPOS_RQST" + self.channels[self.ds_level_pv] = ChannelFloat(value=93.0) + self.channels[self.us_level_pv] = ChannelFloat(value=75.0) + self.channels[self.jt_valve_readback_pv] = ChannelFloat(value=30.0) + self.channels[self.jt_prefix + "MANPOS_RQST"] = PropertyFloatChannel(value=40.0, putter=self.jt_valve_update) + self.channels[self.heater_setpoint] = PropertyFloatChannel(value=24.0, putter=self.heater_setpoint_update) + self.jt_stable_pos = self.total_heat_load() + + def total_heat_load(self, heater_value: Optional[float] = None, amp_change: Optional[Dict[int, float]] = None): + rf_heat = 0 + for _, cavity in self.cavities.items(): + channels = cavity.channels + aact = channels[cavity.aact_pv].value + if amp_change: + if cavity.number == list(amp_change.keys())[0]: + aact = amp_change[cavity.number] + rf_heat += (aact * 1e6) ** 2 / (1012 * channels[cavity.pv_addr("Q0")].value) + heater = self.channels[self.heater_setpoint].value if not heater_value else heater_value + return rf_heat + heater + + async def adjust_liquid_level(self): + # TODO: calculate sleep + liquid gained/lost function + LOSS_AMOUNT = 0.01 + GAIN_AMOUNT = LOSS_AMOUNT + SLEEP_AMOUNT = 1 + + jt_setpoint = self.channels[self.jt_prefix + "MANPOS_RQST"].value + curr = self.channels[self.ds_level_pv].value + if jt_setpoint * 1.1 > self.jt_stable_pos: + await self.channels[self.ds_level_pv].write(curr - LOSS_AMOUNT) + await sleep(SLEEP_AMOUNT) + elif jt_setpoint * 0.9 < self.jt_stable_pos: + await self.channels[self.ds_level_pv].write(curr + GAIN_AMOUNT) + await sleep(SLEEP_AMOUNT) + + async def jt_valve_update(self, instance, value: float): + if value * 1.1 > self.jt_stable_pos: + while value * 1.1 > self.jt_stable_pos: + await sleep(1) + elif value * 0.9 < self.jt_stable_pos: + while value * 0.9 < self.jt_stable_pos: + await sleep(1) + + async def heater_setpoint_update(self, value: float): + self.jt_stable_pos = self.total_heat_load(heater_value=value) + jt_man_pos = self.channels[self.jt_prefix + "MANPOS_RQST"].value + if jt_man_pos * 1.1 > self.jt_stable_pos: + while jt_man_pos * 1.1 > self.jt_stable_pos: + await self.adjust_liquid_level() + elif jt_man_pos * 0.9 < self.jt_stable_pos: + while jt_man_pos * 0.9 < self.jt_stable_pos: + await self.adjust_liquid_level() + + +class CavityService(Service): + def __init__(self): + super().__init__() + machine = Machine(cavity_class=SimulacrumCavity, cryomodule_class=SimulacrumCM) + for cryomodule in machine.cryomodules.values(): + for p, c in cryomodule.channels.items(): + self[p] = c + for cavity in cryomodule.cavities.values(): + for pv, channel in cavity.channels.items(): + self[pv] = channel + + +def main(): + service = CavityService() + get_event_loop() + _, run_options = ioc_arg_parser( + default_prefix="", desc="Simulated CM Cavity Service" + ) + run(service, **run_options) + + +if __name__ == "__main__": + main() From 723f99d6df8b28de66605f168e470e3449397baf Mon Sep 17 00:00:00 2001 From: ssoham Date: Mon, 5 Aug 2024 14:52:46 -0700 Subject: [PATCH 4/6] fix: add bounds on liquid level and fix JT valve updates --- sc_rf_service/sc_rf_refactored_service.py | 112 +++++++++++++++------- 1 file changed, 77 insertions(+), 35 deletions(-) diff --git a/sc_rf_service/sc_rf_refactored_service.py b/sc_rf_service/sc_rf_refactored_service.py index e93d971..b77c37d 100644 --- a/sc_rf_service/sc_rf_refactored_service.py +++ b/sc_rf_service/sc_rf_refactored_service.py @@ -1,8 +1,9 @@ from asyncio import get_event_loop, sleep +from datetime import datetime from random import randrange from typing import Callable, Optional, Dict -from caproto import ChannelFloat +from caproto import ChannelFloat, ChannelEnum, ChannelInteger, ChannelString from caproto.server import ioc_arg_parser, run from lcls_tools.superconducting.sc_cavity import Cavity from lcls_tools.superconducting.sc_cryomodule import Cryomodule @@ -11,53 +12,89 @@ class PropertyFloatChannel(ChannelFloat): - def __init__(self, putter: Callable=None, precision=0, **kwargs): + def __init__(self, putter: Callable = None, precision=0, **kwargs): super().__init__(precision=precision, **kwargs) self.putter = putter async def verify_value(self, value): value = await super().verify_value(value) - if self.putter is not None: - return await self.putter(value) + if self.putter: + await self.putter(value) + return value + class SimulacrumCavity(Cavity): def __init__(self, cavity_num: int, rack_object: "Rack"): super().__init__(cavity_num, rack_object) self.channels = {self.aact_pv: PropertyFloatChannel(value=16.6, putter=self.aact_amp_change), - self.pv_addr("Q0"): ChannelFloat(value=randrange(int(2.5e10), int(3.5e10), step=int(0.1e10)))} + self.pv_addr("Q0"): ChannelFloat(value=randrange(int(2.5e10), int(3.5e10), step=int(0.1e10))), + self.acon_pv: ChannelFloat(value=16.6, precision=2), + self.ades_pv: ChannelFloat(value=16.6, precision=2), + self.rf_mode_ctrl_pv: ChannelEnum(value=4, + enum_strings=( + "SELAP", "SELA", "SEL", "SEL Raw", "Pulse", "Chirp")), + self.rf_mode_pv: ChannelEnum(value=0, + enum_strings=( + "SELAP", "SELA", "SEL", "SEL Raw", "Pulse", "Chirp")), + self.rf_state_pv: ChannelEnum(value=1, enum_strings=("Off", "On")), + self.ades_max_pv: ChannelFloat(value=21.0), + self.rf_permit_pv: ChannelEnum(value=1, enum_strings=("RF inhibit", "RF allow")), + self.calc_probe_q_pv: ChannelInteger(value=0), + self.push_ssa_slope_pv: ChannelInteger(value=0), + self.interlock_reset_pv: ChannelEnum(value=0, enum_strings=("", "Reset")), + self.drive_level_pv: ChannelFloat(value=0.0), + self.characterization_start_pv: ChannelInteger(value=0), + self.characterization_status_pv: ChannelString( + value=datetime.now().strftime("%Y-%m-%d-%H:%M:%S")), + self.push_scale_factor_pv: ChannelInteger(value=0), + self.stepper_temp_pv: ChannelFloat(value=35.0), + self.detune_best_pv: ChannelInteger(value=randrange(-10000, 10000)), + self.quench_latch_pv: ChannelEnum(value=0, enum_strings=("Ok", "Fault")), + } async def aact_amp_change(self, value: float): cm = self.cryomodule cm.jt_stable_pos = cm.total_heat_load(amp_change={self.number: value}) jt_man_pos = cm.channels[cm.jt_prefix + "MANPOS_RQST"].value - if jt_man_pos * 1.1 > cm.jt_stable_pos: - while jt_man_pos * 1.1 > cm.jt_stable_pos: + if jt_man_pos * 0.9 > cm.jt_stable_pos: + while jt_man_pos * 0.9 > cm.jt_stable_pos: await cm.adjust_liquid_level() - elif jt_man_pos * 0.9 < cm.jt_stable_pos: - while jt_man_pos * 0.9 < cm.jt_stable_pos: + elif jt_man_pos * 1.1 < cm.jt_stable_pos: + while jt_man_pos * 1.1 < cm.jt_stable_pos: await cm.adjust_liquid_level() class SimulacrumCM(Cryomodule): - def __init__(self, cryo_name: str, linac_object: "Linac", ): + def __init__(self, cryo_name: str, linac_object: "Linac"): super().__init__(cryo_name, linac_object) self.channels = {} - self.heater_setpoint = f"CPIC:CM{self.name}:0000:EHCV:MANPOS_RQST" + self.heater_prefix = f"CPIC:CM{self.name}:0000:EHCV:" + self.heater_setpoint = self.heater_prefix + "MANPOS_RQST" + self.heater_mode = self.heater_prefix + "MODE_STRING" self.channels[self.ds_level_pv] = ChannelFloat(value=93.0) self.channels[self.us_level_pv] = ChannelFloat(value=75.0) self.channels[self.jt_valve_readback_pv] = ChannelFloat(value=30.0) self.channels[self.jt_prefix + "MANPOS_RQST"] = PropertyFloatChannel(value=40.0, putter=self.jt_valve_update) + self.channels[self.jt_valve_readback_pv] = ChannelFloat(value=40.0) self.channels[self.heater_setpoint] = PropertyFloatChannel(value=24.0, putter=self.heater_setpoint_update) - self.jt_stable_pos = self.total_heat_load() + self.channels[self.heater_readback_pv] = ChannelFloat(value=24.0) + self.channels[self.heater_mode] = ChannelString(value="MANUAL") + + self._jt_stable_pos: Optional[float] = None + + @property + def jt_stable_pos(self): + if not self._jt_stable_pos: + self._jt_stable_pos = self.total_heat_load() + return self.jt_stable_pos def total_heat_load(self, heater_value: Optional[float] = None, amp_change: Optional[Dict[int, float]] = None): rf_heat = 0 - for _, cavity in self.cavities.items(): + for cavity in self.cavities.values(): channels = cavity.channels aact = channels[cavity.aact_pv].value - if amp_change: - if cavity.number == list(amp_change.keys())[0]: - aact = amp_change[cavity.number] + if amp_change and cavity.number == list(amp_change.keys())[0]: + aact = amp_change[cavity.number] rf_heat += (aact * 1e6) ** 2 / (1012 * channels[cavity.pv_addr("Q0")].value) heater = self.channels[self.heater_setpoint].value if not heater_value else heater_value return rf_heat + heater @@ -70,30 +107,35 @@ async def adjust_liquid_level(self): jt_setpoint = self.channels[self.jt_prefix + "MANPOS_RQST"].value curr = self.channels[self.ds_level_pv].value - if jt_setpoint * 1.1 > self.jt_stable_pos: - await self.channels[self.ds_level_pv].write(curr - LOSS_AMOUNT) - await sleep(SLEEP_AMOUNT) - elif jt_setpoint * 0.9 < self.jt_stable_pos: - await self.channels[self.ds_level_pv].write(curr + GAIN_AMOUNT) + if 70 <= curr <= 100: + new_amount = curr + if jt_setpoint * 0.9 > self.jt_stable_pos: + new_amount -= LOSS_AMOUNT + elif jt_setpoint * 1.1 < self.jt_stable_pos: + new_amount += GAIN_AMOUNT + await self.channels[self.ds_level_pv].write(new_amount) await sleep(SLEEP_AMOUNT) - async def jt_valve_update(self, instance, value: float): - if value * 1.1 > self.jt_stable_pos: - while value * 1.1 > self.jt_stable_pos: - await sleep(1) - elif value * 0.9 < self.jt_stable_pos: - while value * 0.9 < self.jt_stable_pos: - await sleep(1) + async def jt_valve_update(self, value: float): + await self.channels[self.jt_valve_readback_pv].write(value) + if value * 0.9 > self.jt_stable_pos: + while self.channels[self.jt_valve_readback_pv].value * 0.9 > self.jt_stable_pos: + await self.adjust_liquid_level() + elif value * 1.1 < self.jt_stable_pos: + while self.channels[self.jt_valve_readback_pv].value * 1.1 < self.jt_stable_pos: + await self.adjust_liquid_level() async def heater_setpoint_update(self, value: float): self.jt_stable_pos = self.total_heat_load(heater_value=value) - jt_man_pos = self.channels[self.jt_prefix + "MANPOS_RQST"].value - if jt_man_pos * 1.1 > self.jt_stable_pos: - while jt_man_pos * 1.1 > self.jt_stable_pos: - await self.adjust_liquid_level() - elif jt_man_pos * 0.9 < self.jt_stable_pos: - while jt_man_pos * 0.9 < self.jt_stable_pos: - await self.adjust_liquid_level() + if self.channels[self.heater_mode].value == "MANUAL": + await self.channels[self.heater_readback_pv].write(value) + jt_man_pos = self.channels[self.jt_prefix + "MANPOS_RQST"].value + if jt_man_pos * 0.9 > self.jt_stable_pos: + while self.channels[self.jt_prefix + "MANPOS_RQST"].value * 0.9 > self.jt_stable_pos: + await self.adjust_liquid_level() + elif jt_man_pos * 1.1 < self.jt_stable_pos: + while self.channels[self.jt_prefix + "MANPOS_RQST"].value * 1.1 < self.jt_stable_pos: + await self.adjust_liquid_level() class CavityService(Service): From 6e2f981ce231fa33f0a294896453544a915cdb3d Mon Sep 17 00:00:00 2001 From: ssoham Date: Mon, 5 Aug 2024 15:44:59 -0700 Subject: [PATCH 5/6] refactor: move JT loop to liquid level adjustment --- sc_rf_service/sc_rf_refactored_service.py | 59 ++++++++--------------- 1 file changed, 20 insertions(+), 39 deletions(-) diff --git a/sc_rf_service/sc_rf_refactored_service.py b/sc_rf_service/sc_rf_refactored_service.py index b77c37d..98b0b20 100644 --- a/sc_rf_service/sc_rf_refactored_service.py +++ b/sc_rf_service/sc_rf_refactored_service.py @@ -55,13 +55,7 @@ def __init__(self, cavity_num: int, rack_object: "Rack"): async def aact_amp_change(self, value: float): cm = self.cryomodule cm.jt_stable_pos = cm.total_heat_load(amp_change={self.number: value}) - jt_man_pos = cm.channels[cm.jt_prefix + "MANPOS_RQST"].value - if jt_man_pos * 0.9 > cm.jt_stable_pos: - while jt_man_pos * 0.9 > cm.jt_stable_pos: - await cm.adjust_liquid_level() - elif jt_man_pos * 1.1 < cm.jt_stable_pos: - while jt_man_pos * 1.1 < cm.jt_stable_pos: - await cm.adjust_liquid_level() + await cm.adjust_liquid_level() class SimulacrumCM(Cryomodule): @@ -80,13 +74,7 @@ def __init__(self, cryo_name: str, linac_object: "Linac"): self.channels[self.heater_readback_pv] = ChannelFloat(value=24.0) self.channels[self.heater_mode] = ChannelString(value="MANUAL") - self._jt_stable_pos: Optional[float] = None - - @property - def jt_stable_pos(self): - if not self._jt_stable_pos: - self._jt_stable_pos = self.total_heat_load() - return self.jt_stable_pos + self.jt_stable_pos: Optional[float] = self.total_heat_load() def total_heat_load(self, heater_value: Optional[float] = None, amp_change: Optional[Dict[int, float]] = None): rf_heat = 0 @@ -105,37 +93,30 @@ async def adjust_liquid_level(self): GAIN_AMOUNT = LOSS_AMOUNT SLEEP_AMOUNT = 1 - jt_setpoint = self.channels[self.jt_prefix + "MANPOS_RQST"].value - curr = self.channels[self.ds_level_pv].value - if 70 <= curr <= 100: - new_amount = curr - if jt_setpoint * 0.9 > self.jt_stable_pos: - new_amount -= LOSS_AMOUNT - elif jt_setpoint * 1.1 < self.jt_stable_pos: - new_amount += GAIN_AMOUNT - await self.channels[self.ds_level_pv].write(new_amount) - await sleep(SLEEP_AMOUNT) - - async def jt_valve_update(self, value: float): + jt_setpoint = self.channels[self.jt_valve_readback_pv].value + print(self.jt_stable_pos) + if jt_setpoint * 0.9 > self.jt_stable_pos: + while (self.channels[self.jt_valve_readback_pv].value * 0.9 > self.jt_stable_pos + and 70 <= self.channels[self.ds_level_pv].value <= 100): + curr = self.channels[self.ds_level_pv].value + await self.channels[self.ds_level_pv].write(curr - LOSS_AMOUNT) + await sleep(SLEEP_AMOUNT) + elif jt_setpoint * 1.1 < self.jt_stable_pos: + while (self.channels[self.jt_valve_readback_pv].value * 1.1 < self.jt_stable_pos + and 70 <= self.channels[self.ds_level_pv].value <= 100): + curr = self.channels[self.ds_level_pv].value + await self.channels[self.ds_level_pv].write(curr + GAIN_AMOUNT) + await sleep(SLEEP_AMOUNT) + + async def jt_valve_update(self, value): await self.channels[self.jt_valve_readback_pv].write(value) - if value * 0.9 > self.jt_stable_pos: - while self.channels[self.jt_valve_readback_pv].value * 0.9 > self.jt_stable_pos: - await self.adjust_liquid_level() - elif value * 1.1 < self.jt_stable_pos: - while self.channels[self.jt_valve_readback_pv].value * 1.1 < self.jt_stable_pos: - await self.adjust_liquid_level() + await self.adjust_liquid_level() async def heater_setpoint_update(self, value: float): self.jt_stable_pos = self.total_heat_load(heater_value=value) if self.channels[self.heater_mode].value == "MANUAL": await self.channels[self.heater_readback_pv].write(value) - jt_man_pos = self.channels[self.jt_prefix + "MANPOS_RQST"].value - if jt_man_pos * 0.9 > self.jt_stable_pos: - while self.channels[self.jt_prefix + "MANPOS_RQST"].value * 0.9 > self.jt_stable_pos: - await self.adjust_liquid_level() - elif jt_man_pos * 1.1 < self.jt_stable_pos: - while self.channels[self.jt_prefix + "MANPOS_RQST"].value * 1.1 < self.jt_stable_pos: - await self.adjust_liquid_level() + await self.adjust_liquid_level() class CavityService(Service): From 691ffc26a8b3eebbc88dc5db8823759dd84b6760 Mon Sep 17 00:00:00 2001 From: ssoham Date: Fri, 23 Aug 2024 02:25:01 -0700 Subject: [PATCH 6/6] feat: implement pid --- pid.py | 35 +++++++++++++++++++++++ sc_rf_service/sc_rf_refactored_service.py | 32 +++++++++++++++++---- 2 files changed, 62 insertions(+), 5 deletions(-) create mode 100644 pid.py diff --git a/pid.py b/pid.py new file mode 100644 index 0000000..9c17356 --- /dev/null +++ b/pid.py @@ -0,0 +1,35 @@ +from time import time + +class PID: + def __init__(self, setpoint, range=(None, None), Kp=0.0, Ki=0.0, Kd=0.0): + # self.min = min + # self.max = max + self.setpoint = setpoint + self.Kp = Kp + self.Ki = Ki + self.Kd = Kd + + self.proportional = 0 + self.integral = 0 + self.last_error = None + self.current_time = time() + + def __call__(self, current): + print(current) + new_time = time() + dt = new_time - self.current_time + self.current_time = new_time + error = self.setpoint - current + derror = error - (self.last_error if self.last_error else error) + + self.proportional = self.Kp * error + # print(self.proportional) + self.integral += self.Ki * error * dt # need to avoid the windup + derivative = self.Kd * derror / dt + + self.last_error = error + + return self.proportional + self.integral + derivative + + + diff --git a/sc_rf_service/sc_rf_refactored_service.py b/sc_rf_service/sc_rf_refactored_service.py index 98b0b20..135c8b5 100644 --- a/sc_rf_service/sc_rf_refactored_service.py +++ b/sc_rf_service/sc_rf_refactored_service.py @@ -1,3 +1,4 @@ +import time from asyncio import get_event_loop, sleep from datetime import datetime from random import randrange @@ -8,6 +9,7 @@ from lcls_tools.superconducting.sc_cavity import Cavity from lcls_tools.superconducting.sc_cryomodule import Cryomodule from lcls_tools.superconducting.sc_linac import Linac, Machine +from pid import PID from simulacrum import Service @@ -68,13 +70,22 @@ def __init__(self, cryo_name: str, linac_object: "Linac"): self.channels[self.ds_level_pv] = ChannelFloat(value=93.0) self.channels[self.us_level_pv] = ChannelFloat(value=75.0) self.channels[self.jt_valve_readback_pv] = ChannelFloat(value=30.0) - self.channels[self.jt_prefix + "MANPOS_RQST"] = PropertyFloatChannel(value=40.0, putter=self.jt_valve_update) + self.channels[self.jt_prefix + "MANPOS_RQST"] = ChannelFloat(value=70.0) + # self.channels[self.jt_prefix + "MANPOS_RQST"] = PropertyFloatChannel(value=70.0, putter=self.jt_valve_update) self.channels[self.jt_valve_readback_pv] = ChannelFloat(value=40.0) + self.channels[self.jt_prefix + "AUTO"] = PropertyFloatChannel(value=0, putter=self.jt_automate) self.channels[self.heater_setpoint] = PropertyFloatChannel(value=24.0, putter=self.heater_setpoint_update) self.channels[self.heater_readback_pv] = ChannelFloat(value=24.0) self.channels[self.heater_mode] = ChannelString(value="MANUAL") self.jt_stable_pos: Optional[float] = self.total_heat_load() + self._pid: Optional[PID] = None + + @property + def pid(self): + if not self._pid: + self._pid = PID(57, Kp=0.1, Ki=0) + return self._pid def total_heat_load(self, heater_value: Optional[float] = None, amp_change: Optional[Dict[int, float]] = None): rf_heat = 0 @@ -85,16 +96,16 @@ def total_heat_load(self, heater_value: Optional[float] = None, amp_change: Opti aact = amp_change[cavity.number] rf_heat += (aact * 1e6) ** 2 / (1012 * channels[cavity.pv_addr("Q0")].value) heater = self.channels[self.heater_setpoint].value if not heater_value else heater_value + print(f"New JT stable position: {rf_heat + heater}") return rf_heat + heater async def adjust_liquid_level(self): # TODO: calculate sleep + liquid gained/lost function - LOSS_AMOUNT = 0.01 + LOSS_AMOUNT = 0.3 GAIN_AMOUNT = LOSS_AMOUNT SLEEP_AMOUNT = 1 jt_setpoint = self.channels[self.jt_valve_readback_pv].value - print(self.jt_stable_pos) if jt_setpoint * 0.9 > self.jt_stable_pos: while (self.channels[self.jt_valve_readback_pv].value * 0.9 > self.jt_stable_pos and 70 <= self.channels[self.ds_level_pv].value <= 100): @@ -109,8 +120,13 @@ async def adjust_liquid_level(self): await sleep(SLEEP_AMOUNT) async def jt_valve_update(self, value): - await self.channels[self.jt_valve_readback_pv].write(value) - await self.adjust_liquid_level() + if value * 0.9 <= self.jt_stable_pos <= value * 1.1: + await self.channels[self.jt_valve_readback_pv].write(value) + else: + await self.channels[self.jt_valve_readback_pv].write(self.jt_stable_pos) # to stop (outdated) loop, as + # LOSS/GAIN amount is a function of current position + await self.channels[self.jt_valve_readback_pv].write(value) + await self.adjust_liquid_level() async def heater_setpoint_update(self, value: float): self.jt_stable_pos = self.total_heat_load(heater_value=value) @@ -118,6 +134,12 @@ async def heater_setpoint_update(self, value: float): await self.channels[self.heater_readback_pv].write(value) await self.adjust_liquid_level() + async def jt_automate(self, value): + while True: + # print(self.channels[self.jt_prefix + "MANPOS_RQST"].value) + await self.channels[self.jt_prefix + "MANPOS_RQST"].write(self.pid(self.channels[self.jt_prefix + "MANPOS_RQST"].value)) + await sleep(1) + class CavityService(Service): def __init__(self):