Code indexing in gitaly is broken and leads to code not being visible to the user. We work on the issue with highest priority.

Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • slic/maloja
1 result
Show changes
Commits on Source (2)
import numpy as np
import pint
from scipy import optimize
unit = pint.UnitRegistry()
MOMENTUM_SCALE = (1 * unit.GeV / unit.c).to_base_units().magnitude
N_MAGNETS = 4
N_DRIFTS = 2
MAGNET_LENGTH = 0.3109 * unit.m
DRIFT_LENGTH = 0.42 * unit.m
DISTANCE_BETWEEN_MAGNETS_2_AND_3 = 0.16 * unit.m
TOTAL_LENGTH = N_MAGNETS * MAGNET_LENGTH + N_DRIFTS * DRIFT_LENGTH + DISTANCE_BETWEEN_MAGNETS_2_AND_3
# unclear what these magic numbers are:
MAGNUM1 = 0.2998
MAGNUM2 = 0.001934
class DelayCurrentConverter:
def __init__(self, I_guess=30):
"""
I_guess in ampere, must be dimensionless for minimizer
"""
self.I_guess = I_guess
self.vectorized_current_A = np.vectorize(self.current_A)
self.vectorized_delay_fs = np.vectorize(self.delay_fs)
# dimensionless functions
def current_A(self, delay_fs, beam_energy_MeV):
delay = delay_fs * unit.fs
beam_momentum = beam_energy_MeV * unit.MeV / unit.c
return self.current(delay, beam_momentum).to("A").magnitude
def delay_fs(self, I_ampere, beam_energy_MeV):
I = I_ampere * unit.A
beam_momentum = beam_energy_MeV * unit.MeV / unit.c
return self.delay(I, beam_momentum).to("fs").magnitude
# functions below expect pint
def current(self, delay, beam_momentum):
res = optimize.root_scalar(self.delay_error, args=(delay, beam_momentum), x0=self.I_guess, x1=2*self.I_guess)
return res.root * unit.A
def delay(self, I, beam_momentum):
delta = self.path_length_difference(I, beam_momentum) / unit.c
return delta.to("fs")
def delay_error(self, I_ampere, delay_target, beam_momentum):
I = I_ampere * unit.A
result_delay = self.delay(I, beam_momentum)
distance = delay_target - result_delay
return distance.to("fs").magnitude
def path_length_difference(self, I, beam_momentum):
length = self.path_length(I, beam_momentum)
return length - TOTAL_LENGTH
def path_length(self, I, beam_momentum):
angle = self.deflection_angle(I, beam_momentum)
magnet = N_MAGNETS * self.orbit_radius_magnet(I, beam_momentum) * angle
drift = N_DRIFTS * DRIFT_LENGTH / np.cos(angle)
total = magnet + drift + DISTANCE_BETWEEN_MAGNETS_2_AND_3
return total
def orbit_radius_magnet(self, I, beam_momentum):
angle = self.deflection_angle(I, beam_momentum)
return MAGNET_LENGTH / np.sin(angle)
def deflection_angle(self, I, beam_momentum):
field = self.integrate_magnet_field(I)
scaled = beam_momentum / MOMENTUM_SCALE
argument = field / scaled * MAGNUM1 * unit.C
return np.arcsin(argument)
def integrate_magnet_field(self, I):
return I * MAGNUM2 * unit.m * unit.T / unit.A
......@@ -7,6 +7,9 @@ from logzero import logger as log
from slic.core.adjustable import Adjustable, PVAdjustable, PVEnumAdjustable
from slic.core.scanner.scanbackend import wait_for_all #, stop_all
from slic.utils import cprint, tqdm_sleep
from .delay_current import DelayCurrentConverter
UND_NAME_FMT = "SATUN{:02}-UIND030"
......@@ -313,18 +316,81 @@ class CHIC(PVAdjustable):
class TwoColorChicane(PVAdjustable):
class TwoColorChicaneCurrent(PVAdjustable):
def __init__(
self,
pvname_setvalue="SATUN14-MBND100:I-SET", pvname_readback="SATUN14-MBND100:I-READ", name="Two Color Chicane as current",
accuracy=0.1, process_time=1,
hysteresis_protection=True,
**kwargs
):
super().__init__(
pvname_setvalue, pvname_readback, name=name,
accuracy=accuracy, process_time=process_time,
**kwargs
)
self.hysteresis_protection = hysteresis_protection
self.cycle_time = 250
self.pv_cycle = PV("SATUN14-MBND100:CYCLE")
self.previous_target = None
def set_target_value(self, value, **kwargs):
current = self.get_current_value()
if value < current:
cprint(f"Hysteresis Warning: target value {value} is smaller than the current value {current}", color="red")
if self.hysteresis_protection and value == self.previous_target:
cprint(f"target value {value} is identical to previous target value, will not try to change", color="cyan")
return
self.previous_target = value
super().set_target_value(value, **kwargs)
def cycle_magnet(self):
self.pv_cycle.put(1, wait=True)
tqdm_sleep(self.cycle_time)
# set the current to 1 and 2 consecutively,
# since the results after the first step always looks strange
self.set_target_value(1).wait()
sleep(1)
self.set_target_value(2).wait()
sleep(1)
class TwoColorChicaneDelay(Adjustable):
def __init__(self, ID="TWO-COLOR-CHICANE-DELAY", name="Two Color Chicane as delay", pvname_electron_energy="SATCB01:ENE-FILT-OP", hysteresis_protection=True, units="fs", **kwargs):
self.adj_current = TwoColorChicaneCurrent(hysteresis_protection=hysteresis_protection, internal=True)
self.pv_electron_energy = PV(pvname_electron_energy)
self.converter = DelayCurrentConverter()
super().__init__(ID, name=name, units=units, **kwargs)
def get_current_value(self):
current = self.adj_current.get_current_value()
delay = self.calc_delay_fs(current)
return delay
def set_target_value(self, delay):
current = self.calc_current_A(delay)
print(f"{current} A <- {delay} fs")
self.adj_current.set_target_value(current).wait()
def is_moving(self):
return self.adj_current.is_moving()
def __init__(self, name):#, t0=0):
# self.t0 = t0
# name += " Two Color Chicane"
super().__init__("SATUN14-MBND100:I-SET", "SATUN14-MBND100:I-READ", process_time=1, name=name)
def cycle_magnet(self):
self.adj_current.cycle_magnet()
# def set_target_value(self, value, hold=False):
# super().set_target_value(value)
def calc_current_A(self, delay_fs):
electron_energy = self.pv_electron_energy.get()
return self.converter.current_A(delay_fs, electron_energy)
# def get_current_value(self):
# return super().get_current_value()
def calc_delay_fs(self, current_A):
electron_energy = self.pv_electron_energy.get()
return self.converter.delay_fs(current_A, electron_energy)
......