How to use the diffcalc solver#
This guide shows how to create a diffractometer with the diffcalc
solver, orient a crystalline sample, and compute reciprocal-space
positions. It assumes you have already installed the
package.
The diffcalc solver wraps diffcalc-core. This library provides a single
six-circle geometry, diffcalc_4S_2D (the
psic geometry described by You 1999),
with 23 operating modes.
Note
diffcalc-core is an optional backend. Install it with
pip install hklpy2-solvers[diffcalc] or
conda install -c paulscherrerinstitute diffcalc-core. See
Installation for details. Requesting the diffcalc solver
without the backend raises an error explaining how to install it.
Create a diffractometer#
import hklpy2
psic = hklpy2.creator(
solver="diffcalc",
geometry="diffcalc_4S_2D",
name="psic",
)
The object psic has six real axes (mu, delta, nu,
eta, chi, phi) and three pseudo axes (h, k, l).
Set the crystal lattice#
psic.add_sample(name="silicon", a=hklpy2.SI_LATTICE_PARAMETER)
psic.beam.wavelength.put(1.54)
Add orientation reflections#
Provide two reflections measured at known motor positions:
r1 = psic.add_reflection(
pseudos={"h": 4, "k": 0, "l": 0},
reals={"mu": 0, "delta": 69.0966, "nu": 0, "eta": 34.5483, "chi": 0, "phi": 0},
wavelength=1.54,
name="r1",
)
r2 = psic.add_reflection(
pseudos={"h": 0, "k": 4, "l": 0},
reals={"mu": 0, "delta": 69.0966, "nu": 0, "eta": 34.5483, "chi": 0, "phi": 90},
wavelength=1.54,
name="r2",
)
Calculate the UB matrix#
psic.core.calc_UB(r1, r2)
Choose an operating mode#
The default mode is bisect fixed_mu fixed_nu (canonical
bisecting_vertical: mu=0, nu=0; delta and eta acting as
ttheta and ttheta/2, respectively). To choose a different mode:
psic.core.mode = "fixed_mu fixed_chi fixed_phi"
See diffcalc solver for the full list of 23 modes.
Mode names are order-independent#
The mode name is the set of three diffcalc constraints that define the mode; the order in which those constraints are written does not matter. These assignments all select the same mode:
psic.core.mode = "bisect fixed_mu fixed_nu" # canonical form
psic.core.mode = "fixed_mu bisect fixed_nu" # equivalent
psic.core.mode = "fixed_nu fixed_mu bisect" # equivalent
After assignment, reading psic.core.mode always returns the
canonical (registered) form — in the example above,
"bisect fixed_mu fixed_nu". The same equivalence applies to
register_mode()
and unregister_mode():
re-registering a permutation of an existing mode name is rejected
as a duplicate, and unregistering a permutation of a registered
user mode removes the original. See 109.
Cross-reference to common conventions#
Mode names use diffcalc’s constraint vocabulary directly
(fixed_<axis>, bisect, a_eq_b, …) rather than the
bisecting_vertical / lifting_detector_<axis> /
double_diffraction vocabulary used by hkl_soleil and
ad_hoc solvers. The table below maps the most common
conventions onto the equivalent diffcalc mode:
Common-convention name |
Equivalent diffcalc mode |
Notes |
|---|---|---|
|
|
Vertical bisector: |
|
|
Horizontal bisector: |
|
|
All three sample-stage axes other than |
|
|
Equivalent of E6C |
|
|
Sample |
|
2-sample-fixed modes such as |
Pick the |
|
n/a in diffcalc-core |
diffcalc-core does not implement a double-diffraction
constraint; use the |
|
|
Reference-azimuth pinned to a fixed value (here 0). |
The diffcalc constraint categories are documented in
diffcalc.hkl.constraints.Constraints (see the
diffcalc-core documentation).
A valid combination is at most one detector constraint, at
most one reference constraint, and one to three sample
constraints, totalling three constraints overall.
Register a runtime mode#
The 23 built-in modes do not exhaust the constraint combinations
that diffcalc-core implements. Use
register_mode()
to add a new mode at runtime without forking the package:
solver = psic.core.solver
solver.register_mode(
"fixed_delta fixed_eta fixed_chi",
{"delta": 0.0, "eta": 0.0, "chi": 0.0},
)
psic.core.mode = "fixed_delta fixed_eta fixed_chi"
The mode is validated against
diffcalc.hkl.constraints.Constraints before acceptance:
the name cannot clash with a built-in, the dict must have exactly
three diffcalc-recognised constraints with no same-category
conflicts, and the combination must satisfy
is_current_mode_implemented(). Otherwise
SolverError is raised with a message
naming the offending input.
Remove a user mode with
unregister_mode().
Switch away first if the mode is currently active, so the
diffractometer’s cached mode stays consistent with the solver:
psic.core.mode = "bisect fixed_mu fixed_nu"
solver.unregister_mode("fixed_delta fixed_eta fixed_chi")
Persistence across export() / simulator_from_config()#
User-registered modes survive a save/restore cycle when the
diffractometer is reconstructed via
hklpy2.simulator_from_config() (see 108).
DiffcalcSolver._metadata writes a user_modes entry into
the solver: block of the YAML, and
simulator_from_config() forwards it as a solver_kwargs
entry that DiffcalcSolver.__init__() replays via
register_mode():
# The illustration below uses /path/to/mybeamline.yml as a
# stand-in for a real on-disk path you choose for your saved
# configuration.
psic.export("/path/to/mybeamline.yml")
...
import hklpy2
psic2 = hklpy2.simulator_from_config("/path/to/mybeamline.yml")
# user modes are back in psic2.core.solver.modes, and the
# active mode (saved at export time) is restored.
Note
hklpy2.diffract.DiffractometerBase.restore() does not
re-create the underlying solver, so calling restore() on
an existing diffractometer cannot replay solver state.
simulator_from_config() is the supported entry
point for full restoration. hklpy2.Core also caches the
active mode; call diffractometer.forward(...) (or
diffractometer.core.update_solver()) once after setting
core.mode before export() so the saved mode: field
reflects the current value.
Compute motor positions (forward)#
psic.forward(4, 0, 0)
This returns a single chosen motor-position solution for the given
(h, k, l) (an Hklpy2DiffractometerRealPos). The underlying
solver’s forward() may return multiple solutions; the
diffractometer picks one according to the policy assigned to
psic._forward_solution (defaults to
hklpy2.utils.pick_first_solution()). The complete list of
solutions can be returned from psic.core.forward((4, 0, 0)).
See the upstream hklpy2 guide How to Choose the Default forward()
Solution
for details.
Tip
The two call shapes differ: psic.forward(4, 0, 0) takes
h, k, l as separate positional arguments, while
psic.core.forward((4, 0, 0)) takes a single sequence
(tuple / list / ndarray) or dict (e.g. {"h": 4, "k": 0, "l": 0}).
Compute (h, k, l) from motor positions (inverse)#
psic.inverse(psic.real_position)
This returns the (h, k, l) values computed from the supplied
motor positions. psic.real_position is the current readout of
all real axes; pass a different set of values to compute (h, k, l)
at a hypothetical position instead.
Available geometries at a glance#
Geometry |
Real axes |
Modes |
Default mode |
|---|---|---|---|
mu, delta, nu, eta, chi, phi |
23 |
bisect fixed_mu fixed_nu |
See also
diffcalc solver — full reference for axes and modes
How to benchmark a solver geometry — measure solver throughput
hklpy2 user guide — full hklpy2 documentation