From b3d9d0097da8bf00a6efeb3135782cef4e83ebde Mon Sep 17 00:00:00 2001 From: Sven Augustin Date: Fri, 7 Oct 2022 11:47:30 +0200 Subject: [PATCH] first idea for polarization parallel phases --- phases/__init__.py | 0 phases/dataconverter.py | 67 +++++++++++++++++++ phases/model.py | 27 ++++++++ phases/parallel.py | 143 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 237 insertions(+) create mode 100644 phases/__init__.py create mode 100755 phases/dataconverter.py create mode 100644 phases/model.py create mode 100644 phases/parallel.py diff --git a/phases/__init__.py b/phases/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/phases/dataconverter.py b/phases/dataconverter.py new file mode 100755 index 0000000..cfc2706 --- /dev/null +++ b/phases/dataconverter.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python + +import argparse + +parser = argparse.ArgumentParser() +parser.add_argument("finput", help="name of excel file to load") +parser.add_argument("foutput", help="name of json file to write") +clargs = parser.parse_args() + + +from collections import defaultdict +import pandas as pd +from slic.utils import json_save + + +def load(fn, nparams=6, skip_cols=3): + engine = "xlrd" if fn.endswith(".xls") else "openpyxl" + df = pd.read_excel(fn, engine=engine) + + header = df.columns + data = df.values + + nrows, ncols = data.shape + assert nrows % nparams == 0 + nunds = nrows // nparams + + res = defaultdict(dict) + for i in range(nunds): + start = i * nparams + stop = start + nparams + + und_name = data[start, 1] + idata = data[start:stop, 1:] + + for j in range(skip_cols, ncols): + param_name = header[j] + jdata = idata[:, j-1].astype(float) + res[und_name][param_name] = list(jdata) + + return dict(**res) + + +def print_overview(data): + unds = data.keys() + unds = sorted(unds) + + params = data[unds[0]].keys() + params = sorted(params) + + nunds = len(unds) + nparams = len(params) + + print(f"read {nparams} parameters for {nunds} undulators") + print("\nUndulators:", ", ".join(unds)) + print("\nParameters:", ", ".join(params)) + + + + + +if __name__ == "__main__": + data = load(clargs.finput) + print_overview(data) + json_save(data, clargs.foutput) + + + diff --git a/phases/model.py b/phases/model.py new file mode 100644 index 0000000..5f3e969 --- /dev/null +++ b/phases/model.py @@ -0,0 +1,27 @@ +import numpy as np +from scipy.optimize import fsolve + + +def parallel2gap(K, phi, undudict): + gLH = K2gap(K, undudict['K-value_LH']) + if phi >= 0.0: + gLV = K2gap(K, undudict['K-value_LV+']) + gC = K2gap(K, undudict['K-value_C+']) + dgLV = gLV - gLH + dgC = gC - gLH - dgLV/2 + else: + gLV = K2gap(K, undudict['K-value_LV-']) + gC = K2gap(K, undudict['K-value_C-']) + dgLV = gLV - gLH + dgC = gC - gLH - dgLV/2 + return gLH + dgLV * np.sin(0.5 * phi)**2 + dgC * np.sin(phi)**2 + + +def K2gap(Kval, fitparam): + g2K_func = np.poly1d(fitparam[::-1]) + tau_init = 1.0 + k_log = float(np.log(Kval)) + return float(fsolve(k_log - g2K_func, tau_init)) + + + diff --git a/phases/parallel.py b/phases/parallel.py new file mode 100644 index 0000000..17b15a5 --- /dev/null +++ b/phases/parallel.py @@ -0,0 +1,143 @@ +from pyepics import PV +from slic.core.adjustable import Adjustable, PVAdjustable +from slic.utils import json_load +from model import parallel2gap + + +UND_PERIOD = 38.0 + + +def check_phase(phase): + assert -180 <= phase <= 180 #TODO: modulo to be in the correct range? + +def convert_phase_to_shift(phase): + ratio = UND_PERIOD / 360.0 + return phase * ratio / 2 + + + +class UndPhases(Adjustable): + """ + Set of several UndPhase objects + allows to set the same phase to all undulators + """ + + def __init__(self, ID, params, und_names=None, **kwargs): + super().__init__(ID, **kwargs) + self.params = params + + if und_names is None: + und_names = params.keys() + + SUFFIX = "-UIND030:" + self.phases = [UndPhase(i + SUFFIX, params[i]) for i in und_names] + + + def set_target_value(self, value): + tasks = [p.set_target_value(value) for p in self.phases] + for t in tasks: + t.wait() + + + +class UndPhase(Adjustable): + """ + Combination of UndShift, UndRadial and UndTotalK + allows to set the phase of one undulator + """ + + def __init__(self, ID, params, **kwargs): + super().__init__(ID, **kwargs) + self.params = params + self.shift = UndShift(ID) + self.radial = UndRadial(ID) + self.totalk = UndTotalK(ID) + + + def get_current_value(self): + raise NotImplementedError #TODO: how to do that? + + + def set_target_value(self, value): + phase = value + shift = convert_phase_to_shift(phase) + + k = self.totalk.get() + radial = parallel2gap(k, phase, self.params) + radial = round(radial, 4) #TODO: why? + + self.shift.set_target_value(shift).wait() + self.radial.set_target_value(radial).wait() + + + def is_moving(self): + return self.shift.is_moving() or self.radial.is_moving() + + + +class UndShiftRadialBase(PVAdjustable): #TODO: better name? + """ + Base class with functionality shared between UndShift and UndRadial + """ + + def __init__(self, ID, accuracy=0.001): + super().__init__(ID + "-SET", ID + "-TL", accuracy=accuracy) + self.pv_on = PV(ID + "-ON") + self.pv_go = PV(ID + "-GO") + + def set_target_value(self, value): + t = super().set_target_value(value) + sleep(0.3) + self.pv_on.put(1) + sleep(0.3) + self.pv_go.put(1) + t.wait() + + +class UndShift(UndShiftRadialBase): + + def __init__(self, ID): + super().__init__(ID + "SHIFT") + + +class UndRadial(UndShiftRadialBase): + + def __init__(self, ID): + super().__init__(ID + "RADIAL") + + +class UndTotalK: + """ + Helper class to get the total K from set value and taper + """ + + def __init__(self, ID): + self.pv_kset = PV(ID + "K_SET") + self.pv_ktaperset = PV(ID + "K_TAPER_SET") + + + def get(self): + k = self.pv_kset.get() + ktaper = self.pv_ktaperset.get() + + #TODO why? + k = round(k, 4) + ktaper = round(ktaper, 4) + + return k + ktaper + + + + + +if __name__ == "__main__": + phase = 123 + check_phase(phase) + + params = json_load("UE38_all_parallel_parameters.json") + + ups = UndPhases("SATUN-PHASES", params) + ups.set_target_value(phase).wait() + + +