Demonstrate hklpy2’s API#
Note: This is a working notebook as the package is being developed. Anything in this notebook could change.
Basic hklpy2 package information#
Load the hklpy2 package and show basic information about it.
import datetime
import hklpy2
import math
print(f"{datetime.datetime.now()}")
print(f"{hklpy2.__version__=}")
print(f"{hklpy2.solvers()=}")
2025-03-26 12:20:47.933934
hklpy2.__version__='0.0.28.dev92+g12cedce'
hklpy2.solvers()={'hkl_soleil': 'hklpy2.backends.hkl_soleil:HklSolver', 'no_op': 'hklpy2.backends.no_op:NoOpSolver', 'th_tth': 'hklpy2.backends.th_tth_q:ThTthSolver'}
Easy to create a simulated diffractometer#
Create the simulated E4CV (4-circle) diffractometer.
from hklpy2 import creator
sim4c = creator(name="sim4c")
sim4c.wh()
h=0, k=0, l=0
wavelength=1.0
omega=0, chi=0, phi=0, tth=0
Create a $\theta-2\theta$ 2-circle diffractometer
using "th_tth"
, a different backend solver. This demonstrates the ability to choose between
different backend solvers.
The "th_tth"
solver
was written in Python to demonstrate this new capability as a design goal for hklpy2.
import hklpy2
powder = hklpy2.creator(name="powder", geometry="TH TTH Q", solver="th_tth")
powder.wh()
q=0
wavelength=1.0
th=0, tth=0
Simulated Fourc#
Here, we show the simpler form since all the default parameters will create this 4-circle geometry. Alternatively, we could write:
fourc = hklpy2.creator(name="fourc", geometry="E4CV", solver="hkl_soleil")
import hklpy2
fourc = hklpy2.creator(name="fourc")
Add a sample of cubic vibranium, lattice constant exactly \(2\pi\).
fourc.add_sample("vibranium", 2*math.pi, digits=3, replace=True) # or force a replacement
Sample(name='vibranium', lattice=Lattice(a=6.283, system='cubic'))
Show the current settings:
fourc.wh()
h=0, k=0, l=0
wavelength=1.0
omega=0, chi=0, phi=0, tth=0
Change samples#
It’s easy to switch between samples. Go back to the default sample
.
fourc.sample = "sample"
print(f"selected sample: {fourc.sample=}")
print(f"all samples: {fourc.samples=}")
selected sample: fourc.sample=Sample(name='sample', lattice=Lattice(a=1, system='cubic'))
all samples: fourc.samples={'sample': Sample(name='sample', lattice=Lattice(a=1, system='cubic')), 'vibranium': Sample(name='vibranium', lattice=Lattice(a=6.283, system='cubic'))}
Switch back to the vibranium
sample.
fourc.sample = "vibranium"
Orienting Reflections#
Add a couple reflections (with the eventual goal of calculating the $UB$ matrix).
r1 = fourc.add_reflection((1, 0, 0), (10, 0, 0, 20), name="r1")
r2 = fourc.add_reflection((0, 1, 0), (10, -90, 0, 20), name="r2")
print(f"{r1=}")
print(f"{r2=}")
fourc.core.calc_UB(r1, r2)
print(f"{fourc.sample.U=!r}")
print(f"{fourc.sample.UB=!r}")
print(f"{fourc.inverse(10, 0, 0, 20)=!r}")
r1=Reflection(name='r1', h=1, k=0, l=0)
r2=Reflection(name='r2', h=0, k=1, l=0)
fourc.sample.U=[[-0.0, -0.0, 1.0], [0.0, -1.0, -0.0], [1.0, -0.0, 0.0]]
fourc.sample.UB=[[-0.0, -0.0, 1.000000103446], [0.0, -0.999999873545, 1.45487e-07], [0.9999999899, 1.81162e-07, -8.7108e-08]]
fourc.inverse(10, 0, 0, 20)=Hklpy2DiffractometerPseudoPos(h=2.182127379109, k=0, l=0)
Swap the first two reflections.
fourc.sample.reflections.swap()
print(f"{fourc.sample.U=!r}")
print(f"{fourc.sample.UB=!r}")
print(f"{fourc.forward(1, 0, 0)=!r}")
print(f"{fourc.inverse(10, 0, 0, 20)=!r}")
fourc.sample.U=[[-0.0, -0.0, 1.0], [0.0, -1.0, -0.0], [1.0, -0.0, 0.0]]
fourc.sample.UB=[[-0.0, -0.0, 1.000000103446], [0.0, -0.999999873545, 1.45487e-07], [0.9999999899, 1.81162e-07, -8.7108e-08]]
fourc.forward(1, 0, 0)=Hklpy2DiffractometerRealPos(omega=4.564279161557, chi=0, phi=-2.0227e-08, tth=9.128558323113)
fourc.inverse(10, 0, 0, 20)=Hklpy2DiffractometerPseudoPos(h=2.182127379109, k=0, l=0)
Additional Design Goals#
Next steps demonstrate some additional design goals:
Easy to add additional axes, such as $\psi$, $h_2$, $k_2$, & $l_2$.
Even axes, such as energy, that are not used directly but may be interesting to include.
Support for axes used as extra parameters in various diffractometer modes.
User can specify which axes are to be used by the solver.
Automatic selection of pseudo and real axes (based on order of appearance).
User can choose any names for their axes.
Solver class provides some introspection:
name and version
geometries supported
axes and parameters used by a geometry and mode
print(f"{fourc.core.solver.geometries()=}")
fourc.core.solver.geometries()=['APS POLAR', 'E4CH', 'E4CV', 'E6C', 'ESRF ID01 PSIC', 'K4CV', 'K6C', 'PETRA3 P09 EH2', 'PETRA3 P23 4C', 'PETRA3 P23 6C', 'SOLEIL MARS', 'SOLEIL NANOSCOPIUM ROBOT', 'SOLEIL SIRIUS KAPPA', 'SOLEIL SIRIUS TURRET', 'SOLEIL SIXS MED1+2', 'SOLEIL SIXS MED2+2', 'SOLEIL SIXS MED2+3', 'SOLEIL SIXS MED2+3 v2', 'ZAXIS']
print(f"{fourc.solver.get()=}")
print(f"{fourc.geometry.get()=}")
print(f"{fourc.wavelength.get()=}")
fourc.solver.get()='hkl_soleil'
fourc.geometry.get()='E4CV'
fourc.wavelength.get()=1.0
print(f"{fourc.solver_name=}")
print(f"{fourc.core.solver=}")
print(f"{fourc.core.axes_xref=!r}") # our names to solver's names
print(f"{fourc.pseudo_axis_names=}") # our full ordered lists of names
print(f"{fourc.real_axis_names=}")
print(f"{fourc.core.solver.pseudo_axis_names=}") # solver's ordered lists of names
print(f"{fourc.core.solver.real_axis_names=}")
print(f"{fourc.core.solver.extra_axis_names=}")
fourc.solver_name='hkl_soleil'
fourc.core.solver=HklSolver(name='hkl_soleil', version='5.1.2', geometry='E4CV', engine_name='hkl', mode='bissector')
fourc.core.axes_xref={'h': 'h', 'k': 'k', 'l': 'l', 'omega': 'omega', 'chi': 'chi', 'phi': 'phi', 'tth': 'tth'}
fourc.pseudo_axis_names=['h', 'k', 'l']
fourc.real_axis_names=['omega', 'chi', 'phi', 'tth']
fourc.core.solver.pseudo_axis_names=['h', 'k', 'l']
fourc.core.solver.real_axis_names=['omega', 'chi', 'phi', 'tth']
fourc.core.solver.extra_axis_names=[]
Where is the diffractometer now?
fourc.wh()
h=0, k=0, l=0
wavelength=1.0
omega=0, chi=0, phi=0, tth=0
Show ophyd’s description of the diffractometer object.
fourc.summary()
data keys (* hints)
-------------------
*fourc_chi
*fourc_h
fourc_h_setpoint
*fourc_k
fourc_k_setpoint
*fourc_l
fourc_l_setpoint
*fourc_omega
*fourc_phi
*fourc_tth
read attrs
----------
h Hklpy2PseudoAxis ('fourc_h')
h.readback AttributeSignal ('fourc_h')
h.setpoint AttributeSignal ('fourc_h_setpoint')
k Hklpy2PseudoAxis ('fourc_k')
k.readback AttributeSignal ('fourc_k')
k.setpoint AttributeSignal ('fourc_k_setpoint')
l Hklpy2PseudoAxis ('fourc_l')
l.readback AttributeSignal ('fourc_l')
l.setpoint AttributeSignal ('fourc_l_setpoint')
omega SoftPositioner ('fourc_omega')
chi SoftPositioner ('fourc_chi')
phi SoftPositioner ('fourc_phi')
tth SoftPositioner ('fourc_tth')
config keys
-----------
fourc_geometry
fourc_solver
fourc_wavelength
configuration attrs
-------------------
geometry AttributeSignal ('fourc_geometry')
solver AttributeSignal ('fourc_solver')
wavelength AttributeSignal ('fourc_wavelength')
h Hklpy2PseudoAxis ('fourc_h')
k Hklpy2PseudoAxis ('fourc_k')
l Hklpy2PseudoAxis ('fourc_l')
unused attrs
------------