378 lines
11 KiB
Python
378 lines
11 KiB
Python
from types import SimpleNamespace
|
|
|
|
from diffcalc.hkl.calc import HklCalculation
|
|
from diffcalc.hkl.constraints import Constraints
|
|
from diffcalc.hkl.geometry import Position
|
|
from diffcalc.ub.calc import UBCalculation
|
|
|
|
from slic.core.adjustable import Adjustable, PVAdjustable
|
|
from slic.core.device import Device, SimpleDevice
|
|
from slic.devices.general.motor import Motor
|
|
from slic.utils.printing import printable_table
|
|
|
|
from constraints import ExtraConstraint
|
|
|
|
INDICES = {
|
|
"h": 0,
|
|
"k": 1,
|
|
"l": 2
|
|
}
|
|
|
|
|
|
class QSpace3D(Device):
|
|
|
|
def __init__(self, ID, mu, chi, phi, nu, wavelength, **kwargs): # if the diffcalc objects need some initial parameters, add them here and fill them in below...
|
|
super().__init__(ID, **kwargs)
|
|
|
|
# collect the motors in a device for nicer printing
|
|
self.motors = motors = SimpleDevice(
|
|
ID,
|
|
mu = mu,
|
|
chi = chi,
|
|
phi = phi,
|
|
nu = nu
|
|
)
|
|
|
|
self.wavelength = wavelength
|
|
|
|
# some diffcalc objects ...
|
|
ub = UBCalculation()
|
|
cons = Constraints()
|
|
hkl = HklCalculation(ub, cons)
|
|
|
|
# ... collected in a namespace
|
|
self.dc = dc = SimpleNamespace(
|
|
ub = ub,
|
|
cons = cons,
|
|
hkl = hkl
|
|
)
|
|
|
|
# and the individual coordinates:
|
|
self.h = QSpace1D(ID + "-H", "h", dc, motors, wavelength)
|
|
self.k = QSpace1D(ID + "-K", "k", dc, motors, wavelength)
|
|
self.l = QSpace1D(ID + "-L", "l", dc, motors, wavelength)
|
|
|
|
|
|
# it might be nice (but optional) to add some shortcuts like this:
|
|
def set_lattice(self, *args, **kwargs):
|
|
self.dc.ub.set_lattice(*args, **kwargs)
|
|
|
|
def add_orientation(self, *args, **kwargs):
|
|
self.dc.ub.add_orientation(*args, **kwargs)
|
|
|
|
def del_orientation(self, *args):
|
|
self.dc.ub.del_orientation(*args)
|
|
|
|
def add_reflection(self, *args, **kwargs):
|
|
self.dc.ub.add_reflection(*args, **kwargs)
|
|
|
|
def del_reflection(self, *args):
|
|
self.dc.ub.del_reflection(*args)
|
|
|
|
def calc_ub(self, *args):
|
|
self.dc.ub.calc_ub(*args) # not sure whether this needs to be called explicitly?
|
|
|
|
def fit_ub(self, *args, **kwargs):
|
|
self.dc.ub.fit_ub(*args, **kwargs)
|
|
|
|
@property
|
|
def UB(self):
|
|
self.dc.ub.UB
|
|
|
|
def clear_constraints(self):
|
|
self.dc.cons.clear()
|
|
|
|
def set_constraints(self, **kwargs):
|
|
self.clear_constraints()
|
|
for name, value in kwargs.items():
|
|
setattr(self.dc.cons, name, value)
|
|
|
|
def get_position(self, *args, extra_cons):
|
|
wl = self.wavelength.get_current_value()
|
|
res = self.dc.hkl.get_position(*args, wl)
|
|
if extra_cons:
|
|
res = [r for r in res if extra_cons(_flatten_get_position_result(r))]
|
|
return res
|
|
|
|
def hard_constraints(x):
|
|
if x["betain"]<0:
|
|
return False
|
|
if x["betaout"]<0:
|
|
return False
|
|
if x["nu"]<0:
|
|
return False
|
|
if x["nu"]>180:
|
|
return False
|
|
if x["mu"]<0:
|
|
return False
|
|
if x["mu"]>90:
|
|
return False
|
|
if x["chi"]<-30:
|
|
return False
|
|
if x["chi"]>30:
|
|
return False
|
|
if x["phi"]<-60:
|
|
return False
|
|
if x["phi"]>60:
|
|
return False
|
|
return True
|
|
|
|
# if I understand the examples/code correctly, then some more method calls are mandatory?
|
|
# those should probably all get shortcuts...
|
|
|
|
def _get_hkl(self):
|
|
angles = self._get_angles()
|
|
return self._calc_hkl(*angles)
|
|
|
|
def _get_angles(self):
|
|
ms = self.motors
|
|
mu = ms.mu.get_current_value()
|
|
chi = ms.chi.get_current_value()
|
|
phi = ms.phi.get_current_value()
|
|
nu = ms.nu.get_current_value()
|
|
return mu, chi, phi, nu
|
|
|
|
def _calc_hkl(self, mu, chi, phi, nu):
|
|
wl = self.get_wavelength()
|
|
pos = Position(mu=mu, chi=chi, phi=phi, nu=nu)
|
|
hkl = self.dc.hkl.get_hkl(pos, wl)
|
|
return hkl
|
|
|
|
def get_wavelength(self):
|
|
return self.wavelength.get_current_value()
|
|
|
|
def _set_hkl(self, h, k, l, extra_cons):
|
|
angles = self._calc_angles(h, k, l, extra_cons)
|
|
self._set_angles(*angles)
|
|
|
|
def _set_angles(self, mu, chi, phi, nu):
|
|
ms = self.motors
|
|
t_mu = ms.mu.set_target_value(90+mu)
|
|
t_chi = ms.chi.set_target_value(chi-180)
|
|
t_phi = ms.phi.set_target_value(phi)
|
|
t_nu = ms.nu.set_target_value(180+nu) # We may need a correct offset value
|
|
# wait for all motors to arrive
|
|
tasks = (t_mu, t_chi, t_phi, t_nu)
|
|
for t in tasks:
|
|
t.wait()
|
|
|
|
# def _set_angles(self, mu, chi, phi, nu):
|
|
# ms = self.motors
|
|
# t_mu = ms.mu.set_target_value(90-mu)
|
|
# t_chi = ms.chi.set_target_value(chi)
|
|
# t_phi = ms.phi.set_target_value(phi)
|
|
# t_nu = ms.nu.set_target_value(nu)
|
|
# wait for all motors to arrive
|
|
# tasks = (t_mu, t_chi, t_phi, t_nu)
|
|
# for t in tasks:
|
|
# t.wait()
|
|
|
|
# def _calc_angles(self, h, k, l):
|
|
# wl = self.get_wavelength()
|
|
# pos, _virtual_angles = next(iter(
|
|
# self.dc.hkl.get_position(h, k, l, wl)
|
|
# ))
|
|
# return pos.mu, pos.chi, pos.phi, pos.nu
|
|
|
|
def _calc_angles(self, h, k, l, extra_cons):
|
|
wl = self.get_wavelength()
|
|
pos, _virtual_angles = next(iter(
|
|
self.get_position(h, k, l, extra_cons=extra_cons)
|
|
))
|
|
return pos.mu, pos.chi, pos.phi, pos.nu
|
|
|
|
def _flatten_get_position_result(x):
|
|
d0, d1 = x
|
|
d0 = d0.asdict
|
|
return dict(**d0, **d1)
|
|
|
|
|
|
|
|
class QSpace1D(Adjustable):
|
|
|
|
def __init__(self, ID, index, dc, motors, wavelength):
|
|
super().__init__(ID)
|
|
self.index = index
|
|
self.dc = dc
|
|
self.motors = motors
|
|
self.wavelength = wavelength
|
|
|
|
|
|
# the following three methods are mandatory:
|
|
|
|
def get_current_value(self):
|
|
hkl = self._get_hkl()
|
|
i = self._get_index()
|
|
return hkl[i]
|
|
|
|
def set_target_value(self, value, extra_cons):
|
|
# get all current indices
|
|
hkl = self._get_hkl()
|
|
hkl = list(hkl)
|
|
i = self._get_index()
|
|
# insert the target value into the right spot
|
|
hkl[i] = value
|
|
self._set_hkl(*hkl,extra_cons)
|
|
|
|
def is_moving(self):
|
|
ms = self.motors
|
|
return ms.mu.moving or ms.chi.moving or ms.phi.moving
|
|
|
|
|
|
# some helpful things:
|
|
|
|
def get_position(self, *args, extra_cons):
|
|
wl = self.wavelength.get_current_value()
|
|
res = self.dc.hkl.get_position(*args, wl)
|
|
if extra_cons:
|
|
res = [r for r in res if extra_cons(_flatten_get_position_result(r))]
|
|
return res
|
|
|
|
def get_wavelength(self):
|
|
return self.wavelength.get_current_value()
|
|
|
|
def _get_index(self):
|
|
i = self.index
|
|
if isinstance(i, str):
|
|
i = i.casefold()
|
|
i = INDICES.get(i, i)
|
|
if i not in INDICES.values():
|
|
allowed = INDICES.keys() | INDICES.values()
|
|
allowed = sorted(allowed, key=str)
|
|
raise ValueError(f"index must be from {allowed} but is {repr(i)}")
|
|
return i
|
|
|
|
def _get_hkl(self):
|
|
angles = self._get_angles()
|
|
return self._calc_hkl(*angles)
|
|
|
|
def _set_hkl(self, h, k, l, extra_cons):
|
|
angles = self._calc_angles(h, k, l, extra_cons)
|
|
self._set_angles(*angles)
|
|
|
|
def _get_angles(self):
|
|
ms = self.motors
|
|
mu = ms.mu.get_current_value()
|
|
chi = ms.chi.get_current_value()
|
|
phi = ms.phi.get_current_value()
|
|
nu = ms.nu.get_current_value()
|
|
return mu, chi, phi, nu
|
|
|
|
def _set_angles(self, mu, chi, phi, nu):
|
|
ms = self.motors
|
|
t_mu = ms.mu.set_target_value(mu)
|
|
t_chi = ms.chi.set_target_value(chi)
|
|
t_phi = ms.phi.set_target_value(phi)
|
|
t_nu = ms.nu.set_target_value(nu)
|
|
# wait for all motors to arrive
|
|
tasks = (t_mu, t_chi, t_phi, t_nu)
|
|
for t in tasks:
|
|
t.wait()
|
|
|
|
def _calc_hkl(self, mu, chi, phi, nu):
|
|
wl = self.get_wavelength()
|
|
pos = Position(mu=mu, chi=chi, phi=phi, nu=nu)
|
|
hkl = self.dc.hkl.get_hkl(pos, wl)
|
|
return hkl
|
|
|
|
# def _calc_angles(self, h, k, l):
|
|
# wl = self.get_wavelength()
|
|
# pos, _virtual_angles = next(iter(
|
|
# self.dc.hkl.get_position(h, k, l, wl)
|
|
# ))
|
|
# return pos.mu, pos.chi, pos.phi, pos.nu
|
|
|
|
def _calc_angles(self, h, k, l, extra_cons):
|
|
wl = self.get_wavelength()
|
|
pos, _virtual_angles = next(iter(
|
|
self.get_position(h, k, l, extra_cons)
|
|
))
|
|
return pos.mu, pos.chi, pos.phi, pos.nu
|
|
|
|
|
|
h = 6.62607015e-34 # J s
|
|
c = 299792458 # m / s
|
|
e = 1.60217663e-19 # C
|
|
f = h * c / e * 1e-3 * 1e9 *1e4# E [eV] = f / lambda [nm]
|
|
|
|
|
|
class Wavelength(Adjustable):
|
|
|
|
def __init__(self, energy):
|
|
self.energy = energy
|
|
# assert self.energy.units == "keV" # otherwise conversion is wrong
|
|
super().__init__(energy.ID + "_WL", name=energy.name + " as Wavelength", units="nm")
|
|
|
|
def get_current_value(self):
|
|
E = self.energy.get_current_value()
|
|
return f / E
|
|
|
|
def set_target_value(self, value):
|
|
E = f / value
|
|
return self.energy.set_target_value(E)
|
|
|
|
def is_moving(self):
|
|
return self.energy.is_moving()
|
|
|
|
|
|
|
|
class HistoryDummy(Adjustable):
|
|
|
|
def __init__(self, ID, initial_value=None, **kwargs):
|
|
super().__init__(ID, **kwargs)
|
|
self.history = [] if initial_value is None else [initial_value]
|
|
|
|
@classmethod
|
|
def init_from(cls, adj):
|
|
ID = "HD:" + adj.ID
|
|
initial_value = adj.get_current_value()
|
|
name = cls.__name__ + " for " + adj.name
|
|
units = adj.units
|
|
return cls(ID, initial_value=initial_value, name=name, units=units)
|
|
|
|
def get_current_value(self):
|
|
try:
|
|
return self.history[-1]
|
|
except IndexError:
|
|
return None
|
|
|
|
def set_target_value(self, value):
|
|
self.history.append(value)
|
|
|
|
def is_moving(self):
|
|
return False
|
|
|
|
|
|
def print_history(*adjs, enumerate_lines=True, make_legend=True):
|
|
labels = []
|
|
histories = []
|
|
for a in adjs:
|
|
try:
|
|
h = a.history
|
|
except AttributeError:
|
|
continue
|
|
labels.append(repr(a))
|
|
histories.append(h)
|
|
print(printable_table(histories, labels, enumerate_lines=enumerate_lines, make_legend=make_legend))
|
|
|
|
|
|
|
|
## these point to the different motors
|
|
#mu = Motor("SATES30-RIXS:MOT_SRY.VAL")
|
|
#chi = Motor("SATES30-RIXS:MOT_SRZ.VAL")
|
|
#phi = Motor("SATES30-RIXS:MOT_SRX.VAL")
|
|
#nu = Motor("SATES30-RIXS:MOT_DRY.VAL")
|
|
|
|
## and this to the machine wavelength (maybe this needs to be calculated from the energy? then we should add a Wavelength wrapper...)
|
|
#wl = Wavelength(PVAdjustable("MACHINE:ENERGY"))
|
|
|
|
## put it all together:
|
|
#q = QSpace3D("SOMETHING:Q", mu, chi, phi, nu, wl)
|
|
|
|
## in ipython
|
|
#q.set_lattice("SiO2", 4.913, 5.405)
|
|
#q.set_target_value(1.5)
|
|
|
|
|
|
|